diff --git a/.claude/rules/cross-sdk-parity.md b/.claude/rules/cross-sdk-parity.md index 9a6199e2..ed52fd84 100644 --- a/.claude/rules/cross-sdk-parity.md +++ b/.claude/rules/cross-sdk-parity.md @@ -16,6 +16,21 @@ All SDKs must implement the same operations. When adding a feature to one SDK, i | resend | `resend()` | `resend_email()` | `ResendEmail()` | `resend()` | `resendEmail()` | `resend_email()` | | getAuditTrail | `getAuditTrail()` | `get_audit_trail()` | `GetAuditTrail()` | `getAuditTrail()` | `getAuditTrail()` | `get_audit_trail()` | | sendReminder | `sendReminder()` | `send_reminder()` | `SendReminder()` | `sendReminder()` | `sendReminder()` | `send_reminder()` | +| createSigningUrl | `createSigningUrl()` | `create_signing_url()` | `CreateSigningURL()` | `createSigningUrl()` | `createSigningUrl()` | `create_signing_url()` | +| getEmbeddedSigningSettings | `getEmbeddedSigningSettings()` | `get_embedded_signing_settings()` | `GetEmbeddedSigningSettings()` | `getEmbeddedSigningSettings()` | `getEmbeddedSigningSettings()` | `get_embedded_signing_settings()` | +| createEmbeddedSignature | `createEmbeddedSignature()` | `create_embedded_signature()` | `CreateEmbeddedSignature()` | `createEmbeddedSignature()` | `createEmbeddedSignature()` | `create_embedded_signature()` | + +**Embedded signing notes:** `createSigningUrl` posts `{ recipientId | externalId (exactly one), +returnUrl? (https), identityAssertion? }` and unwraps `{ data: { results } }`. The API rejects unknown +keys (no `senderName` here). An identity assertion has four required keys plus six optional ones +(`method`, `methodDetail` — required when method is `other` — `assuranceLevel`, `verifiedName`, +`evidenceUrl`, `overrideEmailMatching`); typed SDKs omit unset optional keys from the wire. +`getEmbeddedSigningSettings` returns `enabled`, `allowExternalIdv`, `allowIdentityOverride`, +`defaultChannel`, `allowChannelOverride` and `allowedFrameAncestors` (empty = framing denied +everywhere); typed SDKs keep `allowChannelOverride` nullable so an API that doesn't report it reads +as unknown, not locked. `createEmbeddedSignature` defaults `sendEmail` to false, never sends an +explicit `otpChannel` (an omitted channel takes the org default), and treats `RecipientNotInTurn` / +`NotSignersTurn` / `RecipientAlreadySigned` as `pending` / `completed` results, not errors. **sendReminder note:** a standalone nudge, deliberately decoupled from the automatic reminder schedule — it ignores the configured cadence, works when reminders are disabled or the per-signer @@ -23,7 +38,10 @@ cap is spent, and does not consume that cap. Only CURRENT-signing-order signers later-order or already-signed recipient comes back as a `skipped_*` result rather than being dropped. `recipientIds` is optional (omit to remind everyone eligible); when supplied the API is all-or-nothing. Every SDK omits the key entirely for an empty list — the API requires at least one -id when the key is present, so sending `[]` would guarantee a 400. +id when the key is present, so sending `[]` would guarantee a 400. `external_idv` and `override` +recipients sign only through a single-use `createSigningUrl` link and are never emailed: remind +reports them as `skipped_requires_single_use_url`, resend skips them, and naming only such +recipients in either call is a 409 (conflict class) with code `RecipientRequiresSingleUseUrl`. **Schedule overrides:** both send paths (`createSignatureReviewLink`, `sendSignature`) accept the eight per-document reminder/expiration fields. Two rules every SDK follows: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68ed119d..f9145f43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,28 @@ jobs: - name: Run tests run: npm test + test-embed: + name: Test Embed Widget + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/embed + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Run tests + run: npm test + test-py: name: Test Python SDK runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index aef2ef2c..24d83f68 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,8 @@ target/ *.pdf *.html !ExampleAssets/** +# The Vite example app's entry page is source, not a generated file. +!examples/embedded-web-app/index.html convert-template.js generate-all-pdfs.js test-php-sdk/ diff --git a/README.md b/README.md index c2abcdde..58e733a0 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,14 @@ await TurboSign.void(documentId, 'Contract terms changed'); await TurboSign.resend(documentId, ['recipient-uuid']); ``` +### Embedded signing (host web-app) + +Embed the signing page in your own app instead of emailing a link: mint a signing URL server-side with +`createSigningUrl`, frame it, and (optionally) verify the signer with email/SMS OTP or your own IdP. See +the full runnable example — a host web-app that collects the signer's email, does email OTP (or a +simulated external identity verification), and completes via postMessage — in +[`examples/embedded-web-app`](./examples/embedded-web-app). + ### Field Types | Type | Description | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 7f94d208..e4d21e23 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -16,7 +16,7 @@ Each SDK wraps a single `HttpClient` class responsible for: 1. **Authentication**: Bearer token via `apiKey` or `accessToken`, sent in `Authorization` header. Org ID sent as `x-rapiddocx-org-id` header. 2. **Base URL**: Configurable, defaults to `https://api.turbodocx.com`. Env var fallback: `TURBODOCX_BASE_URL`. 3. **Response unwrapping**: Backend wraps responses in `{ "data": ... }`. The client auto-unwraps when the response has only a `data` key (smart unwrap). -4. **Error mapping**: HTTP status codes map to typed errors (400→Validation, 401→Auth, 404→NotFound, 429→RateLimit, other→TurboDocxError). +4. **Error mapping**: HTTP status codes map to typed errors (400→Validation, 401→Auth, 403→Authorization, 404→NotFound, 409→Conflict, 429→RateLimit, other→TurboDocxError). 5. **File upload**: Multipart form upload with magic-byte file type detection. ### Partner Client @@ -40,8 +40,10 @@ All SDKs follow: **explicit config > env var fallback > error**. ``` TurboDocxError (base) ├── AuthenticationError (401) +├── AuthorizationError (403) ├── ValidationError (400) ├── NotFoundError (404) +├── ConflictError (409) ├── RateLimitError (429) └── NetworkError (no status — fetch/connection failure) ``` diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..a4a0184d --- /dev/null +++ b/examples/README.md @@ -0,0 +1,11 @@ +# TurboDocx examples + +Runnable, self-contained examples that show a full integration end to end (beyond the per-language +snippets in each `packages/-sdk/examples/`). + +| Example | What it shows | +|---|---| +| [`embedded-web-app`](./embedded-web-app) | Embedded TurboSign signing in a **Vite + React + shadcn** app with **four paths** in one place: **Single signer** (hand-rolled `