@oxy.so/core 1.13.0 → 1.18.0

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.
Files changed (117) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/crypto/identityLink.js +18 -0
  3. package/dist/cjs/i18n/locales/ar-SA.json +105 -85
  4. package/dist/cjs/i18n/locales/ca-ES.json +107 -87
  5. package/dist/cjs/i18n/locales/de-DE.json +105 -85
  6. package/dist/cjs/i18n/locales/en-US.json +105 -164
  7. package/dist/cjs/i18n/locales/es-ES.json +104 -163
  8. package/dist/cjs/i18n/locales/fr-FR.json +105 -85
  9. package/dist/cjs/i18n/locales/it-IT.json +105 -85
  10. package/dist/cjs/i18n/locales/ja-JP.json +105 -85
  11. package/dist/cjs/i18n/locales/ko-KR.json +105 -85
  12. package/dist/cjs/i18n/locales/locales/ar-SA.json +105 -85
  13. package/dist/cjs/i18n/locales/locales/ca-ES.json +107 -87
  14. package/dist/cjs/i18n/locales/locales/de-DE.json +105 -85
  15. package/dist/cjs/i18n/locales/locales/en-US.json +105 -164
  16. package/dist/cjs/i18n/locales/locales/es-ES.json +104 -163
  17. package/dist/cjs/i18n/locales/locales/fr-FR.json +105 -85
  18. package/dist/cjs/i18n/locales/locales/it-IT.json +105 -85
  19. package/dist/cjs/i18n/locales/locales/ja-JP.json +105 -85
  20. package/dist/cjs/i18n/locales/locales/ko-KR.json +105 -85
  21. package/dist/cjs/i18n/locales/locales/pt-PT.json +105 -85
  22. package/dist/cjs/i18n/locales/locales/zh-CN.json +106 -86
  23. package/dist/cjs/i18n/locales/pt-PT.json +105 -85
  24. package/dist/cjs/i18n/locales/zh-CN.json +106 -86
  25. package/dist/cjs/index.js +10 -43
  26. package/dist/cjs/mixins/OxyServices.auth.js +54 -16
  27. package/dist/cjs/mixins/OxyServices.identity.js +128 -8
  28. package/dist/cjs/mixins/OxyServices.user.js +42 -7
  29. package/dist/cjs/server/securityHeaders.js +19 -1
  30. package/dist/cjs/session/accountDialogController.js +37 -142
  31. package/dist/cjs/utils/authWebUrl.js +6 -6
  32. package/dist/cjs/utils/oauthPkce.js +5 -1
  33. package/dist/esm/.tsbuildinfo +1 -1
  34. package/dist/esm/crypto/identityLink.js +15 -0
  35. package/dist/esm/i18n/locales/ar-SA.json +105 -85
  36. package/dist/esm/i18n/locales/ca-ES.json +107 -87
  37. package/dist/esm/i18n/locales/de-DE.json +105 -85
  38. package/dist/esm/i18n/locales/en-US.json +105 -164
  39. package/dist/esm/i18n/locales/es-ES.json +104 -163
  40. package/dist/esm/i18n/locales/fr-FR.json +105 -85
  41. package/dist/esm/i18n/locales/it-IT.json +105 -85
  42. package/dist/esm/i18n/locales/ja-JP.json +105 -85
  43. package/dist/esm/i18n/locales/ko-KR.json +105 -85
  44. package/dist/esm/i18n/locales/locales/ar-SA.json +105 -85
  45. package/dist/esm/i18n/locales/locales/ca-ES.json +107 -87
  46. package/dist/esm/i18n/locales/locales/de-DE.json +105 -85
  47. package/dist/esm/i18n/locales/locales/en-US.json +105 -164
  48. package/dist/esm/i18n/locales/locales/es-ES.json +104 -163
  49. package/dist/esm/i18n/locales/locales/fr-FR.json +105 -85
  50. package/dist/esm/i18n/locales/locales/it-IT.json +105 -85
  51. package/dist/esm/i18n/locales/locales/ja-JP.json +105 -85
  52. package/dist/esm/i18n/locales/locales/ko-KR.json +105 -85
  53. package/dist/esm/i18n/locales/locales/pt-PT.json +105 -85
  54. package/dist/esm/i18n/locales/locales/zh-CN.json +106 -86
  55. package/dist/esm/i18n/locales/pt-PT.json +105 -85
  56. package/dist/esm/i18n/locales/zh-CN.json +106 -86
  57. package/dist/esm/index.js +5 -5
  58. package/dist/esm/mixins/OxyServices.auth.js +55 -17
  59. package/dist/esm/mixins/OxyServices.identity.js +128 -8
  60. package/dist/esm/mixins/OxyServices.user.js +42 -7
  61. package/dist/esm/server/securityHeaders.js +19 -1
  62. package/dist/esm/session/accountDialogController.js +37 -142
  63. package/dist/esm/utils/authWebUrl.js +5 -5
  64. package/dist/esm/utils/oauthPkce.js +5 -1
  65. package/dist/types/.tsbuildinfo +1 -1
  66. package/dist/types/crypto/identityLink.d.ts +8 -0
  67. package/dist/types/index.d.ts +5 -8
  68. package/dist/types/mixins/OxyServices.auth.d.ts +35 -25
  69. package/dist/types/mixins/OxyServices.identity.d.ts +34 -34
  70. package/dist/types/mixins/OxyServices.user.d.ts +29 -6
  71. package/dist/types/models/interfaces.d.ts +46 -13
  72. package/dist/types/server/securityHeaders.d.ts +9 -12
  73. package/dist/types/session/accountDialogController.d.ts +29 -98
  74. package/dist/types/utils/authWebUrl.d.ts +5 -5
  75. package/dist/types/utils/oauthPkce.d.ts +10 -0
  76. package/package.json +3 -3
  77. package/src/crypto/__tests__/identityLink.test.ts +18 -0
  78. package/src/crypto/__tests__/identityProof.test.ts +6 -6
  79. package/src/crypto/identityLink.ts +18 -0
  80. package/src/i18n/locales/ar-SA.json +105 -85
  81. package/src/i18n/locales/ca-ES.json +107 -87
  82. package/src/i18n/locales/de-DE.json +105 -85
  83. package/src/i18n/locales/en-US.json +105 -164
  84. package/src/i18n/locales/es-ES.json +104 -163
  85. package/src/i18n/locales/fr-FR.json +105 -85
  86. package/src/i18n/locales/it-IT.json +105 -85
  87. package/src/i18n/locales/ja-JP.json +105 -85
  88. package/src/i18n/locales/ko-KR.json +105 -85
  89. package/src/i18n/locales/pt-PT.json +105 -85
  90. package/src/i18n/locales/zh-CN.json +106 -86
  91. package/src/index.ts +8 -51
  92. package/src/mixins/OxyServices.auth.ts +72 -24
  93. package/src/mixins/OxyServices.identity.ts +126 -8
  94. package/src/mixins/OxyServices.user.ts +51 -10
  95. package/src/mixins/__tests__/identityLink.test.ts +89 -0
  96. package/src/mixins/__tests__/notifications.read.test.ts +82 -0
  97. package/src/mixins/__tests__/preSessionSkipAuth.test.ts +1 -1
  98. package/src/mixins/__tests__/webauthnAuth.test.ts +75 -1
  99. package/src/models/interfaces.ts +46 -12
  100. package/src/server/__tests__/securityHeaders.test.ts +17 -0
  101. package/src/server/securityHeaders.ts +29 -1
  102. package/src/session/__tests__/accountDialogController.test.ts +42 -184
  103. package/src/session/accountDialogController.ts +50 -185
  104. package/src/utils/__tests__/oauthPkce.test.ts +7 -0
  105. package/src/utils/authWebUrl.ts +5 -5
  106. package/src/utils/oauthPkce.ts +16 -1
  107. package/dist/cjs/crypto/identityMove.js +0 -142
  108. package/dist/cjs/crypto/webIdentityCarrier.js +0 -325
  109. package/dist/esm/crypto/identityMove.js +0 -128
  110. package/dist/esm/crypto/webIdentityCarrier.js +0 -303
  111. package/dist/types/crypto/identityMove.d.ts +0 -75
  112. package/dist/types/crypto/webIdentityCarrier.d.ts +0 -150
  113. package/src/crypto/__tests__/identityMove.test.ts +0 -146
  114. package/src/crypto/__tests__/webIdentityCarrier.test.ts +0 -169
  115. package/src/crypto/__tests__/webIdentityRecoveryMaterial.test.ts +0 -117
  116. package/src/crypto/identityMove.ts +0 -167
  117. package/src/crypto/webIdentityCarrier.ts +0 -394
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The code both screens show while Commons is being linked to a passkey
3
+ * account (ADR 0029 D3): 6 digits derived from the link request and the key
4
+ * Commons signed with. auth.oxy.so derives it from the key the API relayed,
5
+ * Commons from its own; the person checks they match before the passkey is
6
+ * asserted, so a photographed QR cannot slip another key in.
7
+ */
8
+ export declare function deriveIdentityLinkCode(linkId: string, publicKey: string): string;
@@ -72,14 +72,11 @@ export { hkdfSha256 } from './crypto/kdf';
72
72
  export { encryptAead, decryptAead, AEAD_KEY_LENGTH, AEAD_NONCE_LENGTH, } from './crypto/aead';
