@xkova/sdk 0.1.0-alpha.2 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@ holders, wallets, payments, tokenization, and other customer resources.
6
6
  ## Install
7
7
 
8
8
  ```bash
9
- npm install @xkova/sdk@0.1.0-alpha.2
9
+ npm install @xkova/sdk@alpha
10
10
  ```
11
11
 
12
12
  Requires Node.js 22 or newer. Keep this package and your API keys on the server.
@@ -197,26 +197,17 @@ For your own OIDC provider, use `beginAuthorization` and
197
197
  code unless your integration explicitly assigns that exchange to XKOVA. Both
198
198
  sign-in paths use the same XKOVA session and store contracts.
199
199
 
200
- ### Sandbox demo sign-in
201
-
202
- For a sandbox application explicitly enabled for demo sign-in, call
203
- `completeSandboxDemoAuthorization({ state: start.state, transactionStore })`
204
- after `beginHostedAuthorization`. The SDK obtains the capability and exact
205
- callback from the server-held transaction, validates the returned callback and
206
- expiry, and returns `{ callbackUrl, expiresAt }`. Complete that callback through
207
- `completeHostedAuthorization` with the original browser flow cookie. The API
208
- reserves a demo account for the session. This operation is not
209
- available to ordinary tenant applications or production workspaces, and it
210
- accepts no member, email, wallet, or callback selector.
211
-
212
- Keep the same pending reservation and transaction after an ambiguous admission
213
- failure. Repeating the method uses the same idempotency key and recovers the
214
- same demo account reservation. The method never retries automatically.
215
- `isDefinitiveXKOVASandboxDemoAdmissionFailure(error)` identifies confirmed
216
- rejection before an account was reserved; network, malformed-response, and
217
- unknown server failures can leave the reservation outcome uncertain.
218
- API errors retain their status, correlation ID, and Retry-After. Both admission
219
- and session requests reject HTTP redirects.
200
+ ### Local development
201
+
202
+ The SDK accepts HTTP API URLs, browser origins, callbacks, and storage URLs on
203
+ `localhost` and `127.0.0.1`. Remote hosts require HTTPS. Register each origin
204
+ and callback with its exact scheme, host, port, and path. These two local hosts
205
+ are distinct origins; cookies and browser storage are not shared between them.
206
+
207
+ For local passkeys, serve the authentication page on `localhost` and use
208
+ `localhost` as its relying-party ID. An IP address cannot be a passkey
209
+ relying-party ID. Keep API keys and member sessions in your BFF, including
210
+ during local development.
220
211
 
221
212
  ### Stores and callback recovery
222
213
 
@@ -344,6 +335,10 @@ Business requests use `no-store` and reject redirects so credentials cannot
344
335
  follow a redirect. Network failures preserve the application session. Reuse the
345
336
  original idempotency key when reconciling an uncertain write.
346
337
 
338
+ `resolved.resources.self.profile()` returns the current member's profile,
339
+ including email and name. These fields are display metadata, not authentication
340
+ proof. Read the profile when displaying it instead of caching it in the session.
341
+
347
342
  `resolved.resources.self.verification()` returns the member's verification
348
343
  requirements and sessions through the public self-verification operation.
349
344
  `resolved.resources.self.uploadAvatar(image)` performs presign, object upload,
@@ -351,7 +346,7 @@ confirmation, and refreshed avatar lookup. Pass a nonempty PNG, JPEG, WebP, or G
351
346
  `Blob` of at most 5 MiB. Storage receives only the image bytes and their content
352
347
  headers, never bearer credentials, request signatures, member context, or cookies.
353
348
  The helper rejects redirects, expired capabilities, URL credentials, and non-HTTPS
354
- storage URLs (localhost HTTP is permitted for local development). It does not
349
+ storage URLs (HTTP on `localhost` or `127.0.0.1` is permitted for local development). It does not
355
350
  retry an upload. `XKOVAUploadError` identifies the object-storage stage without
356
351
  exposing the signed URL; API failures retain `XKOVAApiError` metadata.
