Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ jobs:
cache: npm
- run: npm ci
- run: npm run lint
# The error-handling guide includes these files verbatim, so a type error
# here means the published documentation no longer compiles.
- run: npm run typecheck:examples

test:
runs-on: ubuntu-latest
Expand All @@ -51,4 +54,5 @@ jobs:
node-version: 22
cache: npm
- run: npm ci
- run: npm test
# With coverage, so the thresholds in jest.config.js are enforced.
- run: npm run test:coverage
20 changes: 16 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,30 @@

### Breaking changes

- **Android**: rejection codes now reflect the failure. `presentCaptiveSigning` and `presentCaptiveSigningWithUrl` previously rejected every error as `signing_failed`; they now surface `not_initialized`, `not_logged_in`, `login_failed` or `signing_failed`, matching the codes the error table has always documented. Callers matching on `error.code === 'signing_failed'` to detect a missing `initialize()` or `loginWithAccessToken()` need to match the specific code instead.
- **iOS**: rejection codes now match the documented table and the Android module. Expo derives a code from the exception class name when none is set, so `not_initialized` reached JS as `ERR_NOT_INITIALIZED`, `signing_failed` as `ERR_SIGNING_FAILED`, and so on for every code the README has always listed. Callers matching on the `ERR_`-prefixed variants need to match the documented code instead.
- **iOS**: `presentCaptiveSigning` and `presentCaptiveSigningWithUrl` forward the failure's own code rather than rejecting everything as `signing_failed`. A failure to find a presenting view controller now rejects and emits `presentation_failed`. The rejection message is the underlying error text on its own, where it previously carried a `DocuSign signing failed:` prefix.
- **Android**: one `onSigningError` event per failure instead of two. The module emitted an event alongside the manager's own, which also flattened `recipient_signing_failed` into `signing_failed`. Listeners that deduplicated by hand can drop that workaround; listeners that counted events will see the count halve.
- Every failure from `initialize`, `loginWithAccessToken`, `presentCaptiveSigning` and `presentCaptiveSigningWithUrl` rejects with a `DocuSignError` on both platforms. It carries `code` (what failed), `reason` (why, set only from verifiable facts), `native` and `http` (the raw SDK error and any DocuSign response), and `toAttributes()`. Branch on `code` and `reason`, never on message text, which was rewritten to say what failed and what to check. See [docs/ERROR_HANDLING.md](docs/ERROR_HANDLING.md).
- `presentCaptiveSigning` and `presentCaptiveSigningWithUrl` never resolve with `status: 'error'`. iOS used it for SDK errors reported after the signing UI was on screen, while Android rejected the same failures. Both platforms now reject, so a resolve always means completed or cancelled. `'error'` stays in `SigningStatus` so existing `switch` statements compile.
- Rejection codes are the documented lowercase codes on both platforms. iOS emitted `ERR_`-prefixed codes that Expo derived from exception class names, and Android rejected most failures as `signing_failed`. Two caller mistakes that hid inside `signing_failed` now have their own codes: `signing_in_progress` and `invalid_signing_url`. A missing presenter on iOS, or a missing foreground Activity on Android, rejects with `presentation_failed`, and an iOS initialization failure rejects with `initialize_failed`.
- `addSigningErrorListener` receives a `DocuSignError` instead of `{ errorCode, errorMessage }`, and now receives every failure from those four functions exactly once, caller mistakes included. It no longer wraps the native `onSigningError` event, which stays available on `DocuSignModule`.
- `useDocuSignSigning` types `error` as `DocuSignError | null`.
- **Android**: one native `onSigningError` event per failure instead of two.

### New features

- `DocuSignError.reason` classifies a failure as `usage`, `network`, `auth`, `configuration`, `recipient` or `unknown`, so an app can show a message that fits and skip retries that cannot succeed. A login failure is checked against `/oauth/userinfo` with the same token, which separates an expired token (`auth`) from a valid token DocuSign still refuses (`configuration`).
- `DocuSignError.toAttributes()` returns flat, primitive attributes ready for Amplitude, New Relic, Sentry or any other tool.
- Messages and details are redacted before they reach app code: JWTs, `Bearer` credentials, URL query strings and token-like URL path segments are removed. The redaction is pattern-based, so `toAttributes()`, which carries no message text, is the safest thing to forward to third-party tools.
- **Android**: `initialize` failures carry the SDK exception's details instead of a message alone, and the underlying error is the root of the exception's cause chain, where the transport failure that explains a timeout actually sits.
- In development, a caller mistake also prints one console warning naming the fix, so a catch that shows a generic toast cannot hide it.
- **Android**: the SDK's own error code, the HTTP status of an SDK REST failure, and DocuSign's error body from the recipient-view request are kept. The module previously forwarded only the exception message, and the `signingUrl` strategy discarded the error body entirely when it fell back to `fetch`.
- New [error handling guide](docs/ERROR_HANDLING.md) covering translated copy, retries, reporting to Amplitude, New Relic and Sentry, and reading the results in production. Its examples live in `examples/error-handling` and are type-checked in CI.

- **Android**: Add `presentCaptiveSigningWithUrl` support. The URL flow now has iOS/Android parity and does not require `loginWithAccessToken`.
- **Android**: Add an opt-in `launchStrategy` on `presentCaptiveSigning`. `signingUrl` mints a recipient view and launches the SDK's URL overload, skipping the envelope download that runs on a size-derived read timeout floored at 15s and can leave the ceremony unopened on large envelopes. Falls back to `fetch` if the mint fails. Defaults to `fetch`, so upgrading changes nothing unless you opt in.

### Fixes

- **iOS**: the view controller to present from is looked up on the main thread. The lookup read `UIApplication.shared` on the background queue the JS call arrived on.
- **iOS**: a missing view controller settles the promise once. It previously completed the pending signing slot with a failure and also threw, rejecting the same call twice.
- **iOS**: `endSigningSession` no longer calls `DSMManager` off the main thread. Expo dispatches a synchronous `AsyncFunction` body on a serial background queue, so `clearAllWebCookies()` and `logout()` were reached off-main on every call, including the one `useDocuSignSigning`'s `reset()` makes between flows. The guard now lives in `clearWebCookiesAsync`, the only method touching `DSMManager` and `WKWebsiteDataStore` directly, so it covers every caller. Thanks to @virajpsimformsolutions for finding and fixing this.
- **iOS**: `reset()` no longer re-enters itself to reach the main thread. The hop sat below the block that cancels an in-flight signing promise, so the re-entrant pass ran that block twice and could cancel a session that claimed the slot in between.
- **iOS**: reject a blank or non-`https` `signingUrl` before presenting. `DSMEnvelopesManager.presentCaptiveSigning` validates nothing and presents unconditionally, so a malformed URL rendered an empty signing controller whose completion never fired and left the promise unsettled. `signingUrl` defaults to `""` when JS omits it, so this was reachable without a malformed URL at all. Brings iOS to parity with the Android guard below.
Expand Down
Loading
Loading