73
73
  export type { AeadResult } from './crypto/aead';
74
74
  export { deriveSharedSecret } from './crypto/ecdh';
75
- export { WEB_IDENTITY_PRF_INPUT, WebIdentityUnlockError, WEB_IDENTITY_PRF_OUTPUT_LENGTH, addWrap, deriveIdentityFromMnemonic, deriveIdentityFromPrivateKey, deriveIdentityFromRecoveryMaterial, deriveKeyEncryptionKey, generateDataKey, generateWebIdentity, isUsablePrfOutput, markWrapVerified, normalizeMnemonic, openWebIdentity, parseRecoveryMaterial, removeWrap, sealWebIdentity, unlockWebIdentity, unwrapDataKey, wipeBytes, wipeOpenedIdentity, } from './crypto/webIdentityCarrier';
76
- export type { OpenedMnemonicIdentity, OpenedRawKeyIdentity, OpenedWebIdentity, WebIdentityRecoveryMaterial, WebIdentityUnlockFailure, WrapInput, } from './crypto/webIdentityCarrier';
77
75
  export { digestIdentityPayload, signIdentityProof } from './crypto/identityProof';
78
- export { buildMoveQrPayload, createMoveCommitment, deriveMoveKey, deriveMoveSas, digestMoveCiphertext, generateMoveEphemeralKeyPair, openMovedIdentity, parseMoveQrPayload, sealIdentityForMove, signMoveReceipt, verifyMoveCommitment, verifyMoveReceipt, } from './crypto/identityMove';
79
- export type { MoveReceiptClaims } from './crypto/identityMove';
76
+ export { deriveIdentityLinkCode } from './crypto/identityLink';
80
77
  export { DeviceManager } from './utils/deviceManager';
81
78
  export type { DeviceFingerprint, StoredDeviceInfo } from './utils/deviceManager';
82
- export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, BatchFileAccessEntry, BatchFileAccessResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress, DeviceLinkedSession, DeviceLinkedSessionsResponse, DeviceLinkedSessionLogoutResponse, UpdateDeviceNameResponse, } from './models/interfaces';
79
+ export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, NotificationActor, NotificationPage, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, BatchFileAccessEntry, BatchFileAccessResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress, DeviceLinkedSession, DeviceLinkedSessionsResponse, DeviceLinkedSessionLogoutResponse, UpdateDeviceNameResponse, } from './models/interfaces';
83
80
  export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
84
81
  export { TopicType, TopicSource } from './models/Topic';
85
82
  export type { TopicData, TopicTranslation, TopicListResult } from './models/Topic';