357
352
 
@@ -371,7 +366,44 @@ Durable application cookies use the host-only `__Host-` prefix, `Secure`, `HttpO
371
366
 
372
367
  The publishable key is a non-secret application registration identifier. It grants no member or tenant authority by itself. The registered application pins tenant, workspace, environment, provider kind, issuer, audience, origins, redirects, and session policy, and must also match the authenticated workspace principal.
373
368
 
374
- ## Cancellation and deadlines
369
+ ### Recover an existing member payment
370
+
371
+ Read recovery state through the resolved member resources:
372
+
373
+ ```ts
374
+ const recovery = await resolved.resources.payments.recovery(paymentId);
375
+ // Call only after the member confirms cancellation in your app.
376
+ if (recovery.can_cancel) {
377
+ await resolved.resources.payments.cancelForMember(paymentId);
378
+ }
379
+ ```
380
+
381
+ The API permits reads by the sender or recipient. Only the sender can cancel
382
+ or continue. The API principal needs `payment.read` and `payment.cancel`
383
+ permissions for these operations. Your BFF must validate the session, exact
384
+ Origin, and CSRF token before cancellation. Cancellation does not require a
385
+ passkey because it sends no value.
386
+
387
+ When `can_continue` is true, use `beginMemberAction()` with
388
+ `resourceType: 'payment'`, the existing `resourceId`, and `action: 'sign'`.
389
+ Complete fresh authorization through the registered provider. Keep the original
390
+ amount, recipient, fees, and deadline. Do not create a second payment or reuse
391
+ an expired signing credential. Recovery uses the payment ID, so a new valid
392
+ session for the same member can recover it without the original form draft.
393
+
394
+ `approval_expired` means the signing window has closed. It remains cancellable
395
+ until a signature has been accepted. After acceptance, `processing` disables
396
+ both actions. `cancelled`, `completed`, and `failed` are separate outcomes;
397
+ retain all three in history. These actions apply to payments; escrow
398
+ cancellation has its own authorization and settlement rules.
399
+
400
+ Availability can change between a read and a click. After a conflict or an
401
+ uncertain cancellation response, read recovery state again. Repeating an
402
+ already completed member cancellation returns the cancelled payment. Aborting
403
+ an HTTP request or cancelling an authorization ceremony does not cancel the
404
+ payment.
405
+
406
+ ## Request cancellation and deadlines
375
407
 
376
408
  Set `timeoutMs` on the client for a default request deadline. Mutation options and workflow polling also accept an `AbortSignal`:
377
409
 
@@ -386,6 +418,33 @@ await xkova.payments.cancel('pay_...', {
386
418
 
387
419
  The SDK does not automatically retry writes. Use `withRetry` with a stable idempotency key when retry behavior is appropriate for your application.
388
420
 
421
+ ## Product enrollment
422
+
423
+ After a member accepts your displayed terms, call
424
+ `resources.self.acceptProductEnrollment(product, { terms_version })` from the
425
+ BFF. Select the product and terms version on your server. Read the saved result
426
+ with `resources.self.productEnrollment(product)` when the member returns.
427
+ Acceptance does not require an `Idempotency-Key` header. Repeating the same
428
+ acceptance preserves its timestamp. Changed terms or credit parameters with the
429
+ same product are rejected.
430
+
431
+ Sandbox applications can include `sandbox_credit: { token_deployment_id,
432
+ amount }` to issue a test-token welcome credit. Use an active dUSDC deployment.
433
+ The credit is issued once per member, workspace, and product, across application
434
+ registrations. The daily workspace budget is 1000 dUSDC. Keep amounts and token
435
+ selection in the BFF, and give each product a stable identifier.
436
+
437
+ For Hosted Auth, set `require_passkey: true` to hold activation and the credit
438
+ until the member has an active passkey and has acknowledged recovery codes.
439
+ `setup_required` means terms are saved and security setup remains incomplete.
440
+ Open Account Center with the `security` intent, then read the enrollment again
441
+ on return. The setup requirement is fixed when the enrollment is accepted.
442
+
443
+ Poll the enrollment after acceptance. `opening` means delivery is pending;
444
+ `active` means the credit has a successful receipt and an indexed balance.
445
+ `requires_review` needs operator investigation. Do not create another product
446
+ identifier to retry an uncertain credit. Credits do not debit bank accounts.
447
+
389
448
  ## Errors
390
449
 
391
450
  `XKOVAApiError.retryAfter` preserves the upstream `Retry-After` header when
@@ -432,11 +491,39 @@ idempotency, and settlement. It links the public customer OpenAPI and expanded
432
491
  documentation. Installed declarations determine the methods available in your
433
492
  version; live documentation may describe a newer release.
434
493
 
435
- ## Contributing
494
+ ## Writing checks for contributors
495
+
496
+ Use plain verbs, concrete examples, and comments that explain non-obvious
497
+ decisions. Vary section length, remove repeated conclusions, and write each
498
+ guide for its reader. Test failure and recovery cases alongside successful
499
+ calls. Keep commits small and omit attribution or co-author trailers.
500
+
501
+ The local pre-push hook checks both public SDKs before the existing test gates.
502
+ It reads each pushed branch or tag's committed files, plus new commit messages
503
+ that touch either package. Uncommitted edits cannot change that result. No
504
+ build or network access is needed. Use the repository's pinned Node version,
505
+ then install workspace dependencies with `pnpm install` to set up the hook.
506
+
507
+ To check edits before committing:
508
+
509
+ ```sh
510
+ pnpm --filter @xkova/sdk writing:check
511
+ ```
436
512
 
437
- Run `pnpm check` from the source repository root before committing. The
438
- [contributor guide](https://github.com/XKOVA/typescript-sdk/blob/main/CONTRIBUTING.md)
439
- covers generated files, local pre-push checks, writing rules, and packaging.
513
+ The check covers package guides, examples, public source and generated types,
514
+ and descriptive package metadata. It rejects stock wording, emoji, em dashes,
515
+ attribution, internal decision narratives, and implementation-provider details.
516
+ Describe installation, supported methods, inputs, results, and the steps an
517
+ application developer needs. Do not turn release planning or maintainer
518
+ conversations into package documentation. Standard license text, rule
519
+ definitions, and negative test fixtures are checked separately.
520
+
521
+ A pass is a mechanical check, not a judgment of authorship. Before pushing,
522
+ reread the result for sentence rhythm, repeated structures, useful comments,
523
+ accurate examples, and test quality. This gate does not scan historical file
524
+ contents or replace secret scanning. It checks commit messages locally;
525
+ review PR descriptions separately. Run `pnpm --filter @xkova/sdk writing:test`
526
+ when changing the rules or hook.
440
527
 
441
528
  ## License
442
529
 
package/dist/client.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { XKOVAApiError } from './errors.js';
2
+ import { isAllowedProtocol } from './url.js';
2
3
  import { generateIdempotencyKey } from './idempotency.js';
3
4
  import { signXKOVARequest, validateXKOVARequestSigner, xkovaContentDigest, } from './request-signing.js';
4
5
  import { AccountHoldersResource, } from './resources/account-holders.js';
@@ -240,8 +241,8 @@ function normalizeApiBaseUrl(baseUrl) {
240
241
  if (url.username || url.password || url.search || url.hash) {
241
242
  throw new TypeError('XKOVA SDK baseUrl must be an API origin or /v1 base URL');
242
243
  }
243
- if (url.protocol !== 'https:' && !(url.protocol === 'http:' && url.hostname === 'localhost')) {
244
- throw new TypeError('XKOVA SDK baseUrl must use HTTPS or localhost HTTP');
244
+ if (!isAllowedProtocol(url)) {
245
+ throw new TypeError('XKOVA SDK baseUrl must use HTTPS or loopback HTTP');
245
246
  }
246
247
  const pathname = url.pathname.replace(/\/+$/, '');
247
248
  if (pathname !== '' && pathname !== '/v1') {