spaps-sdk 1.13.0 → 1.13.2

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/CHANGELOG.md CHANGED
@@ -6,6 +6,10 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ - No changes yet.
10
+
11
+ ## [1.13.1] - 2026-06-20
12
+
9
13
  ### Added
10
14
 
11
15
  - Added `whitelist`, `users`, and `supportTelemetry` namespaces so the TypeScript SDK matches consumer-facing Python client domain coverage.
package/README.md CHANGED
@@ -679,7 +679,7 @@ npm run test:readme
679
679
  ## Metadata
680
680
 
681
681
  - `package_name`: `spaps-sdk`
682
- - `latest_version`: `1.13.0`
682
+ - `latest_version`: `1.13.1`
683
683
  - `minimum_runtime`: `Node.js >=14.0.0`
684
684
  - `api_base_url`: `https://api.sweetpotato.dev`
685
685
 
package/dist/index.d.mts CHANGED
@@ -186,6 +186,28 @@ interface WebSocketAuthHelperConfig {
186
186
  * Should perform the refresh and return the new access token.
187
187
  */
188
188
  refreshAccessToken: () => Promise<string>;
189
+ /**
190
+ * Optional: mint a short-lived, single-use WebSocket connect ticket.
191
+ *
192
+ * When provided, the helper opens the socket with `?ticket=<ticket>` instead
193
+ * of placing any long-lived credential in the URL. This is the preferred,
194
+ * server-aligned path: it matches SPAPS `POST /realtime/ws-ticket`, whose
195
+ * tickets are single-use and expire in seconds, so a leak via logs / proxies
196
+ * / referrers is inert. A fresh ticket is minted for every connection attempt
197
+ * (including reconnects), because each ticket is consumed on use.
198
+ *
199
+ * When omitted, the helper falls back to carrying the bearer token in the
200
+ * WebSocket subprotocol (the `Sec-WebSocket-Protocol` handshake header) rather
201
+ * than the URL query string — so the long-lived JWT is never logged. The
202
+ * server must read the subprotocol for the fallback to authenticate.
203
+ */
204
+ getConnectTicket?: () => Promise<string> | string;
205
+ /**
206
+ * Milliseconds to wait for the socket to open before rejecting `connect()`.
207
+ * Prevents `connect()` from hanging forever when the socket never opens.
208
+ * 0 disables the timeout (default 10000).
209
+ */
210
+ connectTimeoutMs?: number;
189
211
  /** Seconds before token expiry to proactively refresh (default 30). */
190
212
  refreshBufferSeconds?: number;
191
213
  /** Maximum number of reconnection attempts on auth failures (default 3). */
@@ -218,7 +240,11 @@ declare class WebSocketAuthHelper {
218
240
  constructor(config: WebSocketAuthHelperConfig);
219
241
  /**
220
242
  * Open an authenticated WebSocket connection.
221
- * Appends the access token as a query parameter.
243
+ *
244
+ * The long-lived access token is never placed in the URL. Authentication uses
245
+ * a short-lived single-use connect ticket (`?ticket=...`) when
246
+ * `getConnectTicket` is configured, otherwise the bearer token is carried in
247
+ * the WebSocket subprotocol (handshake header).
222
248
  */
223
249
  connect(): Promise<void>;
224
250
  /**
@@ -230,8 +256,23 @@ declare class WebSocketAuthHelper {
230
256
  * Throws if the connection is not open.
231
257
  */
232
258
  send(data: string): void;
233
- /** Build the authenticated WebSocket URL. */
234
- buildAuthUrl(): string;
259
+ /**
260
+ * Build the WebSocket URL.
261
+ *
262
+ * Never embeds the long-lived access token. When a short-lived connect
263
+ * `ticket` is supplied it is appended as `?ticket=<ticket>` (single-use,
264
+ * seconds-long TTL — inert if leaked). Otherwise the bare base URL is
265
+ * returned and the credential travels in the subprotocol instead.
266
+ */
267
+ buildAuthUrl(ticket?: string): string;
268
+ /**
269
+ * Build the WebSocket subprotocols carrying the bearer credential.
270
+ *
271
+ * Used only by the no-ticket fallback so the long-lived JWT travels in the
272
+ * `Sec-WebSocket-Protocol` handshake header rather than the URL query string.
273
+ * Throws when no access token is available.
274
+ */
275
+ buildAuthProtocols(): string[];
235
276
  private openConnection;
236
277
  private handleAuthClose;
237
278
  private handleNetworkClose;
@@ -301,6 +342,7 @@ interface EmailSendOptions {
301
342
  to: string;
302
343
  context: Record<string, string | number | boolean>;
303
344
  userId?: string;
345
+ idempotencyKey?: string;
304
346
  }
305
347
  interface EmailSendResult {
306
348
  success: boolean;
@@ -678,17 +720,22 @@ interface ListMembershipsParams {
678
720
  cursor?: string;
679
721
  }
680
722
  interface SupportActor {
681
- type: string;
723
+ type: SupportActorType;
682
724
  id?: string | null;
683
725
  }
726
+ type SupportActorType = 'user' | 'practitioner' | 'anonymous' | 'service' | 'support_operator';
727
+ type SupportCaseStatus = 'open' | 'in_progress' | 'resolved' | 'ignored';
728
+ type SupportCasePriority = 'low' | 'normal' | 'high' | 'urgent';
729
+ type SupportEventSeverity = 'info' | 'warning' | 'error' | 'critical';
730
+ type SupportSourceChannel = 'issue_log' | 'support_thread' | 'email' | 'frontend' | 'api' | 'webhook';
684
731
  interface SupportCaseSummary {
685
732
  id: string;
686
733
  application_id: string;
687
734
  external_case_ref?: string | null;
688
735
  title?: string | null;
689
- status: string;
690
- priority: string;
691
- highest_severity: string;
736
+ status: SupportCaseStatus;
737
+ priority: SupportCasePriority;
738
+ highest_severity: SupportEventSeverity;
692
739
  assigned_to_user_id?: string | null;
693
740
  first_event_at: string;
694
741
  last_event_at: string;
@@ -700,9 +747,9 @@ interface SupportCaseEvent {
700
747
  id: string;
701
748
  case_id: string;
702
749
  event_key: string;
703
- source_channel: string;
750
+ source_channel: SupportSourceChannel;
704
751
  event_type: string;
705
- severity: string;
752
+ severity: SupportEventSeverity;
706
753
  actor: SupportActor;
707
754
  correlation_id?: string | null;
708
755
  occurred_at: string;
@@ -712,9 +759,9 @@ interface SupportCaseEvent {
712
759
  interface SupportTelemetryIngestRequest {
713
760
  event_key: string;
714
761
  occurred_at: string;
715
- source_channel: string;
762
+ source_channel: SupportSourceChannel;
716
763
  event_type: string;
717
- severity: string;
764
+ severity: SupportEventSeverity;
718
765
  actor: SupportActor;
719
766
  payload: Record<string, unknown>;
720
767
  application_id?: string | null;
@@ -727,13 +774,13 @@ interface SupportTelemetryIngestResponse {
727
774
  case_id: string;
728
775
  created_case: boolean;
729
776
  deduplicated: boolean;
730
- case_status: string;
777
+ case_status: SupportCaseStatus;
731
778
  }
732
779
  interface SupportTelemetryListCasesParams {
733
780
  application_id?: string;
734
- status?: string;
735
- priority?: string;
736
- severity_gte?: string;
781
+ status?: SupportCaseStatus;
782
+ priority?: SupportCasePriority;
783
+ severity_gte?: SupportEventSeverity;
737
784
  assigned_to_user_id?: string;
738
785
  limit?: number;
739
786
  offset?: number;
@@ -748,16 +795,16 @@ interface SupportTelemetryCaseDetailResponse {
748
795
  event_total: number;
749
796
  }
750
797
  interface SupportTelemetryPatchCaseRequest {
751
- status?: string;
752
- priority?: string;
798
+ status?: SupportCaseStatus;
799
+ priority?: SupportCasePriority;
753
800
  assigned_to_user_id?: string | null;
754
801
  resolution_note?: string | null;
755
802
  }
756
803
  interface SupportTelemetryPatchCaseResponse {
757
804
  case: {
758
805
  id: string;
759
- status: string;
760
- priority: string;
806
+ status: SupportCaseStatus;
807
+ priority: SupportCasePriority;
761
808
  assigned_to_user_id?: string | null;
762
809
  resolved_at?: string | null;
763
810
  updated_at: string;
@@ -768,8 +815,8 @@ interface SupportTelemetryCaseByExternalResponse {
768
815
  id: string;
769
816
  application_id: string;
770
817
  external_case_ref?: string | null;
771
- status: string;
772
- priority: string;
818
+ status: SupportCaseStatus;
819
+ priority: SupportCasePriority;
773
820
  assigned_to_user_id?: string | null;
774
821
  resolved_at?: string | null;
775
822
  updated_at: string;
@@ -1390,9 +1437,21 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
1390
1437
  private normalizeAuthPayload;
1391
1438
  private storeAuthTokens;
1392
1439
  private unwrapAuthMethodResponse;
1440
+ /**
1441
+ * Unwrap the SPAPS `{ success, data }` response envelope for the legacy
1442
+ * token-issuing auth methods (login/register/walletSignIn/refresh).
1443
+ *
1444
+ * The server's ResponseEnvelopeMiddleware wraps these responses, so the real
1445
+ * tokens live at `response.data.data`. Reading `response.data.access_token`
1446
+ * (one level too shallow) silently dropped the tokens. Reuse the shared
1447
+ * `unwrapApiResponse` primitive so envelope handling and error fidelity stay
1448
+ * consistent with the rest of the client.
1449
+ */
1450
+ private extractAuthResponse;
1393
1451
  private static isSdkManagedHeader;
1394
1452
  private static hasHeader;
1395
1453
  private static setHeader;
1454
+ private static deleteHeader;
1396
1455
  private static assertSafeHeaderValue;
1397
1456
  private applyCustomHeaders;
1398
1457
  admin: {
@@ -1745,7 +1804,7 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
1745
1804
  register: (payload: {
1746
1805
  email: string;
1747
1806
  password: string;
1748
- }) => Promise<any>;
1807
+ }) => Promise<AuthMethodResponse>;
1749
1808
  /**
1750
1809
  * Verify a magic link token and authenticate the user.
1751
1810
  * Returns full auth response with tokens and auto-stores them.
@@ -2282,4 +2341,4 @@ declare function createServerClient(secretKey: string, options?: Omit<SPAPSConfi
2282
2341
  */
2283
2342
  declare function detectKeyType(key: string): ApiKeyType | null;
2284
2343
 
2285
- export { type AccessDecisionRequest, type AccessDecisionResponse, type AccountDeletionResponse, type ActionPreparationRequest, type ActionPreparationResponse, type AdminConfig, type ApiKeyType, type AuthDiscoveryMethod, type AuthMethodDescriptor, type AuthMethodResponse, type AuthMethodsResponse, type BatchEmailsResponse, type BatchUsersResponse, type CapabilityActionEnvironment, type CapabilityControlHints, type CapabilityDecisionActor, type CapabilityDecisionFact, type CapabilityDecisionNextAction, type CapabilityDecisionOutcome, type CapabilityDecisionReason, type CapabilityDecisionResource, type CapabilityDecisionSource, type CapabilityGraphContractResponse, type CapabilityGraphImpactQuery, type CapabilityGraphImpactResponse, type CapabilityGraphNode, type CapabilityGraphNodesQuery, type CapabilityGraphNodesResponse, type CapabilityGraphPath, type CapabilityGraphPathsQuery, type CapabilityGraphPathsResponse, type CapabilityGraphProjectionStatus, type CapabilityGraphRefreshDiagnostics, type CapabilityGraphRefreshPhase2Gate, type CapabilityGraphRefreshResponse, type CapabilityGraphRefreshSourceTiming, type CapabilityGraphRowStatus, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, type CreateSkillEvalCaseRequest, type CreateSkillEvalGovernanceSnapshotRequest, type CurrentUserProjectAccessParams, type CurrentUserProjectGrantListParams, DEFAULT_ADMIN_ACCOUNTS, type EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type EntitlementCheckResult, type EntitlementListParams, type FeatureContext, type FeatureDefinition, FeatureEvaluator, type GraphExplainResponse, type HeaderProvider, ISSUE_REPORT_ATTACHMENT_ALLOWED_MIME_TYPES, ISSUE_REPORT_ATTACHMENT_MAX_BYTES, ISSUE_REPORT_ATTACHMENT_MAX_RETAINED, type ImportSkillEvalGovernanceOutcomeRequest, type IssueReportAttachmentMimeType, type IssueReportAttachmentUploadOptions, type IssueReportListParams, type IssueReportStatusParams, type ListMembershipsParams, type ListMembershipsResponse, type MarketingEventIngestRequest, type MarketingEventIngestResponse, type MarketingEventType, type MarketingExperimentDecision, type MarketingExperimentEffectDecision, type MarketingExperimentMinSampleDecision, type MarketingExperimentRecommendation, type MarketingExperimentResultsResponse, type MarketingExperimentSrmDecision, type MarketingExperimentSrmStatus, type MarketingExperimentVariantResult, type Membership, type MembershipInvitation, type MembershipMutationRequest, type MembershipMutationResponse, type MembershipRemoveResponse, type MembershipStatus, type MfaRequiredAuthResponse, type MfaTotpActivateRequest, type MfaTotpActivateResponse, type MfaTotpDisableRequest, type MfaTotpDisableResponse, type MfaTotpEnrollResponse, type MfaVerifyRequest, type MfaVerifyResponse, type OidcNonceResponse, type OidcSignInRequest, type PermissionCheckResult, PermissionChecker, type PersistedDecisionTrace, type PersistedDecisionTraceStep, type PreparedCapabilityActionStatus, type PreparedCapabilityExecution, type PreparedCapabilityNextAction, type PreparedCapabilitySideEffect, type RespondToSkillEvalReviewRequest, type RevealSkillEvalEvidenceRequest, RoleHierarchy, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type SPAPSEnvelope, type SPAPSErrorDiagnostic, type SPAPSErrorRemediation, SPAPSSDKError, type SPAPSSDKErrorOptions, type SessionContext, type SkillEvalAccessMode, type SkillEvalActorAccess, type SkillEvalCandidateInput, type SkillEvalCandidateResponse, type SkillEvalCasePolicy, type SkillEvalCaseResponse, type SkillEvalConfidence, type SkillEvalCreateOptions, type SkillEvalDisclosurePolicy, type SkillEvalEligibilitySource, type SkillEvalGovernanceOutcomeResult, type SkillEvalGovernancePurpose, type SkillEvalGovernanceSnapshotResult, type SkillEvalInsight, type SkillEvalInsightsResponse, type SkillEvalModelEffort, type SkillEvalMutationOptions, type SkillEvalPosterResponse, type SkillEvalPosterResponseResult, type SkillEvalRevealField, type SkillEvalRevealResult, type SkillEvalReviewMarkInput, type SkillEvalReviewMarkKind, type SkillEvalReviewResponse, type SkillEvalReviewRoom, type SkillEvalReviewerEligibilityInput, type SkillEvalRewardEvent, type SmsOtpRequest, type SmsOtpRequestResponse, type SmsOtpVerifyRequest, type SubmitSkillEvalReviewRequest, type SupportActor, type SupportCaseEvent, type SupportCaseSummary, type SupportTelemetryCaseByExternalResponse, type SupportTelemetryCaseDetailResponse, type SupportTelemetryIngestRequest, type SupportTelemetryIngestResponse, type SupportTelemetryListCasesParams, type SupportTelemetryListCasesResponse, type SupportTelemetryPatchCaseRequest, type SupportTelemetryPatchCaseResponse, type TemplateVariable, TokenManager, type UserBatchInfo, WalletUtils, type WebAuthnAssertionOptionsRequest, type WebAuthnAssertionVerifyRequest, type WebAuthnOptionsResponse, type WebAuthnRegisterVerifyRequest, type WebAuthnRegisterVerifyResponse, WebSocketAuthHelper, type WebSocketAuthHelperConfig, type WhitelistAddRequest, type WhitelistBulkAddRequest, type WhitelistBulkAddResponse, type WhitelistBulkEntry, type WhitelistBulkFailedEntry, type WhitelistCheckResponse, type WhitelistEntry, type WhitelistListParams, type WhitelistListResponse, type WhitelistMutationResponse, type WhitelistStatsResponse, type WhitelistUpdateRequest, type X402ExecuteActionOptions, X402PaymentRequiredSDKError, type X402ReceiptListParams, type X402VerifyHandoffOptions, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, isEnvelope, isErrorEnvelope, isSuccessEnvelope, unwrapEnvelope, unwrapNestedData, verifyCryptoWebhookSignature };
2344
+ export { type AccessDecisionRequest, type AccessDecisionResponse, type AccountDeletionResponse, type ActionPreparationRequest, type ActionPreparationResponse, type AdminConfig, type ApiKeyType, type AuthDiscoveryMethod, type AuthMethodDescriptor, type AuthMethodResponse, type AuthMethodsResponse, type BatchEmailsResponse, type BatchUsersResponse, type CapabilityActionEnvironment, type CapabilityControlHints, type CapabilityDecisionActor, type CapabilityDecisionFact, type CapabilityDecisionNextAction, type CapabilityDecisionOutcome, type CapabilityDecisionReason, type CapabilityDecisionResource, type CapabilityDecisionSource, type CapabilityGraphContractResponse, type CapabilityGraphImpactQuery, type CapabilityGraphImpactResponse, type CapabilityGraphNode, type CapabilityGraphNodesQuery, type CapabilityGraphNodesResponse, type CapabilityGraphPath, type CapabilityGraphPathsQuery, type CapabilityGraphPathsResponse, type CapabilityGraphProjectionStatus, type CapabilityGraphRefreshDiagnostics, type CapabilityGraphRefreshPhase2Gate, type CapabilityGraphRefreshResponse, type CapabilityGraphRefreshSourceTiming, type CapabilityGraphRowStatus, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, type CreateSkillEvalCaseRequest, type CreateSkillEvalGovernanceSnapshotRequest, type CurrentUserProjectAccessParams, type CurrentUserProjectGrantListParams, DEFAULT_ADMIN_ACCOUNTS, type EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type EntitlementCheckResult, type EntitlementListParams, type FeatureContext, type FeatureDefinition, FeatureEvaluator, type GraphExplainResponse, type HeaderProvider, ISSUE_REPORT_ATTACHMENT_ALLOWED_MIME_TYPES, ISSUE_REPORT_ATTACHMENT_MAX_BYTES, ISSUE_REPORT_ATTACHMENT_MAX_RETAINED, type ImportSkillEvalGovernanceOutcomeRequest, type IssueReportAttachmentMimeType, type IssueReportAttachmentUploadOptions, type IssueReportListParams, type IssueReportStatusParams, type ListMembershipsParams, type ListMembershipsResponse, type MarketingEventIngestRequest, type MarketingEventIngestResponse, type MarketingEventType, type MarketingExperimentDecision, type MarketingExperimentEffectDecision, type MarketingExperimentMinSampleDecision, type MarketingExperimentRecommendation, type MarketingExperimentResultsResponse, type MarketingExperimentSrmDecision, type MarketingExperimentSrmStatus, type MarketingExperimentVariantResult, type Membership, type MembershipInvitation, type MembershipMutationRequest, type MembershipMutationResponse, type MembershipRemoveResponse, type MembershipStatus, type MfaRequiredAuthResponse, type MfaTotpActivateRequest, type MfaTotpActivateResponse, type MfaTotpDisableRequest, type MfaTotpDisableResponse, type MfaTotpEnrollResponse, type MfaVerifyRequest, type MfaVerifyResponse, type OidcNonceResponse, type OidcSignInRequest, type PermissionCheckResult, PermissionChecker, type PersistedDecisionTrace, type PersistedDecisionTraceStep, type PreparedCapabilityActionStatus, type PreparedCapabilityExecution, type PreparedCapabilityNextAction, type PreparedCapabilitySideEffect, type RespondToSkillEvalReviewRequest, type RevealSkillEvalEvidenceRequest, RoleHierarchy, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type SPAPSEnvelope, type SPAPSErrorDiagnostic, type SPAPSErrorRemediation, SPAPSSDKError, type SPAPSSDKErrorOptions, type SessionContext, type SkillEvalAccessMode, type SkillEvalActorAccess, type SkillEvalCandidateInput, type SkillEvalCandidateResponse, type SkillEvalCasePolicy, type SkillEvalCaseResponse, type SkillEvalConfidence, type SkillEvalCreateOptions, type SkillEvalDisclosurePolicy, type SkillEvalEligibilitySource, type SkillEvalGovernanceOutcomeResult, type SkillEvalGovernancePurpose, type SkillEvalGovernanceSnapshotResult, type SkillEvalInsight, type SkillEvalInsightsResponse, type SkillEvalModelEffort, type SkillEvalMutationOptions, type SkillEvalPosterResponse, type SkillEvalPosterResponseResult, type SkillEvalRevealField, type SkillEvalRevealResult, type SkillEvalReviewMarkInput, type SkillEvalReviewMarkKind, type SkillEvalReviewResponse, type SkillEvalReviewRoom, type SkillEvalReviewerEligibilityInput, type SkillEvalRewardEvent, type SmsOtpRequest, type SmsOtpRequestResponse, type SmsOtpVerifyRequest, type SubmitSkillEvalReviewRequest, type SupportActor, type SupportActorType, type SupportCaseEvent, type SupportCasePriority, type SupportCaseStatus, type SupportCaseSummary, type SupportEventSeverity, type SupportSourceChannel, type SupportTelemetryCaseByExternalResponse, type SupportTelemetryCaseDetailResponse, type SupportTelemetryIngestRequest, type SupportTelemetryIngestResponse, type SupportTelemetryListCasesParams, type SupportTelemetryListCasesResponse, type SupportTelemetryPatchCaseRequest, type SupportTelemetryPatchCaseResponse, type TemplateVariable, TokenManager, type UserBatchInfo, WalletUtils, type WebAuthnAssertionOptionsRequest, type WebAuthnAssertionVerifyRequest, type WebAuthnOptionsResponse, type WebAuthnRegisterVerifyRequest, type WebAuthnRegisterVerifyResponse, WebSocketAuthHelper, type WebSocketAuthHelperConfig, type WhitelistAddRequest, type WhitelistBulkAddRequest, type WhitelistBulkAddResponse, type WhitelistBulkEntry, type WhitelistBulkFailedEntry, type WhitelistCheckResponse, type WhitelistEntry, type WhitelistListParams, type WhitelistListResponse, type WhitelistMutationResponse, type WhitelistStatsResponse, type WhitelistUpdateRequest, type X402ExecuteActionOptions, X402PaymentRequiredSDKError, type X402ReceiptListParams, type X402VerifyHandoffOptions, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, isEnvelope, isErrorEnvelope, isSuccessEnvelope, unwrapEnvelope, unwrapNestedData, verifyCryptoWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -186,6 +186,28 @@ interface WebSocketAuthHelperConfig {
186
186
  * Should perform the refresh and return the new access token.
187
187
  */
188
188
  refreshAccessToken: () => Promise<string>;
189
+ /**
190
+ * Optional: mint a short-lived, single-use WebSocket connect ticket.
191
+ *
192
+ * When provided, the helper opens the socket with `?ticket=<ticket>` instead
193
+ * of placing any long-lived credential in the URL. This is the preferred,
194
+ * server-aligned path: it matches SPAPS `POST /realtime/ws-ticket`, whose
195
+ * tickets are single-use and expire in seconds, so a leak via logs / proxies
196
+ * / referrers is inert. A fresh ticket is minted for every connection attempt
197
+ * (including reconnects), because each ticket is consumed on use.
198
+ *
199
+ * When omitted, the helper falls back to carrying the bearer token in the
200
+ * WebSocket subprotocol (the `Sec-WebSocket-Protocol` handshake header) rather
201
+ * than the URL query string — so the long-lived JWT is never logged. The
202
+ * server must read the subprotocol for the fallback to authenticate.
203
+ */
204
+ getConnectTicket?: () => Promise<string> | string;
205
+ /**
206
+ * Milliseconds to wait for the socket to open before rejecting `connect()`.
207
+ * Prevents `connect()` from hanging forever when the socket never opens.
208
+ * 0 disables the timeout (default 10000).
209
+ */
210
+ connectTimeoutMs?: number;
189
211
  /** Seconds before token expiry to proactively refresh (default 30). */
190
212
  refreshBufferSeconds?: number;
191
213
  /** Maximum number of reconnection attempts on auth failures (default 3). */
@@ -218,7 +240,11 @@ declare class WebSocketAuthHelper {
218
240
  constructor(config: WebSocketAuthHelperConfig);
219
241
  /**
220
242
  * Open an authenticated WebSocket connection.
221
- * Appends the access token as a query parameter.
243
+ *
244
+ * The long-lived access token is never placed in the URL. Authentication uses
245
+ * a short-lived single-use connect ticket (`?ticket=...`) when
246
+ * `getConnectTicket` is configured, otherwise the bearer token is carried in
247
+ * the WebSocket subprotocol (handshake header).
222
248
  */
223
249
  connect(): Promise<void>;
224
250
  /**
@@ -230,8 +256,23 @@ declare class WebSocketAuthHelper {
230
256
  * Throws if the connection is not open.
231
257
  */
232
258
  send(data: string): void;
233
- /** Build the authenticated WebSocket URL. */
234
- buildAuthUrl(): string;
259
+ /**
260
+ * Build the WebSocket URL.
261
+ *
262
+ * Never embeds the long-lived access token. When a short-lived connect
263
+ * `ticket` is supplied it is appended as `?ticket=<ticket>` (single-use,
264
+ * seconds-long TTL — inert if leaked). Otherwise the bare base URL is
265
+ * returned and the credential travels in the subprotocol instead.
266
+ */
267
+ buildAuthUrl(ticket?: string): string;
268
+ /**
269
+ * Build the WebSocket subprotocols carrying the bearer credential.
270
+ *
271
+ * Used only by the no-ticket fallback so the long-lived JWT travels in the
272
+ * `Sec-WebSocket-Protocol` handshake header rather than the URL query string.
273
+ * Throws when no access token is available.
274
+ */
275
+ buildAuthProtocols(): string[];
235
276
  private openConnection;
236
277
  private handleAuthClose;
237
278
  private handleNetworkClose;
@@ -301,6 +342,7 @@ interface EmailSendOptions {
301
342
  to: string;
302
343
  context: Record<string, string | number | boolean>;
303
344
  userId?: string;
345
+ idempotencyKey?: string;
304
346
  }
305
347
  interface EmailSendResult {
306
348
  success: boolean;
@@ -678,17 +720,22 @@ interface ListMembershipsParams {
678
720
  cursor?: string;
679
721
  }
680
722
  interface SupportActor {
681
- type: string;
723
+ type: SupportActorType;
682
724
  id?: string | null;
683
725
  }
726
+ type SupportActorType = 'user' | 'practitioner' | 'anonymous' | 'service' | 'support_operator';
727
+ type SupportCaseStatus = 'open' | 'in_progress' | 'resolved' | 'ignored';
728
+ type SupportCasePriority = 'low' | 'normal' | 'high' | 'urgent';
729
+ type SupportEventSeverity = 'info' | 'warning' | 'error' | 'critical';
730
+ type SupportSourceChannel = 'issue_log' | 'support_thread' | 'email' | 'frontend' | 'api' | 'webhook';
684
731
  interface SupportCaseSummary {
685
732
  id: string;
686
733
  application_id: string;
687
734
  external_case_ref?: string | null;
688
735
  title?: string | null;
689
- status: string;
690
- priority: string;
691
- highest_severity: string;
736
+ status: SupportCaseStatus;
737
+ priority: SupportCasePriority;
738
+ highest_severity: SupportEventSeverity;
692
739
  assigned_to_user_id?: string | null;
693
740
  first_event_at: string;
694
741
  last_event_at: string;
@@ -700,9 +747,9 @@ interface SupportCaseEvent {
700
747
  id: string;
701
748
  case_id: string;
702
749
  event_key: string;
703
- source_channel: string;
750
+ source_channel: SupportSourceChannel;
704
751
  event_type: string;
705
- severity: string;
752
+ severity: SupportEventSeverity;
706
753
  actor: SupportActor;
707
754
  correlation_id?: string | null;
708
755
  occurred_at: string;
@@ -712,9 +759,9 @@ interface SupportCaseEvent {
712
759
  interface SupportTelemetryIngestRequest {
713
760
  event_key: string;
714
761
  occurred_at: string;
715
- source_channel: string;
762
+ source_channel: SupportSourceChannel;
716
763
  event_type: string;
717
- severity: string;
764
+ severity: SupportEventSeverity;
718
765
  actor: SupportActor;
719
766
  payload: Record<string, unknown>;
720
767
  application_id?: string | null;
@@ -727,13 +774,13 @@ interface SupportTelemetryIngestResponse {
727
774
  case_id: string;
728
775
  created_case: boolean;
729
776
  deduplicated: boolean;
730
- case_status: string;
777
+ case_status: SupportCaseStatus;
731
778
  }
732
779
  interface SupportTelemetryListCasesParams {
733
780
  application_id?: string;
734
- status?: string;
735
- priority?: string;
736
- severity_gte?: string;
781
+ status?: SupportCaseStatus;
782
+ priority?: SupportCasePriority;
783
+ severity_gte?: SupportEventSeverity;
737
784
  assigned_to_user_id?: string;
738
785
  limit?: number;
739
786
  offset?: number;
@@ -748,16 +795,16 @@ interface SupportTelemetryCaseDetailResponse {
748
795
  event_total: number;
749
796
  }
750
797
  interface SupportTelemetryPatchCaseRequest {
751
- status?: string;
752
- priority?: string;
798
+ status?: SupportCaseStatus;
799
+ priority?: SupportCasePriority;
753
800
  assigned_to_user_id?: string | null;
754
801
  resolution_note?: string | null;
755
802
  }
756
803
  interface SupportTelemetryPatchCaseResponse {
757
804
  case: {
758
805
  id: string;
759
- status: string;
760
- priority: string;
806
+ status: SupportCaseStatus;
807
+ priority: SupportCasePriority;
761
808
  assigned_to_user_id?: string | null;
762
809
  resolved_at?: string | null;
763
810
  updated_at: string;
@@ -768,8 +815,8 @@ interface SupportTelemetryCaseByExternalResponse {
768
815
  id: string;
769
816
  application_id: string;
770
817
  external_case_ref?: string | null;
771
- status: string;
772
- priority: string;
818
+ status: SupportCaseStatus;
819
+ priority: SupportCasePriority;
773
820
  assigned_to_user_id?: string | null;
774
821
  resolved_at?: string | null;
775
822
  updated_at: string;
@@ -1390,9 +1437,21 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
1390
1437
  private normalizeAuthPayload;
1391
1438
  private storeAuthTokens;
1392
1439
  private unwrapAuthMethodResponse;
1440
+ /**
1441
+ * Unwrap the SPAPS `{ success, data }` response envelope for the legacy
1442
+ * token-issuing auth methods (login/register/walletSignIn/refresh).
1443
+ *
1444
+ * The server's ResponseEnvelopeMiddleware wraps these responses, so the real
1445
+ * tokens live at `response.data.data`. Reading `response.data.access_token`
1446
+ * (one level too shallow) silently dropped the tokens. Reuse the shared
1447
+ * `unwrapApiResponse` primitive so envelope handling and error fidelity stay
1448
+ * consistent with the rest of the client.
1449
+ */
1450
+ private extractAuthResponse;
1393
1451
  private static isSdkManagedHeader;
1394
1452
  private static hasHeader;
1395
1453
  private static setHeader;
1454
+ private static deleteHeader;
1396
1455
  private static assertSafeHeaderValue;
1397
1456
  private applyCustomHeaders;
1398
1457
  admin: {
@@ -1745,7 +1804,7 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
1745
1804
  register: (payload: {
1746
1805
  email: string;
1747
1806
  password: string;
1748
- }) => Promise<any>;
1807
+ }) => Promise<AuthMethodResponse>;
1749
1808
  /**
1750
1809
  * Verify a magic link token and authenticate the user.
1751
1810
  * Returns full auth response with tokens and auto-stores them.
@@ -2282,4 +2341,4 @@ declare function createServerClient(secretKey: string, options?: Omit<SPAPSConfi
2282
2341
  */
2283
2342
  declare function detectKeyType(key: string): ApiKeyType | null;
2284
2343
 
2285
- export { type AccessDecisionRequest, type AccessDecisionResponse, type AccountDeletionResponse, type ActionPreparationRequest, type ActionPreparationResponse, type AdminConfig, type ApiKeyType, type AuthDiscoveryMethod, type AuthMethodDescriptor, type AuthMethodResponse, type AuthMethodsResponse, type BatchEmailsResponse, type BatchUsersResponse, type CapabilityActionEnvironment, type CapabilityControlHints, type CapabilityDecisionActor, type CapabilityDecisionFact, type CapabilityDecisionNextAction, type CapabilityDecisionOutcome, type CapabilityDecisionReason, type CapabilityDecisionResource, type CapabilityDecisionSource, type CapabilityGraphContractResponse, type CapabilityGraphImpactQuery, type CapabilityGraphImpactResponse, type CapabilityGraphNode, type CapabilityGraphNodesQuery, type CapabilityGraphNodesResponse, type CapabilityGraphPath, type CapabilityGraphPathsQuery, type CapabilityGraphPathsResponse, type CapabilityGraphProjectionStatus, type CapabilityGraphRefreshDiagnostics, type CapabilityGraphRefreshPhase2Gate, type CapabilityGraphRefreshResponse, type CapabilityGraphRefreshSourceTiming, type CapabilityGraphRowStatus, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, type CreateSkillEvalCaseRequest, type CreateSkillEvalGovernanceSnapshotRequest, type CurrentUserProjectAccessParams, type CurrentUserProjectGrantListParams, DEFAULT_ADMIN_ACCOUNTS, type EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type EntitlementCheckResult, type EntitlementListParams, type FeatureContext, type FeatureDefinition, FeatureEvaluator, type GraphExplainResponse, type HeaderProvider, ISSUE_REPORT_ATTACHMENT_ALLOWED_MIME_TYPES, ISSUE_REPORT_ATTACHMENT_MAX_BYTES, ISSUE_REPORT_ATTACHMENT_MAX_RETAINED, type ImportSkillEvalGovernanceOutcomeRequest, type IssueReportAttachmentMimeType, type IssueReportAttachmentUploadOptions, type IssueReportListParams, type IssueReportStatusParams, type ListMembershipsParams, type ListMembershipsResponse, type MarketingEventIngestRequest, type MarketingEventIngestResponse, type MarketingEventType, type MarketingExperimentDecision, type MarketingExperimentEffectDecision, type MarketingExperimentMinSampleDecision, type MarketingExperimentRecommendation, type MarketingExperimentResultsResponse, type MarketingExperimentSrmDecision, type MarketingExperimentSrmStatus, type MarketingExperimentVariantResult, type Membership, type MembershipInvitation, type MembershipMutationRequest, type MembershipMutationResponse, type MembershipRemoveResponse, type MembershipStatus, type MfaRequiredAuthResponse, type MfaTotpActivateRequest, type MfaTotpActivateResponse, type MfaTotpDisableRequest, type MfaTotpDisableResponse, type MfaTotpEnrollResponse, type MfaVerifyRequest, type MfaVerifyResponse, type OidcNonceResponse, type OidcSignInRequest, type PermissionCheckResult, PermissionChecker, type PersistedDecisionTrace, type PersistedDecisionTraceStep, type PreparedCapabilityActionStatus, type PreparedCapabilityExecution, type PreparedCapabilityNextAction, type PreparedCapabilitySideEffect, type RespondToSkillEvalReviewRequest, type RevealSkillEvalEvidenceRequest, RoleHierarchy, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type SPAPSEnvelope, type SPAPSErrorDiagnostic, type SPAPSErrorRemediation, SPAPSSDKError, type SPAPSSDKErrorOptions, type SessionContext, type SkillEvalAccessMode, type SkillEvalActorAccess, type SkillEvalCandidateInput, type SkillEvalCandidateResponse, type SkillEvalCasePolicy, type SkillEvalCaseResponse, type SkillEvalConfidence, type SkillEvalCreateOptions, type SkillEvalDisclosurePolicy, type SkillEvalEligibilitySource, type SkillEvalGovernanceOutcomeResult, type SkillEvalGovernancePurpose, type SkillEvalGovernanceSnapshotResult, type SkillEvalInsight, type SkillEvalInsightsResponse, type SkillEvalModelEffort, type SkillEvalMutationOptions, type SkillEvalPosterResponse, type SkillEvalPosterResponseResult, type SkillEvalRevealField, type SkillEvalRevealResult, type SkillEvalReviewMarkInput, type SkillEvalReviewMarkKind, type SkillEvalReviewResponse, type SkillEvalReviewRoom, type SkillEvalReviewerEligibilityInput, type SkillEvalRewardEvent, type SmsOtpRequest, type SmsOtpRequestResponse, type SmsOtpVerifyRequest, type SubmitSkillEvalReviewRequest, type SupportActor, type SupportCaseEvent, type SupportCaseSummary, type SupportTelemetryCaseByExternalResponse, type SupportTelemetryCaseDetailResponse, type SupportTelemetryIngestRequest, type SupportTelemetryIngestResponse, type SupportTelemetryListCasesParams, type SupportTelemetryListCasesResponse, type SupportTelemetryPatchCaseRequest, type SupportTelemetryPatchCaseResponse, type TemplateVariable, TokenManager, type UserBatchInfo, WalletUtils, type WebAuthnAssertionOptionsRequest, type WebAuthnAssertionVerifyRequest, type WebAuthnOptionsResponse, type WebAuthnRegisterVerifyRequest, type WebAuthnRegisterVerifyResponse, WebSocketAuthHelper, type WebSocketAuthHelperConfig, type WhitelistAddRequest, type WhitelistBulkAddRequest, type WhitelistBulkAddResponse, type WhitelistBulkEntry, type WhitelistBulkFailedEntry, type WhitelistCheckResponse, type WhitelistEntry, type WhitelistListParams, type WhitelistListResponse, type WhitelistMutationResponse, type WhitelistStatsResponse, type WhitelistUpdateRequest, type X402ExecuteActionOptions, X402PaymentRequiredSDKError, type X402ReceiptListParams, type X402VerifyHandoffOptions, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, isEnvelope, isErrorEnvelope, isSuccessEnvelope, unwrapEnvelope, unwrapNestedData, verifyCryptoWebhookSignature };
2344
+ export { type AccessDecisionRequest, type AccessDecisionResponse, type AccountDeletionResponse, type ActionPreparationRequest, type ActionPreparationResponse, type AdminConfig, type ApiKeyType, type AuthDiscoveryMethod, type AuthMethodDescriptor, type AuthMethodResponse, type AuthMethodsResponse, type BatchEmailsResponse, type BatchUsersResponse, type CapabilityActionEnvironment, type CapabilityControlHints, type CapabilityDecisionActor, type CapabilityDecisionFact, type CapabilityDecisionNextAction, type CapabilityDecisionOutcome, type CapabilityDecisionReason, type CapabilityDecisionResource, type CapabilityDecisionSource, type CapabilityGraphContractResponse, type CapabilityGraphImpactQuery, type CapabilityGraphImpactResponse, type CapabilityGraphNode, type CapabilityGraphNodesQuery, type CapabilityGraphNodesResponse, type CapabilityGraphPath, type CapabilityGraphPathsQuery, type CapabilityGraphPathsResponse, type CapabilityGraphProjectionStatus, type CapabilityGraphRefreshDiagnostics, type CapabilityGraphRefreshPhase2Gate, type CapabilityGraphRefreshResponse, type CapabilityGraphRefreshSourceTiming, type CapabilityGraphRowStatus, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, type CreateSkillEvalCaseRequest, type CreateSkillEvalGovernanceSnapshotRequest, type CurrentUserProjectAccessParams, type CurrentUserProjectGrantListParams, DEFAULT_ADMIN_ACCOUNTS, type EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type EntitlementCheckResult, type EntitlementListParams, type FeatureContext, type FeatureDefinition, FeatureEvaluator, type GraphExplainResponse, type HeaderProvider, ISSUE_REPORT_ATTACHMENT_ALLOWED_MIME_TYPES, ISSUE_REPORT_ATTACHMENT_MAX_BYTES, ISSUE_REPORT_ATTACHMENT_MAX_RETAINED, type ImportSkillEvalGovernanceOutcomeRequest, type IssueReportAttachmentMimeType, type IssueReportAttachmentUploadOptions, type IssueReportListParams, type IssueReportStatusParams, type ListMembershipsParams, type ListMembershipsResponse, type MarketingEventIngestRequest, type MarketingEventIngestResponse, type MarketingEventType, type MarketingExperimentDecision, type MarketingExperimentEffectDecision, type MarketingExperimentMinSampleDecision, type MarketingExperimentRecommendation, type MarketingExperimentResultsResponse, type MarketingExperimentSrmDecision, type MarketingExperimentSrmStatus, type MarketingExperimentVariantResult, type Membership, type MembershipInvitation, type MembershipMutationRequest, type MembershipMutationResponse, type MembershipRemoveResponse, type MembershipStatus, type MfaRequiredAuthResponse, type MfaTotpActivateRequest, type MfaTotpActivateResponse, type MfaTotpDisableRequest, type MfaTotpDisableResponse, type MfaTotpEnrollResponse, type MfaVerifyRequest, type MfaVerifyResponse, type OidcNonceResponse, type OidcSignInRequest, type PermissionCheckResult, PermissionChecker, type PersistedDecisionTrace, type PersistedDecisionTraceStep, type PreparedCapabilityActionStatus, type PreparedCapabilityExecution, type PreparedCapabilityNextAction, type PreparedCapabilitySideEffect, type RespondToSkillEvalReviewRequest, type RevealSkillEvalEvidenceRequest, RoleHierarchy, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type SPAPSEnvelope, type SPAPSErrorDiagnostic, type SPAPSErrorRemediation, SPAPSSDKError, type SPAPSSDKErrorOptions, type SessionContext, type SkillEvalAccessMode, type SkillEvalActorAccess, type SkillEvalCandidateInput, type SkillEvalCandidateResponse, type SkillEvalCasePolicy, type SkillEvalCaseResponse, type SkillEvalConfidence, type SkillEvalCreateOptions, type SkillEvalDisclosurePolicy, type SkillEvalEligibilitySource, type SkillEvalGovernanceOutcomeResult, type SkillEvalGovernancePurpose, type SkillEvalGovernanceSnapshotResult, type SkillEvalInsight, type SkillEvalInsightsResponse, type SkillEvalModelEffort, type SkillEvalMutationOptions, type SkillEvalPosterResponse, type SkillEvalPosterResponseResult, type SkillEvalRevealField, type SkillEvalRevealResult, type SkillEvalReviewMarkInput, type SkillEvalReviewMarkKind, type SkillEvalReviewResponse, type SkillEvalReviewRoom, type SkillEvalReviewerEligibilityInput, type SkillEvalRewardEvent, type SmsOtpRequest, type SmsOtpRequestResponse, type SmsOtpVerifyRequest, type SubmitSkillEvalReviewRequest, type SupportActor, type SupportActorType, type SupportCaseEvent, type SupportCasePriority, type SupportCaseStatus, type SupportCaseSummary, type SupportEventSeverity, type SupportSourceChannel, type SupportTelemetryCaseByExternalResponse, type SupportTelemetryCaseDetailResponse, type SupportTelemetryIngestRequest, type SupportTelemetryIngestResponse, type SupportTelemetryListCasesParams, type SupportTelemetryListCasesResponse, type SupportTelemetryPatchCaseRequest, type SupportTelemetryPatchCaseResponse, type TemplateVariable, TokenManager, type UserBatchInfo, WalletUtils, type WebAuthnAssertionOptionsRequest, type WebAuthnAssertionVerifyRequest, type WebAuthnOptionsResponse, type WebAuthnRegisterVerifyRequest, type WebAuthnRegisterVerifyResponse, WebSocketAuthHelper, type WebSocketAuthHelperConfig, type WhitelistAddRequest, type WhitelistBulkAddRequest, type WhitelistBulkAddResponse, type WhitelistBulkEntry, type WhitelistBulkFailedEntry, type WhitelistCheckResponse, type WhitelistEntry, type WhitelistListParams, type WhitelistListResponse, type WhitelistMutationResponse, type WhitelistStatsResponse, type WhitelistUpdateRequest, type X402ExecuteActionOptions, X402PaymentRequiredSDKError, type X402ReceiptListParams, type X402VerifyHandoffOptions, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, isEnvelope, isErrorEnvelope, isSuccessEnvelope, unwrapEnvelope, unwrapNestedData, verifyCryptoWebhookSignature };
package/dist/index.js CHANGED
@@ -335,6 +335,7 @@ var FeatureEvaluator = class {
335
335
 
336
336
  // src/websocket-auth-helper.ts
337
337
  var AUTH_CLOSE_CODES = /* @__PURE__ */ new Set([4001, 4003]);
338
+ var WS_BEARER_SUBPROTOCOL = "spaps.bearer.v1";
338
339
  var WebSocketAuthHelper = class {
339
340
  config;
340
341
  ws = null;
@@ -348,6 +349,8 @@ var WebSocketAuthHelper = class {
348
349
  url: config.url,
349
350
  getAccessToken: config.getAccessToken,
350
351
  refreshAccessToken: config.refreshAccessToken,
352
+ getConnectTicket: config.getConnectTicket,
353
+ connectTimeoutMs: config.connectTimeoutMs ?? 1e4,
351
354
  refreshBufferSeconds: config.refreshBufferSeconds ?? 30,
352
355
  maxAuthRetries: config.maxAuthRetries ?? 3,
353
356
  maxNetworkRetries: config.maxNetworkRetries ?? 5,
@@ -363,7 +366,11 @@ var WebSocketAuthHelper = class {
363
366
  // ── Public API ──────────────────────────────────────────────────────────
364
367
  /**
365
368
  * Open an authenticated WebSocket connection.
366
- * Appends the access token as a query parameter.
369
+ *
370
+ * The long-lived access token is never placed in the URL. Authentication uses
371
+ * a short-lived single-use connect ticket (`?ticket=...`) when
372
+ * `getConnectTicket` is configured, otherwise the bearer token is carried in
373
+ * the WebSocket subprotocol (handshake header).
367
374
  */
368
375
  async connect() {
369
376
  this.intentionalClose = false;
@@ -393,30 +400,84 @@ var WebSocketAuthHelper = class {
393
400
  this.ws.send(data);
394
401
  }
395
402
  // ── Internal helpers ────────────────────────────────────────────────────
396
- /** Build the authenticated WebSocket URL. */
397
- buildAuthUrl() {
403
+ /**
404
+ * Build the WebSocket URL.
405
+ *
406
+ * Never embeds the long-lived access token. When a short-lived connect
407
+ * `ticket` is supplied it is appended as `?ticket=<ticket>` (single-use,
408
+ * seconds-long TTL — inert if leaked). Otherwise the bare base URL is
409
+ * returned and the credential travels in the subprotocol instead.
410
+ */
411
+ buildAuthUrl(ticket) {
412
+ if (ticket) {
413
+ const separator = this.config.url.includes("?") ? "&" : "?";
414
+ return `${this.config.url}${separator}ticket=${encodeURIComponent(ticket)}`;
415
+ }
416
+ return this.config.url;
417
+ }
418
+ /**
419
+ * Build the WebSocket subprotocols carrying the bearer credential.
420
+ *
421
+ * Used only by the no-ticket fallback so the long-lived JWT travels in the
422
+ * `Sec-WebSocket-Protocol` handshake header rather than the URL query string.
423
+ * Throws when no access token is available.
424
+ */
425
+ buildAuthProtocols() {
398
426
  const token = this.config.getAccessToken();
399
427
  if (!token) {
400
428
  throw new Error("No access token available for WebSocket authentication");
401
429
  }
402
- const separator = this.config.url.includes("?") ? "&" : "?";
403
- return `${this.config.url}${separator}token=${encodeURIComponent(token)}`;
430
+ return [WS_BEARER_SUBPROTOCOL, token];
404
431
  }
405
432
  async openConnection() {
406
- const url = this.buildAuthUrl();
433
+ let url;
434
+ let protocols;
435
+ if (this.config.getConnectTicket) {
436
+ const ticket = await this.config.getConnectTicket();
437
+ if (!ticket) {
438
+ throw new Error("Failed to obtain WebSocket connect ticket");
439
+ }
440
+ url = this.buildAuthUrl(ticket);
441
+ protocols = void 0;
442
+ } else {
443
+ url = this.buildAuthUrl();
444
+ protocols = this.buildAuthProtocols();
445
+ }
407
446
  return new Promise((resolve, reject) => {
447
+ let settled = false;
448
+ let connectTimer = null;
449
+ const settle = (action) => {
450
+ if (settled) return;
451
+ settled = true;
452
+ if (connectTimer) {
453
+ clearTimeout(connectTimer);
454
+ connectTimer = null;
455
+ }
456
+ action();
457
+ };
408
458
  try {
409
- this.ws = new WebSocket(url);
459
+ this.ws = protocols ? new WebSocket(url, protocols) : new WebSocket(url);
410
460
  } catch (err) {
411
- reject(err);
461
+ settle(() => reject(err));
412
462
  return;
413
463
  }
464
+ if (this.config.connectTimeoutMs > 0) {
465
+ connectTimer = setTimeout(() => {
466
+ settle(() => {
467
+ try {
468
+ this.ws?.close();
469
+ } catch {
470
+ }
471
+ reject(new Error("WebSocket connection timed out before opening"));
472
+ });
473
+ }, this.config.connectTimeoutMs);
474
+ }
414
475
  this.ws.onopen = () => {
415
476
  this.networkRetries = 0;
416
477
  this.startPing();
417
478
  this.scheduleTokenRefresh();
418
479
  this.config.onOpen?.();
419
- resolve();
480
+ settle(resolve);
420
481
  };
421
482
  this.ws.onmessage = (event) => {
422
483
  this.config.onMessage?.(String(event.data));
@@ -427,6 +488,13 @@ var WebSocketAuthHelper = class {
427
488
  this.ws.onclose = (event) => {
428
489
  this.cleanup();
429
490
  this.config.onClose?.(event.code, event.reason);
491
+ settle(
492
+ () => reject(
493
+ new Error(
494
+ `WebSocket closed before opening (code ${event.code}${event.reason ? `: ${event.reason}` : ""})`
495
+ )
496
+ )
497
+ );
430
498
  if (this.intentionalClose) {
431
499
  return;
432
500
  }
@@ -817,6 +885,22 @@ var SPAPSClient = class _SPAPSClient {
817
885
  this.storeAuthTokens(result);
818
886
  return result;
819
887
  }
888
+ /**
889
+ * Unwrap the SPAPS `{ success, data }` response envelope for the legacy
890
+ * token-issuing auth methods (login/register/walletSignIn/refresh).
891
+ *
892
+ * The server's ResponseEnvelopeMiddleware wraps these responses, so the real
893
+ * tokens live at `response.data.data`. Reading `response.data.access_token`
894
+ * (one level too shallow) silently dropped the tokens. Reuse the shared
895
+ * `unwrapApiResponse` primitive so envelope handling and error fidelity stay
896
+ * consistent with the rest of the client.
897
+ */
898
+ extractAuthResponse(response, fallback) {
899
+ return this.unwrapApiResponse(
900
+ response,
901
+ fallback
902
+ );
903
+ }
820
904
  static isSdkManagedHeader(name) {
821
905
  const normalized = name.toLowerCase();
822
906
  return normalized === "authorization" || normalized === "x-api-key";
@@ -836,6 +920,19 @@ var SPAPSClient = class _SPAPSClient {
836
920
  }
837
921
  headers[name] = value;
838
922
  }
923
+ static deleteHeader(headers, name) {
924
+ if (!headers) return;
925
+ if (typeof headers.delete === "function") {
926
+ headers.delete(name);
927
+ return;
928
+ }
929
+ const normalized = name.toLowerCase();
930
+ for (const key of Object.keys(headers)) {
931
+ if (key.toLowerCase() === normalized) {
932
+ delete headers[key];
933
+ }
934
+ }
935
+ }
839
936
  static assertSafeHeaderValue(name, value) {
840
937
  if (typeof value === "string" && /[\r\n]/.test(value)) {
841
938
  throw new Error(`Invalid header value for ${name}: control characters not allowed`);
@@ -1049,10 +1146,14 @@ var SPAPSClient = class _SPAPSClient {
1049
1146
  } else {
1050
1147
  formData.append("file", file);
1051
1148
  }
1149
+ const uploadHeaders = { "Content-Type": null };
1150
+ if (this.accessToken) {
1151
+ uploadHeaders.Authorization = `Bearer ${this.accessToken}`;
1152
+ }
1052
1153
  const res = await this.client.post(
1053
1154
  "/api/v1/issue-reports/attachments",
1054
1155
  formData,
1055
- this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
1156
+ { headers: uploadHeaders }
1056
1157
  );
1057
1158
  return this.unwrapApiResponse(res, "Failed to upload issue report attachment");
1058
1159
  },
@@ -1535,6 +1636,9 @@ var SPAPSClient = class _SPAPSClient {
1535
1636
  });
1536
1637
  this.client.interceptors.request.use((config2) => {
1537
1638
  config2.headers = config2.headers || {};
1639
+ if (typeof FormData !== "undefined" && config2.data instanceof FormData) {
1640
+ _SPAPSClient.deleteHeader(config2.headers, "Content-Type");
1641
+ }
1538
1642
  this.applyCustomHeaders(config2.headers);
1539
1643
  if (this.apiKey && !_SPAPSClient.hasHeader(config2.headers, "X-API-Key")) {
1540
1644
  _SPAPSClient.setHeader(config2.headers, "X-API-Key", this.apiKey);
@@ -1547,15 +1651,18 @@ var SPAPSClient = class _SPAPSClient {
1547
1651
  this.client.interceptors.response.use(
1548
1652
  (response) => response,
1549
1653
  async (error) => {
1550
- if (error.response?.status === 401 && this.refreshToken) {
1654
+ const cfg = error.config || {};
1655
+ const requestUrl = cfg.url || "";
1656
+ const shouldAttemptRefresh = error.response?.status === 401 && !!this.refreshToken && !cfg.__isRetry && !requestUrl.includes("/api/auth/refresh") && !!error.config;
1657
+ if (shouldAttemptRefresh) {
1551
1658
  try {
1552
1659
  const { data } = await this.refresh(this.refreshToken);
1553
1660
  this.accessToken = data.access_token;
1554
1661
  this.refreshToken = data.refresh_token;
1555
- if (error.config) {
1556
- error.config.headers.Authorization = `Bearer ${this.accessToken}`;
1557
- return this.client.request(error.config);
1558
- }
1662
+ cfg.__isRetry = true;
1663
+ cfg.headers = cfg.headers || {};
1664
+ _SPAPSClient.setHeader(cfg.headers, "Authorization", `Bearer ${this.accessToken}`);
1665
+ return this.client.request(cfg);
1559
1666
  } catch (refreshError) {
1560
1667
  this.accessToken = void 0;
1561
1668
  this.refreshToken = void 0;
@@ -1598,18 +1705,21 @@ var SPAPSClient = class _SPAPSClient {
1598
1705
  email,
1599
1706
  password
1600
1707
  });
1601
- this.accessToken = response.data.access_token;
1602
- this.refreshToken = response.data.refresh_token;
1603
- return response;
1708
+ const data = this.extractAuthResponse(response, "Login failed");
1709
+ this.accessToken = data.access_token;
1710
+ this.refreshToken = data.refresh_token;
1711
+ return { data };
1604
1712
  }
1605
1713
  async register(email, password) {
1606
1714
  const response = await this.client.post("/api/auth/register", {
1607
1715
  email,
1608
1716
  password
1609
1717
  });
1610
- this.accessToken = response.data.access_token;
1611
- this.refreshToken = response.data.refresh_token;
1612
- return response;
1718
+ const data = this.normalizeAuthPayload(
1719
+ this.extractAuthResponse(response, "Registration failed")
1720
+ );
1721
+ this.storeAuthTokens(data);
1722
+ return { data };
1613
1723
  }
1614
1724
  async walletSignIn(walletAddress, signature, message, chainType = "solana") {
1615
1725
  const response = await this.client.post("/api/auth/wallet-sign-in", {
@@ -1618,17 +1728,19 @@ var SPAPSClient = class _SPAPSClient {
1618
1728
  message,
1619
1729
  chain_type: chainType
1620
1730
  });
1621
- this.accessToken = response.data.access_token;
1622
- this.refreshToken = response.data.refresh_token;
1623
- return response;
1731
+ const data = this.extractAuthResponse(response, "Wallet sign-in failed");
1732
+ this.accessToken = data.access_token;
1733
+ this.refreshToken = data.refresh_token;
1734
+ return { data };
1624
1735
  }
1625
1736
  async refresh(refreshToken) {
1626
1737
  const response = await this.client.post("/api/auth/refresh", {
1627
1738
  refresh_token: refreshToken || this.refreshToken
1628
1739
  });
1629
- this.accessToken = response.data.access_token;
1630
- this.refreshToken = response.data.refresh_token;
1631
- return response;
1740
+ const data = this.extractAuthResponse(response, "Token refresh failed");
1741
+ this.accessToken = data.access_token;
1742
+ this.refreshToken = data.refresh_token;
1743
+ return { data };
1632
1744
  }
1633
1745
  async logout() {
1634
1746
  await this.client.post("/api/auth/logout");
@@ -1636,7 +1748,9 @@ var SPAPSClient = class _SPAPSClient {
1636
1748
  this.refreshToken = void 0;
1637
1749
  }
1638
1750
  async getUser() {
1639
- return this.client.get("/api/auth/user");
1751
+ const res = await this.client.get("/api/auth/user");
1752
+ const payload = this.unwrapApiResponse(res, "Failed to get user");
1753
+ return { data: payload.user };
1640
1754
  }
1641
1755
  async getSessionContext() {
1642
1756
  const res = await this.client.get("/api/auth/session-context", this.authConfig());
@@ -1715,12 +1829,7 @@ var SPAPSClient = class _SPAPSClient {
1715
1829
  },
1716
1830
  register: async (payload) => {
1717
1831
  const res = await this.client.post("/api/auth/register", payload);
1718
- const body = res.data;
1719
- if (body?.success === false) throw new Error(body?.error?.message || "Register failed");
1720
- const data = body?.data ?? body;
1721
- this.accessToken = data.access_token;
1722
- this.refreshToken = data.refresh_token;
1723
- return data;
1832
+ return this.unwrapAuthMethodResponse(res, "Register failed");
1724
1833
  },
1725
1834
  /**
1726
1835
  * Verify a magic link token and authenticate the user.
@@ -2522,15 +2631,25 @@ var SPAPSClient = class _SPAPSClient {
2522
2631
  }
2523
2632
  async getSubscription(subscriptionId) {
2524
2633
  if (subscriptionId) {
2525
- return this.client.get(`/api/stripe/subscription/${subscriptionId}`);
2634
+ const res = await this.client.get(
2635
+ `/api/stripe/subscription/${subscriptionId}`
2636
+ );
2637
+ return { data: this.unwrapApiResponse(res, "Failed to get subscription") };
2526
2638
  }
2527
- return this.client.get("/api/stripe/subscriptions");
2639
+ const response = await this.client.get(
2640
+ "/api/stripe/subscriptions"
2641
+ );
2642
+ return { data: response.data?.subscriptions ?? [] };
2528
2643
  }
2529
2644
  async cancelSubscription(subscriptionId, options = {}) {
2530
2645
  if (!subscriptionId) {
2531
2646
  throw new Error("subscriptionId is required to cancel a subscription.");
2532
2647
  }
2533
- return this.client.post(`/api/stripe/subscription/${subscriptionId}/cancel`, options);
2648
+ const res = await this.client.post(
2649
+ `/api/stripe/subscription/${subscriptionId}/cancel`,
2650
+ options
2651
+ );
2652
+ return { data: this.unwrapApiResponse(res, "Failed to cancel subscription") };
2534
2653
  }
2535
2654
  // Usage Methods
2536
2655
  async authorizeUsage(payload) {
@@ -2573,7 +2692,8 @@ var SPAPSClient = class _SPAPSClient {
2573
2692
  template_key: options.templateKey,
2574
2693
  to: options.to,
2575
2694
  context: options.context,
2576
- ...options.userId && { user_id: options.userId }
2695
+ ...options.userId && { user_id: options.userId },
2696
+ ...options.idempotencyKey && { idempotency_key: options.idempotencyKey }
2577
2697
  });
2578
2698
  return this.unwrapApiResponse(response, "Failed to send email");
2579
2699
  }
package/dist/index.mjs CHANGED
@@ -296,6 +296,7 @@ var FeatureEvaluator = class {
296
296
 
297
297
  // src/websocket-auth-helper.ts
298
298
  var AUTH_CLOSE_CODES = /* @__PURE__ */ new Set([4001, 4003]);
299
+ var WS_BEARER_SUBPROTOCOL = "spaps.bearer.v1";
299
300
  var WebSocketAuthHelper = class {
300
301
  config;
301
302
  ws = null;
@@ -309,6 +310,8 @@ var WebSocketAuthHelper = class {
309
310
  url: config.url,
310
311
  getAccessToken: config.getAccessToken,
311
312
  refreshAccessToken: config.refreshAccessToken,
313
+ getConnectTicket: config.getConnectTicket,
314
+ connectTimeoutMs: config.connectTimeoutMs ?? 1e4,
312
315
  refreshBufferSeconds: config.refreshBufferSeconds ?? 30,
313
316
  maxAuthRetries: config.maxAuthRetries ?? 3,
314
317
  maxNetworkRetries: config.maxNetworkRetries ?? 5,
@@ -324,7 +327,11 @@ var WebSocketAuthHelper = class {
324
327
  // ── Public API ──────────────────────────────────────────────────────────
325
328
  /**
326
329
  * Open an authenticated WebSocket connection.
327
- * Appends the access token as a query parameter.
330
+ *
331
+ * The long-lived access token is never placed in the URL. Authentication uses
332
+ * a short-lived single-use connect ticket (`?ticket=...`) when
333
+ * `getConnectTicket` is configured, otherwise the bearer token is carried in
334
+ * the WebSocket subprotocol (handshake header).
328
335
  */
329
336
  async connect() {
330
337
  this.intentionalClose = false;
@@ -354,30 +361,84 @@ var WebSocketAuthHelper = class {
354
361
  this.ws.send(data);
355
362
  }
356
363
  // ── Internal helpers ────────────────────────────────────────────────────
357
- /** Build the authenticated WebSocket URL. */
358
- buildAuthUrl() {
364
+ /**
365
+ * Build the WebSocket URL.
366
+ *
367
+ * Never embeds the long-lived access token. When a short-lived connect
368
+ * `ticket` is supplied it is appended as `?ticket=<ticket>` (single-use,
369
+ * seconds-long TTL — inert if leaked). Otherwise the bare base URL is
370
+ * returned and the credential travels in the subprotocol instead.
371
+ */
372
+ buildAuthUrl(ticket) {
373
+ if (ticket) {
374
+ const separator = this.config.url.includes("?") ? "&" : "?";
375
+ return `${this.config.url}${separator}ticket=${encodeURIComponent(ticket)}`;
376
+ }
377
+ return this.config.url;
378
+ }
379
+ /**
380
+ * Build the WebSocket subprotocols carrying the bearer credential.
381
+ *
382
+ * Used only by the no-ticket fallback so the long-lived JWT travels in the
383
+ * `Sec-WebSocket-Protocol` handshake header rather than the URL query string.
384
+ * Throws when no access token is available.
385
+ */
386
+ buildAuthProtocols() {
359
387
  const token = this.config.getAccessToken();
360
388
  if (!token) {
361
389
  throw new Error("No access token available for WebSocket authentication");
362
390
  }
363
- const separator = this.config.url.includes("?") ? "&" : "?";
364
- return `${this.config.url}${separator}token=${encodeURIComponent(token)}`;
391
+ return [WS_BEARER_SUBPROTOCOL, token];
365
392
  }
366
393
  async openConnection() {
367
- const url = this.buildAuthUrl();
394
+ let url;
395
+ let protocols;
396
+ if (this.config.getConnectTicket) {
397
+ const ticket = await this.config.getConnectTicket();
398
+ if (!ticket) {
399
+ throw new Error("Failed to obtain WebSocket connect ticket");
400
+ }
401
+ url = this.buildAuthUrl(ticket);
402
+ protocols = void 0;
403
+ } else {
404
+ url = this.buildAuthUrl();
405
+ protocols = this.buildAuthProtocols();
406
+ }
368
407
  return new Promise((resolve, reject) => {
408
+ let settled = false;
409
+ let connectTimer = null;
410
+ const settle = (action) => {
411
+ if (settled) return;
412
+ settled = true;
413
+ if (connectTimer) {
414
+ clearTimeout(connectTimer);
415
+ connectTimer = null;
416
+ }
417
+ action();
418
+ };
369
419
  try {
370
- this.ws = new WebSocket(url);
420
+ this.ws = protocols ? new WebSocket(url, protocols) : new WebSocket(url);
371
421
  } catch (err) {
372
- reject(err);
422
+ settle(() => reject(err));
373
423
  return;
374
424
  }
425
+ if (this.config.connectTimeoutMs > 0) {
426
+ connectTimer = setTimeout(() => {
427
+ settle(() => {
428
+ try {
429
+ this.ws?.close();
430
+ } catch {
431
+ }
432
+ reject(new Error("WebSocket connection timed out before opening"));
433
+ });
434
+ }, this.config.connectTimeoutMs);
435
+ }
375
436
  this.ws.onopen = () => {
376
437
  this.networkRetries = 0;
377
438
  this.startPing();
378
439
  this.scheduleTokenRefresh();
379
440
  this.config.onOpen?.();
380
- resolve();
441
+ settle(resolve);
381
442
  };
382
443
  this.ws.onmessage = (event) => {
383
444
  this.config.onMessage?.(String(event.data));
@@ -388,6 +449,13 @@ var WebSocketAuthHelper = class {
388
449
  this.ws.onclose = (event) => {
389
450
  this.cleanup();
390
451
  this.config.onClose?.(event.code, event.reason);
452
+ settle(
453
+ () => reject(
454
+ new Error(
455
+ `WebSocket closed before opening (code ${event.code}${event.reason ? `: ${event.reason}` : ""})`
456
+ )
457
+ )
458
+ );
391
459
  if (this.intentionalClose) {
392
460
  return;
393
461
  }
@@ -778,6 +846,22 @@ var SPAPSClient = class _SPAPSClient {
778
846
  this.storeAuthTokens(result);
779
847
  return result;
780
848
  }
849
+ /**
850
+ * Unwrap the SPAPS `{ success, data }` response envelope for the legacy
851
+ * token-issuing auth methods (login/register/walletSignIn/refresh).
852
+ *
853
+ * The server's ResponseEnvelopeMiddleware wraps these responses, so the real
854
+ * tokens live at `response.data.data`. Reading `response.data.access_token`
855
+ * (one level too shallow) silently dropped the tokens. Reuse the shared
856
+ * `unwrapApiResponse` primitive so envelope handling and error fidelity stay
857
+ * consistent with the rest of the client.
858
+ */
859
+ extractAuthResponse(response, fallback) {
860
+ return this.unwrapApiResponse(
861
+ response,
862
+ fallback
863
+ );
864
+ }
781
865
  static isSdkManagedHeader(name) {
782
866
  const normalized = name.toLowerCase();
783
867
  return normalized === "authorization" || normalized === "x-api-key";
@@ -797,6 +881,19 @@ var SPAPSClient = class _SPAPSClient {
797
881
  }
798
882
  headers[name] = value;
799
883
  }
884
+ static deleteHeader(headers, name) {
885
+ if (!headers) return;
886
+ if (typeof headers.delete === "function") {
887
+ headers.delete(name);
888
+ return;
889
+ }
890
+ const normalized = name.toLowerCase();
891
+ for (const key of Object.keys(headers)) {
892
+ if (key.toLowerCase() === normalized) {
893
+ delete headers[key];
894
+ }
895
+ }
896
+ }
800
897
  static assertSafeHeaderValue(name, value) {
801
898
  if (typeof value === "string" && /[\r\n]/.test(value)) {
802
899
  throw new Error(`Invalid header value for ${name}: control characters not allowed`);
@@ -1010,10 +1107,14 @@ var SPAPSClient = class _SPAPSClient {
1010
1107
  } else {
1011
1108
  formData.append("file", file);
1012
1109
  }
1110
+ const uploadHeaders = { "Content-Type": null };
1111
+ if (this.accessToken) {
1112
+ uploadHeaders.Authorization = `Bearer ${this.accessToken}`;
1113
+ }
1013
1114
  const res = await this.client.post(
1014
1115
  "/api/v1/issue-reports/attachments",
1015
1116
  formData,
1016
- this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
1117
+ { headers: uploadHeaders }
1017
1118
  );
1018
1119
  return this.unwrapApiResponse(res, "Failed to upload issue report attachment");
1019
1120
  },
@@ -1496,6 +1597,9 @@ var SPAPSClient = class _SPAPSClient {
1496
1597
  });
1497
1598
  this.client.interceptors.request.use((config2) => {
1498
1599
  config2.headers = config2.headers || {};
1600
+ if (typeof FormData !== "undefined" && config2.data instanceof FormData) {
1601
+ _SPAPSClient.deleteHeader(config2.headers, "Content-Type");
1602
+ }
1499
1603
  this.applyCustomHeaders(config2.headers);
1500
1604
  if (this.apiKey && !_SPAPSClient.hasHeader(config2.headers, "X-API-Key")) {
1501
1605
  _SPAPSClient.setHeader(config2.headers, "X-API-Key", this.apiKey);
@@ -1508,15 +1612,18 @@ var SPAPSClient = class _SPAPSClient {
1508
1612
  this.client.interceptors.response.use(
1509
1613
  (response) => response,
1510
1614
  async (error) => {
1511
- if (error.response?.status === 401 && this.refreshToken) {
1615
+ const cfg = error.config || {};
1616
+ const requestUrl = cfg.url || "";
1617
+ const shouldAttemptRefresh = error.response?.status === 401 && !!this.refreshToken && !cfg.__isRetry && !requestUrl.includes("/api/auth/refresh") && !!error.config;
1618
+ if (shouldAttemptRefresh) {
1512
1619
  try {
1513
1620
  const { data } = await this.refresh(this.refreshToken);
1514
1621
  this.accessToken = data.access_token;
1515
1622
  this.refreshToken = data.refresh_token;
1516
- if (error.config) {
1517
- error.config.headers.Authorization = `Bearer ${this.accessToken}`;
1518
- return this.client.request(error.config);
1519
- }
1623
+ cfg.__isRetry = true;
1624
+ cfg.headers = cfg.headers || {};
1625
+ _SPAPSClient.setHeader(cfg.headers, "Authorization", `Bearer ${this.accessToken}`);
1626
+ return this.client.request(cfg);
1520
1627
  } catch (refreshError) {
1521
1628
  this.accessToken = void 0;
1522
1629
  this.refreshToken = void 0;
@@ -1559,18 +1666,21 @@ var SPAPSClient = class _SPAPSClient {
1559
1666
  email,
1560
1667
  password
1561
1668
  });
1562
- this.accessToken = response.data.access_token;
1563
- this.refreshToken = response.data.refresh_token;
1564
- return response;
1669
+ const data = this.extractAuthResponse(response, "Login failed");
1670
+ this.accessToken = data.access_token;
1671
+ this.refreshToken = data.refresh_token;
1672
+ return { data };
1565
1673
  }
1566
1674
  async register(email, password) {
1567
1675
  const response = await this.client.post("/api/auth/register", {
1568
1676
  email,
1569
1677
  password
1570
1678
  });
1571
- this.accessToken = response.data.access_token;
1572
- this.refreshToken = response.data.refresh_token;
1573
- return response;
1679
+ const data = this.normalizeAuthPayload(
1680
+ this.extractAuthResponse(response, "Registration failed")
1681
+ );
1682
+ this.storeAuthTokens(data);
1683
+ return { data };
1574
1684
  }
1575
1685
  async walletSignIn(walletAddress, signature, message, chainType = "solana") {
1576
1686
  const response = await this.client.post("/api/auth/wallet-sign-in", {
@@ -1579,17 +1689,19 @@ var SPAPSClient = class _SPAPSClient {
1579
1689
  message,
1580
1690
  chain_type: chainType
1581
1691
  });
1582
- this.accessToken = response.data.access_token;
1583
- this.refreshToken = response.data.refresh_token;
1584
- return response;
1692
+ const data = this.extractAuthResponse(response, "Wallet sign-in failed");
1693
+ this.accessToken = data.access_token;
1694
+ this.refreshToken = data.refresh_token;
1695
+ return { data };
1585
1696
  }
1586
1697
  async refresh(refreshToken) {
1587
1698
  const response = await this.client.post("/api/auth/refresh", {
1588
1699
  refresh_token: refreshToken || this.refreshToken
1589
1700
  });
1590
- this.accessToken = response.data.access_token;
1591
- this.refreshToken = response.data.refresh_token;
1592
- return response;
1701
+ const data = this.extractAuthResponse(response, "Token refresh failed");
1702
+ this.accessToken = data.access_token;
1703
+ this.refreshToken = data.refresh_token;
1704
+ return { data };
1593
1705
  }
1594
1706
  async logout() {
1595
1707
  await this.client.post("/api/auth/logout");
@@ -1597,7 +1709,9 @@ var SPAPSClient = class _SPAPSClient {
1597
1709
  this.refreshToken = void 0;
1598
1710
  }
1599
1711
  async getUser() {
1600
- return this.client.get("/api/auth/user");
1712
+ const res = await this.client.get("/api/auth/user");
1713
+ const payload = this.unwrapApiResponse(res, "Failed to get user");
1714
+ return { data: payload.user };
1601
1715
  }
1602
1716
  async getSessionContext() {
1603
1717
  const res = await this.client.get("/api/auth/session-context", this.authConfig());
@@ -1676,12 +1790,7 @@ var SPAPSClient = class _SPAPSClient {
1676
1790
  },
1677
1791
  register: async (payload) => {
1678
1792
  const res = await this.client.post("/api/auth/register", payload);
1679
- const body = res.data;
1680
- if (body?.success === false) throw new Error(body?.error?.message || "Register failed");
1681
- const data = body?.data ?? body;
1682
- this.accessToken = data.access_token;
1683
- this.refreshToken = data.refresh_token;
1684
- return data;
1793
+ return this.unwrapAuthMethodResponse(res, "Register failed");
1685
1794
  },
1686
1795
  /**
1687
1796
  * Verify a magic link token and authenticate the user.
@@ -2483,15 +2592,25 @@ var SPAPSClient = class _SPAPSClient {
2483
2592
  }
2484
2593
  async getSubscription(subscriptionId) {
2485
2594
  if (subscriptionId) {
2486
- return this.client.get(`/api/stripe/subscription/${subscriptionId}`);
2595
+ const res = await this.client.get(
2596
+ `/api/stripe/subscription/${subscriptionId}`
2597
+ );
2598
+ return { data: this.unwrapApiResponse(res, "Failed to get subscription") };
2487
2599
  }
2488
- return this.client.get("/api/stripe/subscriptions");
2600
+ const response = await this.client.get(
2601
+ "/api/stripe/subscriptions"
2602
+ );
2603
+ return { data: response.data?.subscriptions ?? [] };
2489
2604
  }
2490
2605
  async cancelSubscription(subscriptionId, options = {}) {
2491
2606
  if (!subscriptionId) {
2492
2607
  throw new Error("subscriptionId is required to cancel a subscription.");
2493
2608
  }
2494
- return this.client.post(`/api/stripe/subscription/${subscriptionId}/cancel`, options);
2609
+ const res = await this.client.post(
2610
+ `/api/stripe/subscription/${subscriptionId}/cancel`,
2611
+ options
2612
+ );
2613
+ return { data: this.unwrapApiResponse(res, "Failed to cancel subscription") };
2495
2614
  }
2496
2615
  // Usage Methods
2497
2616
  async authorizeUsage(payload) {
@@ -2534,7 +2653,8 @@ var SPAPSClient = class _SPAPSClient {
2534
2653
  template_key: options.templateKey,
2535
2654
  to: options.to,
2536
2655
  context: options.context,
2537
- ...options.userId && { user_id: options.userId }
2656
+ ...options.userId && { user_id: options.userId },
2657
+ ...options.idempotencyKey && { idempotency_key: options.idempotencyKey }
2538
2658
  });
2539
2659
  return this.unwrapApiResponse(response, "Failed to send email");
2540
2660
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaps-sdk",
3
- "version": "1.13.0",
3
+ "version": "1.13.2",
4
4
  "description": "Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking, role-based access control, and dayrate scheduling",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,7 +51,7 @@
51
51
  "dependencies": {
52
52
  "axios": "^1.15.1",
53
53
  "cross-fetch": "^4.0.0",
54
- "spaps-types": "^1.5.0"
54
+ "spaps-types": "^1.5.2"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^20.10.0",
@@ -81,4 +81,4 @@
81
81
  "engines": {
82
82
  "node": ">=14.0.0"
83
83
  }
84
- }
84
+ }