@@ -111,12 +108,12 @@ export { updateAvatarVisibility } from './utils/avatarUtils';
111
108
  export { buildAccountsArray, createQuickAccount, getAccountDisplayName, getAccountFallbackHandle, formatPublicKeyHandle, getAccountColor, } from './utils/accountUtils';
112
109
  export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
113
110
  export { registrableApex } from './utils/registrableApex';
114
- export { CENTRAL_IDP_APEX, IDENTITY_WEB_ORIGIN } from './utils/authWebUrl';
111
+ export { AUTH_WEB_ORIGIN, CENTRAL_IDP_APEX } from './utils/authWebUrl';
115
112
  export { isOxyRpOrigin } from './utils/webauthnOrigin';
116
113
  export { runColdBoot } from './utils/coldBoot';
117
114
  export type { ColdBootStep, ColdBootStepResult, ColdBootSession, ColdBootSkip, ColdBootOutcome, RunColdBootOptions, } from './utils/coldBoot';
118
115
  export { buildOAuthAuthorizeUrl, computeCodeChallenge, generateOAuthState, generatePkcePair, DEFAULT_OAUTH_SCOPE, OXY_AUTHORIZE_URL, OXY_OAUTH_STATE_STORAGE_KEY, OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY, OXY_OAUTH_REDIRECT_URI_STORAGE_KEY, OXY_OAUTH_RETURN_PATH_STORAGE_KEY, normalizeOAuthRedirectUri, canonicalizeOAuthRedirectUri, persistOAuthHandshake, readOAuthHandshake, clearOAuthHandshake, persistOAuthReturnPath, consumeOAuthReturnPath, } from './utils/oauthPkce';
119
- export type { PkcePair, BuildOAuthAuthorizeUrlParams } from './utils/oauthPkce';
116
+ export type { PkcePair, BuildOAuthAuthorizeUrlParams, OxyAuthScreen } from './utils/oauthPkce';
120
117
  export { isLoopbackOrigin, isOfficialWebOrigin, isAllowedDeviceJoinOrigin, } from './utils/officialOrigins';
121
118
  export { SessionClient } from './session/SessionClient';
122
119
  export type { TokenTransport, SessionClientHost, SessionClientOptions, DeviceCredential, SessionStateOrigin } from './session/SessionClient';
@@ -132,7 +129,7 @@ export { isSwitchTargetAccount, canSwitchIntoAccount, } from './session/accountS
132
129
  export { resolveAccountDelegationAccess } from './session/accountDelegationAccess';
133
130
  export type { AccountDelegationAccess, AccountDelegationNode, } from './session/accountDelegationAccess';
134
131
  export { AccountDialogController, createAccountDialogController, } from './session/accountDialogController';
135
- export type { AccountDialogControllerOptions, AccountDialogSnapshot, AccountDialogView, CommonsAvailability, ContextChoiceOutcome, PopupWindowHandle, SignInFailureReason, SignInFlowPhase, SignInFlowState, SignInProgress, } from './session/accountDialogController';
132
+ export type { AccountDialogControllerOptions, AccountDialogSnapshot, AccountDialogView, CommonsAvailability, ContextChoiceOutcome, SignInFailureReason, SignInFlowPhase, SignInFlowState, SignInProgress, } from './session/accountDialogController';
136
133
  export { createWebAuthStateStore, createNativeAuthStateStore, createMemoryAuthStateStore, AUTH_STATE_STORAGE_KEY, } from './session/authStateStore';
137
134
  export type { PersistedAuthState, AuthStateStore, NativeKeyValueStorage, } from './session/authStateStore';
138
135
  export { createSharedMirroringAuthStateStore, decideSharedDeviceJoin, decideSharedDevicePublish, normalizeSharedDeviceSessionRead, publishProvenDeviceCredential, readLocalDeviceCredential, } from './session/sharedDeviceCredential';
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { User } from '../models/interfaces';
7
7
  import type { LoginResult, CommonsDenyReason } from '@oxy.so/contracts';
8
- import { type IdentityProof, type WebIdentityEnvelope } from '@oxy.so/contracts';
8
+ import { type EmailVerificationConfirmResponse, type EmailVerificationStartRequest, type EmailVerificationStartResponse } from '@oxy.so/contracts';
9
9
  import type { SessionLoginResponse } from '../models/session';
10
10
  import type { OxyServicesBase } from '../OxyServices.base';
11
11
  import type { PublicApplication } from './OxyServices.connectedApps';
@@ -790,50 +790,60 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
790
790
  message: string;
791
791
  }>;
792
792
  /**
793
- * Check email availability
793
+ * Send a 6-digit code to an email (ADR 0029 D3): the recovery email of a
794
+ * new passkey account (`purpose: 'signup'`), or — named by username or
795
+ * email — the recovery email of an account being recovered
796
+ * (`purpose: 'recovery'`). The answer is the same whether or not an
797
+ * account exists; nothing is revealed about who has one.
794
798
  */
795
- checkEmailAvailability(email: string): Promise<{
796
- available: boolean;
797
- message: string;
798
- }>;
799
+ startEmailVerification(request: EmailVerificationStartRequest): Promise<EmailVerificationStartResponse>;
800
+ /**
801
+ * Confirm the code {@link startEmailVerification} sent. Resolves to a
802
+ * short-lived one-use ticket: a sign-up passes it (with the email) to
803
+ * {@link webauthnRegisterVerify}; a recovery passes it as `recoveryTicket`
804
+ * to {@link webauthnRegisterOptions} and {@link webauthnRegisterVerify}.
805
+ */
806
+ confirmEmailVerification(verificationId: string, code: string): Promise<EmailVerificationConfirmResponse>;
799
807
  /**
800
808
  * Begin a WebAuthn / passkey REGISTRATION ceremony. Requests the
801
809
  * `PublicKeyCredentialCreationOptions` the browser's `navigator.credentials
802
810
  * .create()` (or `@simplewebauthn/browser`'s `startRegistration`) needs.
803
811
  *
804
- * With a bearer token planted this links a passkey to the signed-in account
805
- * (`username` ignored); without one it is a prospective signup and `username`
806
- * is the desired handle. The returned options are OPAQUE — Oxy does not own
807
- * their shape (the browser / `@simplewebauthn` does), so they pass through
808
- * as `unknown` for the caller to hand straight to the ceremony.
812
+ * With a bearer token planted this adds a passkey to the signed-in account.
813
+ * Without one, `username` is a prospective sign-up's handle, and
814
+ * `recoveryTicket` a recovery's new passkey for the account it names. The
815
+ * returned options are OPAQUE — Oxy does not own their shape (the browser /
816
+ * `@simplewebauthn` does), so they pass through as `unknown` for the caller
817
+ * to hand straight to the ceremony.
809
818
  */
810
- webauthnRegisterOptions(username?: string): Promise<unknown>;
819
+ webauthnRegisterOptions(request?: {
820
+ username?: string;
821
+ recoveryTicket?: string;
822
+ }): Promise<unknown>;
811
823
  /**
812
824
  * Finish a WebAuthn / passkey REGISTRATION ceremony. Forwards the opaque
813
825
  * browser `RegistrationResponseJSON` (`response`) alongside the Oxy envelope
814
826
  * (desired `username` for signup + the device-session naming fields).
815
827
  *
816
- * Two server branches, disambiguated by the response shape:
817
- * - **Signup** (no bearer): the account is created and a session minted —
818
- * the response carries `sessionId`, is the SAME {@link LoginResult}
819
- * contract as `POST /auth/verify`, and its access token is planted here.
828
+ * Server branches, disambiguated by the response shape:
829
+ * - **Sign-up** (no bearer; `username`, `email`, `emailTicket`) and
830
+ * **recovery** (no bearer; `recoveryTicket`): the account is created, or
831
+ * gains the passkey, and a session is minted — the response carries
832
+ * `sessionId`, is the SAME {@link LoginResult} contract as
833
+ * `POST /auth/verify`, and its access token is planted here.
820
834
  * - **Link** (bearer present): the passkey is attached to the signed-in
821
835
  * account and the server returns `{ success, message }` with no session,
822
836
  * which is returned verbatim (no token planting).
823
837
  */
824
838
  webauthnRegisterVerify(response: unknown, envelope?: {
825
839
  username?: string;
840
+ /** Sign-up: the recovery email, confirmed with `emailTicket`. */
841
+ email?: string;
842
+ emailTicket?: string;
843
+ /** Recovery: the ticket {@link confirmEmailVerification} returned. */
844
+ recoveryTicket?: string;
826
845
  deviceName?: string;
827
846
  deviceFingerprint?: string;
828
- /**
829
- * Sign-up only: the root created on the holder before this call, sealed
830
- * under the passkey being registered, plus its `enroll_identity` proof
831
- * (ADR 0024 D4). The account is created WITH it or not at all.
832
- */
833
- identity?: {
834
- envelope: WebIdentityEnvelope;
835
- proof: IdentityProof;
836
- };
837
847
  }): Promise<{
838
848
  success: true;
839
849
  message: string;
@@ -1,29 +1,4 @@
1
- /**
2
- * Identity Methods Mixin (self-sovereign identity layer)
3
- *
4
- * Provides typed access to Oxy's AtProto/Bluesky-flavoured identity &
5
- * portability layer:
6
- * - DID resolution (`did:web:oxy.so:u:<userId>`, derived on demand by the API).
7
- * - The auth-method ↔ DID verification-method mapping (`GET /auth/methods`) and
8
- * passkey removal. A root is linked by the holder flows (ADR 0024) and never
9
- * unlinked; it is replaced by rotation.
10
- * - Signed records: clients sign an envelope with their own cryptographic key
11
- * (`SignatureService.signRecord` + the shared `canonicalize`) and publish it;
12
- * anyone can fetch and verify it.
13
- * - The signed data-export ("credible exit") bundle.
14
- * - Verified-domain badges (prove ownership of `nate.com`).
15
- *
16
- * Wire shapes come from `@oxy.so/contracts` (`DidDocument`,
17
- * `SignedRecordEnvelope`, `AuthMethodsResponse`, `VerifiedDomain`,
18
- * `DomainVerificationInstructions`, `ExportBundle`) — the single source of truth
19
- * the API validates its output against, so producer and consumer cannot drift.
20
- *
21
- * Identity signing is NATIVE-ONLY: the private key lives in native secure
22
- * storage, so `signRecord` and `publishRecord` require an
23
- * on-device identity and throw on web (where `KeyManager.getPublicKey()` is
24
- * always `null`).
25
- */
26
- import type { AuthMethodsResponse, IdentityRootStatus, DidDocument, DomainVerificationInstructions, ExportBundle, OxySignedRecordType, SignedRecordEnvelope, VerifiedDomain } from '@oxy.so/contracts';
1
+ import type { AuthMethodsResponse, IdentityLinkCreateResponse, IdentityLinkState, IdentityRootStatus, DidDocument, DomainVerificationInstructions, ExportBundle, OxySignedRecordType, SignedRecordEnvelope, VerifiedDomain } from '@oxy.so/contracts';
27
2
  import type { OxyServicesBase } from '../OxyServices.base';
28
3
  import { type PendingIdentityResult } from '../crypto/recoveryPhrase';
29
4
  /**
@@ -140,22 +115,47 @@ export declare function OxyServicesIdentityMixin<T extends typeof OxyServicesBas
140
115
  */
141
116
  listAuthMethods(): Promise<AuthMethodsResponse>;
142
117
  /**
143
- * The signed-in account's root readiness (ADR 0024 D5): whether a root is
144
- * linked, how many passkeys can open the web holder, whether the root has a
145
- * phrase and whether it is saved and was shown to recover it. Metadata only —
146
- * nothing here can open the root — so any first-party surface may show a
147
- * "save your recovery phrase" reminder from it.
118
+ * How the signed-in account is kept (ADR 0029 D3): whether Commons' root is
119
+ * linked (self-custody), or the recovery email of a passkey account. Any
120
+ * first-party surface may read it to recommend linking Commons.
148
121
  */
149
122
  getIdentityRootStatus(): Promise<IdentityRootStatus>;
123
+ /**
124
+ * Open a request to link Commons to the signed-in passkey account (ADR 0029
125
+ * D3). auth.oxy.so shows its `qrPayload`; Commons scans it and signs with
126
+ * {@link signIdentityLink}.
127
+ */
128
+ createIdentityLink(): Promise<IdentityLinkCreateResponse>;
129
+ /** Where a link request stands. Both devices poll it; it needs no session. */
130
+ getIdentityLink(linkId: string): Promise<IdentityLinkState>;
131
+ /**
132
+ * Commons' half: sign the scanned request's `link_identity` proof with THIS
133
+ * device's identity key and post it. NATIVE-ONLY (the key lives in native
134
+ * secure storage). Resolves to the key and the 6-digit code auth.oxy.so will
135
+ * show for it, for the person to compare.
136
+ */
137
+ signIdentityLink(linkId: string, challenge: string): Promise<{
138
+ publicKey: string;
139
+ code: string;
140
+ username: string | null;
141
+ }>;
142
+ /** WebAuthn request options over the account's passkeys, whose challenge is the link's. Opaque. */
143
+ getIdentityLinkAssertionOptions(linkId: string, challenge: string): Promise<unknown>;
144
+ /**
145
+ * auth.oxy.so's half: the passkey assertion over the link's challenge. The
146
+ * account gains Commons' root and loses its recovery email.
147
+ */
148
+ completeIdentityLink(linkId: string, assertion: unknown): Promise<{
149
+ success: true;
150
+ }>;
151
+ /** Withdraw a link request that has not completed. */
152
+ cancelIdentityLink(linkId: string): Promise<void>;
150
153
  /**
151
154
  * Remove ONE passkey (WebAuthn credential) from the current account.
152
155
  *
153
156
  * Passkeys are per-credential, so this targets a specific credential id.
154
157
  * The server refuses to remove the last remaining auth method (the account
155
158
  * would become inaccessible) and deletes the stored `WebauthnCredential`.
156
- * A passkey that also opens the account's root on the web takes its wrap with
157
- * it, and the ONLY such passkey is refused with
158
- * `code: 'IDENTITY_LAST_WEB_HOLDER'` (ADR 0024 D6).
159
159
  *
160
160
  * @param credentialId - The passkey's public credential id
161
161
  * (`AuthMethodEntry.credentialId`).
@@ -2,7 +2,7 @@
2
2
  * User Management Methods Mixin
3
3
  */
4
4
  import type { CreateOxyNotificationRequest } from '@oxy.so/contracts';
5
- import type { User, Notification, NotificationPreferences, UserPreferences, SearchProfilesResponse, PrivacySettings } from '../models/interfaces';
5
+ import type { User, Notification, NotificationPage, NotificationPreferences, UserPreferences, SearchProfilesResponse, PrivacySettings } from '../models/interfaces';
6
6
  import type { UserNameResponse, UserProfileUpdate, RecommendationRequest, RecommendationItem, ThemePreference } from '@oxy.so/contracts';
7
7
  import type { OxyServicesBase } from '../OxyServices.base';
8
8
  import { type PaginationParams, type FollowGraphParams } from '../utils/apiUtils';
@@ -350,6 +350,23 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
350
350
  deleteAccount(confirmText: string): Promise<{
351
351
  message: string;
352
352
  }>;
353
+ /**
354
+ * WebAuthn request options to delete a PASSKEY account (ADR 0029 D3): its own
355
+ * passkeys, user verification required, and a challenge bound to it. Opaque —
356
+ * hand them to the browser's authentication ceremony, on auth.oxy.so. An
357
+ * account with a Commons key deletes with {@link deleteAccount} instead.
358
+ */
359
+ getAccountDeletionOptions(): Promise<unknown>;
360
+ /**
361
+ * Delete a PASSKEY account permanently, with an assertion by one of its
362
+ * passkeys over {@link getAccountDeletionOptions}' challenge.
363
+ *
364
+ * @param confirmText - Must equal the user's username (verified server-side)
365
+ * @param assertion - The ceremony's opaque `AuthenticationResponseJSON`
366
+ */
367
+ deleteAccountWithPasskey(confirmText: string, assertion: unknown): Promise<{
368
+ message: string;
369
+ }>;
353
370
  /**
354
371
  * Invalidate every cached read a follow/unfollow write invalidates.
355
372
  *
@@ -497,9 +514,13 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
497
514
  */
498
515
  getViewerGraph(): Promise<ViewerGraph>;
499
516
  /**
500
- * Get notifications
517
+ * One page of the signed-in user's Oxy notifications, newest first
518
+ * (`GET /notifications`), with the unread count across all pages.
501
519
  */
502
- getNotifications(): Promise<Notification[]>;
520
+ getNotifications(params?: {
521
+ page?: number;
522
+ limit?: number;
523
+ }): Promise<NotificationPage>;
503
524
  /**
504
525
  * Get unread notification count
505
526
  */
@@ -511,11 +532,13 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
511
532
  * from an Oxy service about the recipient's own account, with the recipient
512
533
  * as actor and their profile as the entity.
513
534
  */
514
- createNotification(data: CreateOxyNotificationRequest | Partial<Notification>): Promise<Notification>;
535
+ createNotification(data: CreateOxyNotificationRequest): Promise<Notification>;
515
536
  /**
516
- * Mark notification as read
537
+ * Mark one of the signed-in user's notifications read and return it as
538
+ * stored. Scoped to the recipient server-side, so it is also the
539
+ * authoritative read of a notification by id: a foreign or unknown id 404s.
517
540
  */
518
- markNotificationAsRead(notificationId: string): Promise<void>;
541
+ markNotificationAsRead(notificationId: string): Promise<Notification>;
519
542
  /**
520
543
  * Mark all notifications as read
521
544
  */
@@ -1,4 +1,4 @@
1
- import type { AccountKind, AccountCategoryId, UserNameResponse, UserRelationship, ThemePreference } from '@oxy.so/contracts';
1
+ import type { AccountKind, AccountCategoryId, UserNameResponse, UserRelationship, ThemePreference, OxyNotificationEntityType, OxyNotificationType } from '@oxy.so/contracts';
2
2
  export interface OxyConfig {
3
3
  baseURL: string;
4
4
  cloudURL?: string;
@@ -241,25 +241,58 @@ export interface LoginResponse {
241
241
  user: User;
242
242
  message?: string;
243
243
  }
244
+ /**
245
+ * The actor of a notification as `GET /notifications` populates it — the raw
246
+ * stored name, not a composed display name.
247
+ */
248
+ export interface NotificationActor {
249
+ _id: string;
250
+ username?: string;
251
+ name?: {
252
+ first: string;
253
+ last: string;
254
+ };
255
+ avatar?: string;
256
+ }
257
+ /**
258
+ * One Oxy notification exactly as the API serializes it
259
+ * (`packages/api/src/controllers/notification.controller.ts`). `_id` — not
260
+ * `id` — is the key every notification endpoint addresses it by.
261
+ */
244
262
  export interface Notification {
245
- id: string;
246
- message: string;
263
+ _id: string;
264
+ recipientId: string;
265
+ /** Populated on a list read; the bare id on a create or a mark-read. */
266
+ actorId: string | NotificationActor;
247
267
  /** One of `OXY_NOTIFICATION_TYPES` (`@oxy.so/contracts`). */
248
- type?: string;
268
+ type: OxyNotificationType;
269
+ /**
270
+ * What `entityId` names: `post` | `reply` | `profile`, or `app` — an opaque id
271
+ * in the notifying application's namespace (`system` notifications only).
272
+ */
273
+ entityType: OxyNotificationEntityType;
274
+ entityId: string;
249
275
  /**
250
- * `system` notifications only (with `message`): the text an Oxy service sent
251
- * about the recipient's own account. Render these two instead of composing a
252
- * sentence from actor + entity.
276
+ * `system` notifications only: the text an Oxy service sent about the
277
+ * recipient's own account. Render these instead of composing a sentence from
278
+ * actor + entity.
253
279
  */
254
280
  title?: string;
281
+ message?: string;
255
282
  /** `system` notifications only: an optional deep link (https or the sending app's scheme). */
256
283
  url?: string;
257
- /**
258
- * What `entityId` names: `post` | `reply` | `profile`, or `app` — an opaque id
259
- * in the notifying application's namespace (`system` notifications only).
260
- */
261
- entityType?: string;
262
- entityId?: string;
284
+ read: boolean;
285
+ createdAt: string;
286
+ updatedAt: string;
287
+ }
288
+ /** One page of `GET /notifications`, newest first. */
289
+ export interface NotificationPage {
290
+ notifications: Notification[];
291
+ /** Unread notifications across ALL pages. */
292
+ unreadCount: number;
293
+ hasMore: boolean;
294
+ page: number;
295
+ limit: number;
263
296
  }
264
297
  export interface Wallet {
265
298
  id: string;
@@ -166,6 +166,15 @@ export interface OxyPagesHeadersOptions {
166
166
  * injecting the beacon at all — otherwise every page load logs a CSP
167
167
  * violation for a script we never asked for. The rest of the value is Pages'
168
168
  * own default, so caching is unchanged.
169
+ *
170
+ * And, because this origin is where a web account is created, recovered and
171
+ * deleted and every Oxy passkey is asserted (ADR 0028, ADR 0029) — the rest
172
+ * of the holder policy: no `<base>` and no form posts (`base-uri` /
173
+ * `form-action 'none'`), no Referer to anyone, passkeys for this origin only
174
+ * with every other powerful feature off (`Permissions-Policy`), and its
175
+ * responses readable by no other origin (`Cross-Origin-Resource-Policy`).
176
+ * Never `Cross-Origin-Opener-Policy`: the third-party OAuth popup reports
177
+ * its code to the app that opened it, and COOP severs `window.opener`.
169
178
  */
170
179
  sensitive?: boolean;
171
180
  /**
@@ -184,18 +193,6 @@ export interface OxyPagesHeadersOptions {
184
193
  */
185
194
  html?: readonly string[];
186
195
  }
187
- /**
188
- * Build a Cloudflare Pages `_headers` block for an Oxy HTML origin. Uses the
189
- * same CSP resolution as {@link createOxySecurityHeaders} plus the non-CSP
190
- * hardening headers Helmet would add on an Express HTML backend.
191
- *
192
- * Adding a hash to `script-src` does not narrow it: per CSP Level 3 a hash is
193
- * an additional source, so `'self'` and the beacon host keep matching external
194
- * scripts. (It WOULD neutralize `'unsafe-inline'` in the same directive — which
195
- * is why this hashes scripts only. `style-src` keeps `'unsafe-inline'` for
196
- * react-native-web's runtime stylesheet, and a style hash would silently switch
197
- * that off and render every Oxy web app unstyled.)
198
- */
199
196
  export declare function buildOxyPagesHeaders(options?: OxyPagesHeadersOptions): string;
200
197
  export interface OxySecurityHeadersOptions {
201
198
  /**
@@ -36,12 +36,6 @@
36
36
  * - `commonsAvailability` — whether Commons is installed on this device
37
37
  * (native only, via the injected `canOpenApp` probe), so the QR view can
38
38
  * offer a "Get Commons" fallback instead of a same-device dead end.
39
- * - `startPasskeyHubSignIn` — on a non-Oxy web origin (where a WebAuthn
40
- * ceremony can't run locally, the RP-ID is bound to oxy.so), open a popup
41
- * at the auth.oxy.so passkey hub instead, scoped to the SAME device-flow
42
- * session `showQr` creates, and complete it via the SAME poll/socket/claim
43
- * engine. Falls back to `showQr`'s plain QR rendering if the popup is
44
- * blocked.
45
39
  *
46
40
  * Sign-in is passkey (WebAuthn) or the Commons QR / shared-keychain handoff —
47
41
  * password, social login, and 2FA were removed ecosystem-wide. Account
@@ -114,8 +108,6 @@ export type SignInProgress = 'idle' | 'preparing' | 'awaiting-approval' | 'deliv
114
108
  * WHY a sign-in attempt ended in `'error'` — a machine-readable reason the UI
115
109
  * turns into localized copy (the controller ships no user-facing prose).
116
110
  *
117
- * - `'cancelled'` — the USER walked away (closed the passkey hub popup). A
118
- * voluntary outcome, not a failure to report.
119
111
  * - `'denied'` — the approver declined the request in Commons.
120
112
  * - `'expired'` — the request outlived its server-authoritative expiry.
121
113
  * - `'network'` — Oxy could not be reached (no response, timeout).
@@ -124,22 +116,7 @@ export type SignInProgress = 'idle' | 'preparing' | 'awaiting-approval' | 'deliv
124
116
  * - `'claim-failed'` — the request WAS approved, but the session could not be claimed.
125
117
  * - `'unknown'` — anything else; the raw message stays on `error` for logs.
126
118
  */
127
- export type SignInFailureReason = 'cancelled' | 'denied' | 'expired' | 'network' | 'not-configured' | 'unsupported-flow' | 'claim-failed' | 'unknown';
128
- /**
129
- * Minimal structural handle over a popup `Window` — just enough for the
130
- * cross-origin passkey hub flow ({@link AccountDialogController.startPasskeyHubSignIn}):
131
- * navigate it once the device-flow session's `authorizeCode` is known, detect
132
- * the user closing it early (`closed`), and close it programmatically on
133
- * completion/failure. Satisfied directly by `window.open()`'s return value on
134
- * web; native has no popup concept and never injects an opener.
135
- */
136
- export interface PopupWindowHandle {
137
- readonly closed: boolean;
138
- close(): void;
139
- location: {
140
- href: string;
141
- };
142
- }
119
+ export type SignInFailureReason = 'denied' | 'expired' | 'network' | 'not-configured' | 'unsupported-flow' | 'claim-failed' | 'unknown';
143
120
  /**
144
121
  * State of the "Sign in with Oxy" (shared-key / QR) device flow.
145
122
  *
@@ -217,6 +194,14 @@ export interface SignInFlowState {
217
194
  * attempt failing with the same message is).
218
195
  */
219
196
  attempt: number;
197
+ /**
198
+ * `true` when this attempt is the sign-in entry's EMBEDDED QR
199
+ * ({@link AccountDialogController.startInlineQr}): a request the surface
200
+ * started on its own, not one the person asked for. Its failures (an expired
201
+ * code, above all) belong to that QR, which renews itself, and are not news
202
+ * to report.
203
+ */
204
+ inline: boolean;
220
205
  }
221
206
  /** Immutable snapshot consumed by `useSyncExternalStore`. */
222
207
  export interface AccountDialogSnapshot {
@@ -343,28 +328,6 @@ export interface AccountDialogControllerOptions {
343
328
  * `showQr` behaves exactly as before (render QR only).
344
329
  */
345
330
  canOpenApp?: (url: string) => Promise<boolean>;
346
- /**
347
- * Open a new, empty popup window SYNCHRONOUSLY (before any `await`) so the
348
- * browser attributes it to the click gesture that triggered it, returning a
349
- * handle to navigate once the device-flow session is known — or `null` if
350
- * the popup was blocked. Injected by the provider (web only:
351
- * `window.open('', ...)`; absent on native, where the hub-popup flow does
352
- * not apply). Used by {@link AccountDialogController.startPasskeyHubSignIn}.
353
- */
354
- openPopup?: () => PopupWindowHandle | null;
355
- /**
356
- * Origin of the web identity carrier (default `IDENTITY_WEB_ORIGIN`,
357
- * `https://id.oxy.so`) — where a passkey sign-in or sign-up runs
358
- * and the account's identity is kept sealed under the passkey. The popup opens
359
- * `<identityOrigin>/continue?code=…`. Overridable for local/staging testing.
360
- */
361
- identityOrigin?: string;
362
- /**
363
- * @deprecated Alias of {@link identityOrigin}, kept for existing
364
- * configuration. The popup used to open `auth.oxy.so/hub-passkey`; it now opens
365
- * the identity origin's `/continue`, so a value here must be that origin.
366
- */
367
- hubBaseUrl?: string;
368
331
  /**
369
332
  * Which surface the sign-in is initiated from — a FACT supplied by the
370
333
  * consumer, because only the consumer can classify its own environment
@@ -389,8 +352,6 @@ export declare class AccountDialogController {
389
352
  private readonly openUrl?;
390
353
  private readonly canOpenApp?;
391
354
  private readonly socketFactory?;
392
- private readonly openPopup?;
393
- private readonly identityOrigin;
394
355
  private readonly platform;
395
356
  private readonly listeners;
396
357
  private view;
@@ -433,10 +394,6 @@ export declare class AccountDialogController {
433
394
  * ABANDONED request never blocks the next request's first poll.
434
395
  */
435
396
  private pollInFlightToken;
436
- /** The popup opened by {@link startPasskeyHubSignIn}, or `null`. */
437
- private activePopup;
438
- /** Watches {@link activePopup}'s `closed` state; see {@link watchPopup}. */
439
- private popupWatchTimer;
440
397
  private unsubscribeSession;
441
398
  private unsubscribeTokens;
442
399
  /** Last-observed SDK auth readiness (a planted bearer). Drives the fetch edge. */
@@ -642,38 +599,21 @@ export declare class AccountDialogController {
642
599
  */
643
600
  showQr(): Promise<void>;
644
601
  /**
645
- * Start a NEW attempt the same way the last one was started — "Try again"
646
- * repeats the user's choice rather than silently switching method. A failed
647
- * passkey-hub attempt reopens the hub popup; a failed "Sign in with Oxy"
648
- * retries that; an explicit QR request shows the QR.
602
+ * The sign-in entry's EMBEDDED QR: a request whose only route is the QR,
603
+ * started without leaving the current view.
649
604
  *
650
- * Call it straight from the press handler: the hub route opens its popup
651
- * synchronously, before this method's first `await`, so the browser still
652
- * attributes it to the click.
605
+ * Unlike {@link showQr} it runs no delivery selection. The surface starts it
606
+ * by itself, on mount, so it must not push to the person's phone or open
607
+ * Commons — nobody asked for either. Any other sign-in the person then
608
+ * chooses supersedes it (and withdraws its request) like any new attempt.
653
609
  */
654
- retrySignIn(): Promise<void>;
610
+ startInlineQr(): Promise<void>;
655
611
  /**
656
- * Web-only: sign in (or create an account) with a passkey at the web identity
657
- * carrier. The ceremony runs on `id.oxy.so`, never on the calling origin, for
658
- * two reasons: a credential minted with `WEBAUTHN_RP_ID=oxy.so` can only be
659
- * asserted from `oxy.so`/a subdomain/loopback (a browser-enforced boundary),
660
- * and only the identity origin may unseal or create the account's identity
661
- * (one identity, two carriers). Opens a popup at `<identityOrigin>/continue`,
662
- * scoped to the SAME device-flow
663
- * session {@link showQr} would create (same `authorizeCode`/`sessionToken`
664
- * pair), and let the SAME poll/socket/claim engine complete it once the hub
665
- * authorizes the session (`POST /auth/session/authorize-code/:authorizeCode`,
666
- * bearer-authed, server-side — this method never sees or transmits the
667
- * secret `sessionToken`).
668
- *
669
- * The popup MUST be opened synchronously, before any `await`, or the
670
- * browser's popup blocker loses the click-gesture attribution and silently
671
- * blocks it — so {@link openPopup} is invoked as the very first step, before
672
- * the device-flow session (which needs an async call) even exists. If the
673
- * popup is blocked (or no `openPopup` was injected), this falls back to
674
- * `showQr`'s plain QR rendering — no dead end.
675
- */
676
- startPasskeyHubSignIn(): Promise<void>;
612
+ * Start a NEW attempt the same way the last one was started — "Try again"
613
+ * repeats the user's choice rather than silently switching method: a failed
614
+ * "Sign in with Oxy" retries that; an explicit QR request shows the QR.
615
+ */
616
+ retrySignIn(): Promise<void>;
677
617
  /**
678
618
  * End whatever attempt is running (withdrawing its request) and begin a new
679
619
  * one. Returns the new attempt's identity for the caller to re-check after
@@ -683,9 +623,9 @@ export declare class AccountDialogController {
683
623
  /** Whether `attempt` is still the one the surface is waiting on. */
684
624
  private isCurrentAttempt;
685
625
  /**
686
- * Shared device-flow session creation for both {@link showQr} (renders the
687
- * QR) and {@link startPasskeyHubSignIn} (also opens the hub popup) — the
688
- * same `startCommonsSignIn` → poll/socket wiring either way. Returns the
626
+ * Shared device-flow session creation for {@link signInWithOxy},
627
+ * {@link showQr} and {@link startInlineQr} — the same `startCommonsSignIn` →
628
+ * poll/socket wiring every time. Returns the
689
629
  * handle on success (already reflected in `signIn`), or `null` when the
690
630
  * attempt failed (already set as `signIn.failure`) or was abandoned while the
691
631
  * request was being created.
@@ -696,8 +636,8 @@ export declare class AccountDialogController {
696
636
  * request, a socket, and a poll behind it.
697
637
  * @param opts.deliver - Whether to run automatic Commons delivery selection
698
638
  * ({@link resolveDeliveryRoute}). `true` for the normal one-primary-action
699
- * entry; `false` when the caller already owns the primary surface (the
700
- * passkey hub popup), where the request's Commons route is simply the QR.
639
+ * entry; `false` for the embedded QR, which the screen starts by itself,
640
+ * where the request's Commons route is simply the QR.
701
641
  */
702
642
  private startDeviceFlowSession;
703
643
  /**
@@ -744,17 +684,6 @@ export declare class AccountDialogController {
744
684
  * end the user stares at.
745
685
  */
746
686
  private openCommonsLink;
747
- /**
748
- * Poll {@link PopupWindowHandle.closed} so a user who dismisses the hub
749
- * popup without completing sign-in gets prompt feedback (there is no DOM
750
- * event for a cross-origin popup closing — polling is the only mechanism).
751
- * Cleared by {@link closeActivePopup}, which every sign-in exit path already
752
- * calls.
753
- */
754
- private watchPopup;
755
- private clearPopupWatchTimer;
756
- /** Tear down the popup watch timer and close the popup, if any. Idempotent. */
757
- private closeActivePopup;
758
687
  /**
759
688
  * Resolve whether Commons is installed on this device via the injected
760
689
  * `canOpenApp` probe, updating {@link commonsAvailability} as durable,
@@ -769,7 +698,7 @@ export declare class AccountDialogController {
769
698
  */
770
699
  private resolveCommonsAvailability;
771
700
  /**
772
- * Tear down the active sign-in device flow (timers + socket + popup + token),
701
+ * Tear down the active sign-in device flow (timers + socket + token),
773
702
  * WITHDRAW the request server-side, and reset to idle.
774
703
  *
775
704
  * Cancellation has to converge in both directions: the surface closing must
@@ -869,6 +798,8 @@ export declare class AccountDialogController {
869
798
  * to advance without a fact behind it.
870
799
  */
871
800
  private setSignIn;
801
+ /** `facts` as the current attempt's state. */
802
+ private stampSignIn;
872
803
  /** Update a subset of the device-flow facts, re-deriving `progress`. */
873
804
  private patchSignIn;
874
805
  private computeSnapshot;