@oxy.so/core 1.18.1 → 3.0.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.
- package/README.md +13 -7
- package/dist/cjs/HttpService.js +159 -111
- package/dist/cjs/OxyServices.errors.js +48 -1
- package/dist/cjs/OxyServices.js +263 -60
- package/dist/cjs/api/accounts.js +231 -0
- package/dist/cjs/api/agency.js +102 -0
- package/dist/cjs/api/appData.js +45 -0
- package/dist/cjs/api/apps.js +197 -0
- package/dist/cjs/api/assets.js +329 -0
- package/dist/cjs/api/auth.js +900 -0
- package/dist/cjs/api/billing.js +44 -0
- package/dist/cjs/api/civic.js +289 -0
- package/dist/cjs/api/contacts.js +16 -0
- package/dist/cjs/api/devices.js +351 -0
- package/dist/cjs/api/follows.js +240 -0
- package/dist/cjs/api/identity.js +569 -0
- package/dist/cjs/api/linkedAccounts.js +48 -0
- package/dist/cjs/api/nodes.js +75 -0
- package/dist/cjs/api/notifications.js +75 -0
- package/dist/cjs/api/privacy.js +105 -0
- package/dist/cjs/api/reputation.js +74 -0
- package/dist/cjs/api/session.js +177 -0
- package/dist/cjs/api/store.js +199 -0
- package/dist/cjs/api/topics.js +66 -0
- package/dist/cjs/api/users.js +309 -0
- package/dist/cjs/boot/sessionColdBoot.js +4 -4
- package/dist/cjs/civic/index.js +16 -0
- package/dist/cjs/civic/payloads.js +156 -0
- package/dist/cjs/client/context.js +2 -0
- package/dist/cjs/crypto/identityLink.js +5 -5
- package/dist/cjs/crypto/index.js +44 -0
- package/dist/cjs/crypto/internal.js +37 -0
- package/dist/cjs/crypto/keyManager.js +2 -2
- package/dist/cjs/crypto/polyfill.js +9 -80
- package/dist/cjs/crypto/recoveryPhrase.js +1 -0
- package/dist/cjs/crypto/registrationPow.js +1 -1
- package/dist/cjs/crypto/signatureService.js +1 -0
- package/dist/cjs/i18n/accountCategoryLabels.js +3 -3
- package/dist/cjs/i18n/accountRoleLabels.js +7 -7
- package/dist/cjs/i18n/index.js +130 -52
- package/dist/cjs/i18n/locales/{ar-SA.json → ar-SA.js} +134 -52
- package/dist/cjs/i18n/locales/{ca-ES.json → ca-ES.js} +134 -52
- package/dist/{esm/i18n/locales/de-DE.json → cjs/i18n/locales/de-DE.js} +136 -54
- package/dist/{esm/i18n/locales/en-US.json → cjs/i18n/locales/en-US.js} +163 -245
- package/dist/{esm/i18n/locales/es-ES.json → cjs/i18n/locales/es-ES.js} +163 -245
- package/dist/cjs/i18n/locales/{fr-FR.json → fr-FR.js} +134 -52
- package/dist/cjs/i18n/locales/{it-IT.json → it-IT.js} +134 -52
- package/dist/{esm/i18n/locales/ja-JP.json → cjs/i18n/locales/ja-JP.js} +134 -52
- package/dist/cjs/i18n/locales/{ko-KR.json → ko-KR.js} +134 -52
- package/dist/{esm/i18n/locales/pt-PT.json → cjs/i18n/locales/pt-PT.js} +136 -54
- package/dist/{esm/i18n/locales/zh-CN.json → cjs/i18n/locales/zh-CN.js} +135 -53
- package/dist/cjs/i18n/reputationCategoryLabels.js +2 -2
- package/dist/cjs/i18n/trustTierLabels.js +2 -2
- package/dist/cjs/index.js +55 -263
- package/dist/cjs/inference/index.js +21 -0
- package/dist/cjs/server/OxyServer.js +521 -0
- package/dist/cjs/server/auth.js +2 -2
- package/dist/cjs/server/index.js +23 -6
- package/dist/cjs/server/middleware.js +1154 -0
- package/dist/cjs/server/namespaces.js +214 -0
- package/dist/cjs/server/rateLimit.js +2 -2
- package/dist/cjs/server/requesterAssertion.js +5 -5
- package/dist/cjs/server/securityHeaders.js +3 -3
- package/dist/cjs/server/traffic.js +7 -6
- package/dist/cjs/server/trafficNodeHttp.js +9 -3
- package/dist/cjs/server/userInvalidation.js +5 -5
- package/dist/cjs/session/accountDialogController.js +23 -23
- package/dist/cjs/session/authStateStore.js +2 -2
- package/dist/cjs/session/identitySession.js +3 -3
- package/dist/cjs/session/index.js +131 -0
- package/dist/cjs/session/refresh.js +37 -30
- package/dist/cjs/session/sessionClientHost.js +5 -5
- package/dist/cjs/session/sharedDeviceCredential.js +3 -2
- package/dist/cjs/shared/utils/networkUtils.js +2 -2
- package/dist/cjs/utils/accountCacheSweep.js +11 -15
- package/dist/cjs/utils/asyncUtils.js +22 -163
- package/dist/cjs/utils/authHelpers.js +2 -2
- package/dist/cjs/utils/authWebUrl.js +3 -4
- package/dist/cjs/utils/avatarUtils.js +2 -2
- package/dist/cjs/utils/cache.js +32 -2
- package/dist/cjs/utils/cacheKey.js +5 -10
- package/dist/cjs/utils/deviceManager.js +17 -16
- package/dist/cjs/utils/edgeRegion.js +2 -3
- package/dist/cjs/utils/identityCacheSweep.js +6 -10
- package/dist/cjs/utils/oxyServiceEnvironment.js +3 -7
- package/dist/cjs/utils/requestUtils.js +40 -33
- package/dist/cjs/utils/sessionUtils.js +0 -2
- package/dist/cjs/utils/tokenClaims.js +34 -0
- package/dist/esm/HttpService.js +160 -112
- package/dist/esm/OxyServices.errors.js +45 -0
- package/dist/esm/OxyServices.js +262 -57
- package/dist/esm/api/accounts.js +218 -0
- package/dist/esm/api/agency.js +95 -0
- package/dist/esm/api/appData.js +40 -0
- package/dist/esm/api/apps.js +191 -0
- package/dist/esm/api/assets.js +324 -0
- package/dist/esm/api/auth.js +858 -0
- package/dist/esm/api/billing.js +40 -0
- package/dist/esm/api/civic.js +250 -0
- package/dist/esm/api/contacts.js +12 -0
- package/dist/esm/api/devices.js +346 -0
- package/dist/esm/api/follows.js +236 -0
- package/dist/esm/api/identity.js +527 -0
- package/dist/esm/api/linkedAccounts.js +44 -0
- package/dist/esm/api/nodes.js +71 -0
- package/dist/esm/api/notifications.js +71 -0
- package/dist/esm/api/privacy.js +101 -0
- package/dist/esm/api/reputation.js +70 -0
- package/dist/esm/api/session.js +173 -0
- package/dist/esm/api/store.js +192 -0
- package/dist/esm/api/topics.js +62 -0
- package/dist/esm/api/users.js +272 -0
- package/dist/esm/boot/sessionColdBoot.js +4 -4
- package/dist/esm/civic/index.js +10 -0
- package/dist/esm/civic/payloads.js +118 -0
- package/dist/esm/client/context.js +1 -0
- package/dist/esm/crypto/identityLink.js +5 -5
- package/dist/esm/crypto/index.js +24 -0
- package/dist/esm/crypto/internal.js +19 -0
- package/dist/esm/crypto/keyManager.js +2 -2
- package/dist/esm/crypto/polyfill.js +10 -81
- package/dist/esm/crypto/recoveryPhrase.js +1 -0
- package/dist/esm/crypto/registrationPow.js +1 -1
- package/dist/esm/crypto/signatureService.js +1 -0
- package/dist/esm/i18n/accountCategoryLabels.js +2 -2
- package/dist/esm/i18n/accountRoleLabels.js +1 -1
- package/dist/esm/i18n/index.js +93 -52
- package/dist/esm/i18n/locales/{ar-SA.json → ar-SA.js} +132 -52
- package/dist/esm/i18n/locales/{ca-ES.json → ca-ES.js} +132 -52
- package/dist/{cjs/i18n/locales/de-DE.json → esm/i18n/locales/de-DE.js} +134 -54
- package/dist/{cjs/i18n/locales/en-US.json → esm/i18n/locales/en-US.js} +161 -245
- package/dist/{cjs/i18n/locales/es-ES.json → esm/i18n/locales/es-ES.js} +161 -245
- package/dist/esm/i18n/locales/{fr-FR.json → fr-FR.js} +132 -52
- package/dist/esm/i18n/locales/{it-IT.json → it-IT.js} +132 -52
- package/dist/{cjs/i18n/locales/ja-JP.json → esm/i18n/locales/ja-JP.js} +132 -52
- package/dist/esm/i18n/locales/{ko-KR.json → ko-KR.js} +132 -52
- package/dist/{cjs/i18n/locales/pt-PT.json → esm/i18n/locales/pt-PT.js} +134 -54
- package/dist/{cjs/i18n/locales/zh-CN.json → esm/i18n/locales/zh-CN.js} +133 -53
- package/dist/esm/i18n/reputationCategoryLabels.js +1 -1
- package/dist/esm/i18n/trustTierLabels.js +1 -1
- package/dist/esm/index.js +30 -166
- package/dist/esm/inference/index.js +13 -0
- package/dist/esm/server/OxyServer.js +516 -0
- package/dist/esm/server/auth.js +2 -2
- package/dist/esm/server/index.js +10 -5
- package/dist/esm/server/middleware.js +1147 -0
- package/dist/esm/server/namespaces.js +206 -0
- package/dist/esm/server/rateLimit.js +2 -2
- package/dist/esm/server/requesterAssertion.js +5 -5
- package/dist/esm/server/securityHeaders.js +3 -3
- package/dist/esm/server/traffic.js +7 -6
- package/dist/esm/server/trafficNodeHttp.js +9 -3
- package/dist/esm/server/userInvalidation.js +5 -5
- package/dist/esm/session/accountDialogController.js +23 -23
- package/dist/esm/session/authStateStore.js +2 -2
- package/dist/esm/session/identitySession.js +3 -3
- package/dist/esm/session/index.js +82 -0
- package/dist/esm/session/refresh.js +37 -30
- package/dist/esm/session/sessionClientHost.js +5 -5
- package/dist/esm/session/sharedDeviceCredential.js +3 -2
- package/dist/esm/shared/utils/networkUtils.js +2 -2
- package/dist/esm/utils/accountCacheSweep.js +11 -15
- package/dist/esm/utils/asyncUtils.js +22 -151
- package/dist/esm/utils/authHelpers.js +2 -2
- package/dist/esm/utils/authWebUrl.js +3 -4
- package/dist/esm/utils/avatarUtils.js +2 -2
- package/dist/esm/utils/cache.js +32 -2
- package/dist/esm/utils/cacheKey.js +5 -10
- package/dist/esm/utils/deviceManager.js +17 -16
- package/dist/esm/utils/edgeRegion.js +1 -2
- package/dist/esm/utils/identityCacheSweep.js +6 -10
- package/dist/esm/utils/oxyServiceEnvironment.js +3 -7
- package/dist/esm/utils/requestUtils.js +40 -33
- package/dist/esm/utils/sessionUtils.js +0 -2
- package/dist/esm/utils/tokenClaims.js +31 -0
- package/dist/types/HttpService.d.ts +26 -9
- package/dist/types/OxyServices.d.ts +160 -131
- package/dist/types/OxyServices.errors.d.ts +42 -0
- package/dist/types/api/accounts.d.ts +415 -0
- package/dist/types/api/agency.d.ts +135 -0
- package/dist/types/api/appData.d.ts +27 -0
- package/dist/types/api/apps.d.ts +433 -0
- package/dist/types/api/assets.d.ts +174 -0
- package/dist/types/api/auth.d.ts +766 -0
- package/dist/types/api/billing.d.ts +113 -0
- package/dist/types/api/civic.d.ts +203 -0
- package/dist/types/api/contacts.d.ts +31 -0
- package/{src/mixins/OxyServices.deviceBoot.ts → dist/types/api/devices.d.ts} +130 -110
- package/dist/types/api/follows.d.ts +236 -0
- package/dist/types/api/identity.d.ts +275 -0
- package/dist/types/api/linkedAccounts.d.ts +48 -0
- package/dist/types/api/nodes.d.ts +102 -0
- package/dist/types/api/notifications.d.ts +77 -0
- package/dist/types/api/privacy.d.ts +43 -0
- package/dist/types/api/reputation.d.ts +50 -0
- package/dist/types/api/session.d.ts +85 -0
- package/dist/types/api/store.d.ts +278 -0
- package/dist/types/api/topics.d.ts +34 -0
- package/dist/types/api/users.d.ts +134 -0
- package/dist/types/boot/sessionColdBoot.d.ts +1 -1
- package/dist/types/civic/index.d.ts +10 -0
- package/dist/types/civic/payloads.d.ts +83 -0
- package/dist/types/client/context.d.ts +33 -0
- package/dist/types/crypto/identityLink.d.ts +5 -5
- package/dist/types/crypto/index.d.ts +24 -0
- package/dist/types/crypto/internal.d.ts +19 -0
- package/dist/types/crypto/polyfill.d.ts +6 -9
- package/dist/types/crypto/registrationPow.d.ts +1 -1
- package/dist/types/i18n/accountCategoryLabels.d.ts +1 -1
- package/dist/types/i18n/accountRoleLabels.d.ts +1 -1
- package/dist/types/i18n/index.d.ts +20 -1
- package/dist/types/i18n/locales/ar-SA.d.ts +386 -0
- package/dist/types/i18n/locales/ca-ES.d.ts +386 -0
- package/dist/types/i18n/locales/de-DE.d.ts +386 -0
- package/dist/types/i18n/locales/en-US.d.ts +2034 -0
- package/dist/types/i18n/locales/es-ES.d.ts +2034 -0
- package/dist/types/i18n/locales/fr-FR.d.ts +386 -0
- package/dist/types/i18n/locales/it-IT.d.ts +386 -0
- package/dist/types/i18n/locales/ja-JP.d.ts +386 -0
- package/dist/types/i18n/locales/ko-KR.d.ts +386 -0
- package/dist/types/i18n/locales/pt-PT.d.ts +386 -0
- package/dist/types/i18n/locales/zh-CN.d.ts +386 -0
- package/dist/types/index.d.ts +46 -95
- package/dist/types/inference/index.d.ts +22 -0
- package/dist/types/models/interfaces.d.ts +1 -22
- package/dist/types/server/OxyServer.d.ts +183 -0
- package/dist/types/server/auth.d.ts +10 -7
- package/dist/types/server/index.d.ts +10 -4
- package/dist/types/server/middleware.d.ts +291 -0
- package/dist/types/server/namespaces.d.ts +98 -0
- package/dist/types/server/rateLimit.d.ts +7 -5
- package/dist/types/server/requesterAssertion.d.ts +14 -11
- package/dist/types/server/securityHeaders.d.ts +4 -5
- package/dist/types/server/userInvalidation.d.ts +10 -8
- package/dist/types/session/accountDelegationAccess.d.ts +1 -1
- package/dist/types/session/accountDialogController.d.ts +8 -8
- package/dist/types/session/accountSwitchTargets.d.ts +1 -1
- package/dist/types/session/authStateStore.d.ts +7 -6
- package/dist/types/session/index.d.ts +40 -0
- package/dist/types/utils/accountCacheSweep.d.ts +7 -10
- package/dist/types/utils/apiUtils.d.ts +3 -3
- package/dist/types/utils/asyncUtils.d.ts +11 -58
- package/dist/types/utils/authWebUrl.d.ts +3 -4
- package/dist/types/utils/avatarUtils.d.ts +5 -6
- package/dist/types/utils/cache.d.ts +11 -3
- package/dist/types/utils/commonsApproval.d.ts +1 -1
- package/dist/types/utils/edgeRegion.d.ts +1 -2
- package/dist/types/utils/identityCacheSweep.d.ts +6 -4
- package/dist/types/utils/oauthPkce.d.ts +2 -2
- package/dist/types/utils/oxyServiceEnvironment.d.ts +3 -7
- package/dist/types/utils/requestUtils.d.ts +18 -14
- package/dist/types/utils/tokenClaims.d.ts +9 -0
- package/package.json +68 -7
- package/src/HttpService.ts +166 -131
- package/src/OxyServices.errors.ts +73 -0
- package/src/OxyServices.ts +289 -164
- package/src/api/accounts.ts +579 -0
- package/src/api/agency.ts +277 -0
- package/src/api/appData.ts +63 -0
- package/src/api/apps.ts +596 -0
- package/src/api/assets.ts +416 -0
- package/src/api/auth.ts +1457 -0
- package/src/api/billing.ts +144 -0
- package/src/api/civic.ts +428 -0
- package/src/api/contacts.ts +41 -0
- package/src/api/devices.ts +438 -0
- package/src/api/follows.ts +414 -0
- package/src/api/identity.ts +727 -0
- package/src/api/linkedAccounts.ts +78 -0
- package/src/api/nodes.ts +169 -0
- package/src/api/notifications.ts +135 -0
- package/src/api/privacy.ts +120 -0
- package/src/api/reputation.ts +134 -0
- package/src/api/session.ts +210 -0
- package/src/api/store.ts +435 -0
- package/src/api/topics.ts +83 -0
- package/src/api/users.ts +357 -0
- package/src/boot/sessionColdBoot.ts +4 -4
- package/src/civic/index.ts +12 -0
- package/src/civic/payloads.ts +175 -0
- package/src/client/context.ts +34 -0
- package/src/crypto/identityLink.ts +5 -5
- package/src/crypto/index.ts +39 -0
- package/src/crypto/internal.ts +20 -0
- package/src/crypto/keyManager.ts +2 -2
- package/src/crypto/polyfill.ts +9 -89
- package/src/crypto/recoveryPhrase.ts +1 -0
- package/src/crypto/registrationPow.ts +1 -1
- package/src/crypto/signatureService.ts +1 -0
- package/src/i18n/accountCategoryLabels.ts +2 -2
- package/src/i18n/accountRoleLabels.ts +2 -2
- package/src/i18n/index.ts +105 -54
- package/src/i18n/locales/{ar-SA.json → ar-SA.ts} +132 -52
- package/src/i18n/locales/{ca-ES.json → ca-ES.ts} +132 -52
- package/{dist/cjs/i18n/locales/locales/de-DE.json → src/i18n/locales/de-DE.ts} +134 -54
- package/src/i18n/locales/{en-US.json → en-US.ts} +161 -245
- package/{dist/cjs/i18n/locales/locales/es-ES.json → src/i18n/locales/es-ES.ts} +161 -245
- package/src/i18n/locales/{fr-FR.json → fr-FR.ts} +132 -52
- package/src/i18n/locales/{it-IT.json → it-IT.ts} +132 -52
- package/{dist/cjs/i18n/locales/locales/ja-JP.json → src/i18n/locales/ja-JP.ts} +132 -52
- package/src/i18n/locales/{ko-KR.json → ko-KR.ts} +132 -52
- package/src/i18n/locales/{pt-PT.json → pt-PT.ts} +134 -54
- package/src/i18n/locales/{zh-CN.json → zh-CN.ts} +133 -53
- package/src/i18n/reputationCategoryLabels.ts +1 -1
- package/src/i18n/trustTierLabels.ts +1 -1
- package/src/index.ts +170 -501
- package/src/inference/index.ts +48 -0
- package/src/models/interfaces.ts +1 -22
- package/src/server/OxyServer.ts +637 -0
- package/src/server/auth.ts +12 -8
- package/src/server/index.ts +28 -5
- package/src/server/middleware.ts +1520 -0
- package/src/server/namespaces.ts +257 -0
- package/src/server/rateLimit.ts +10 -9
- package/src/server/requesterAssertion.ts +13 -10
- package/src/server/securityHeaders.ts +7 -8
- package/src/server/traffic.ts +6 -6
- package/src/server/trafficNodeHttp.ts +7 -3
- package/src/server/userInvalidation.ts +9 -9
- package/src/session/accountDelegationAccess.ts +1 -1
- package/src/session/accountDialogController.ts +24 -24
- package/src/session/accountSwitchTargets.ts +1 -1
- package/src/session/authStateStore.ts +9 -8
- package/src/session/identitySession.ts +3 -3
- package/src/session/index.ts +210 -0
- package/src/session/refresh.ts +36 -30
- package/src/session/sessionClientHost.ts +5 -5
- package/src/session/sharedDeviceCredential.ts +3 -2
- package/src/shared/utils/networkUtils.ts +2 -2
- package/src/utils/accountCacheSweep.ts +11 -15
- package/src/utils/apiUtils.ts +3 -3
- package/src/utils/asyncUtils.ts +38 -235
- package/src/utils/authHelpers.ts +2 -2
- package/src/utils/authWebUrl.ts +3 -4
- package/src/utils/avatarUtils.ts +4 -7
- package/src/utils/cache.ts +36 -5
- package/src/utils/cacheKey.ts +5 -9
- package/src/utils/commonsApproval.ts +1 -1
- package/src/utils/deviceManager.ts +17 -16
- package/src/utils/edgeRegion.ts +1 -2
- package/src/utils/identityCacheSweep.ts +7 -12
- package/src/utils/oauthPkce.ts +2 -2
- package/src/utils/oxyServiceEnvironment.ts +3 -7
- package/src/utils/requestUtils.ts +48 -44
- package/src/utils/sessionUtils.ts +0 -2
- package/src/utils/tokenClaims.ts +38 -0
- package/dist/cjs/.tsbuildinfo +0 -1
- package/dist/cjs/OxyServices.base.js +0 -392
- package/dist/cjs/i18n/locales/locales/ar-SA.json +0 -306
- package/dist/cjs/i18n/locales/locales/ca-ES.json +0 -306
- package/dist/cjs/i18n/locales/locales/en-US.json +0 -2127
- package/dist/cjs/i18n/locales/locales/fr-FR.json +0 -306
- package/dist/cjs/i18n/locales/locales/it-IT.json +0 -306
- package/dist/cjs/i18n/locales/locales/ko-KR.json +0 -306
- package/dist/cjs/i18n/locales/locales/pt-PT.json +0 -306
- package/dist/cjs/i18n/locales/locales/zh-CN.json +0 -306
- package/dist/cjs/mixins/OxyServices.accounts.js +0 -555
- package/dist/cjs/mixins/OxyServices.agency.js +0 -154
- package/dist/cjs/mixins/OxyServices.analytics.js +0 -49
- package/dist/cjs/mixins/OxyServices.appData.js +0 -108
- package/dist/cjs/mixins/OxyServices.assets.js +0 -854
- package/dist/cjs/mixins/OxyServices.auth.js +0 -1495
- package/dist/cjs/mixins/OxyServices.chains.js +0 -73
- package/dist/cjs/mixins/OxyServices.civic.js +0 -611
- package/dist/cjs/mixins/OxyServices.connectedApps.js +0 -95
- package/dist/cjs/mixins/OxyServices.contacts.js +0 -50
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +0 -191
- package/dist/cjs/mixins/OxyServices.devices.js +0 -116
- package/dist/cjs/mixins/OxyServices.features.js +0 -335
- package/dist/cjs/mixins/OxyServices.followGraph.js +0 -221
- package/dist/cjs/mixins/OxyServices.identity.js +0 -562
- package/dist/cjs/mixins/OxyServices.identityBackup.js +0 -172
- package/dist/cjs/mixins/OxyServices.inference.js +0 -59
- package/dist/cjs/mixins/OxyServices.language.js +0 -125
- package/dist/cjs/mixins/OxyServices.linkedAccounts.js +0 -79
- package/dist/cjs/mixins/OxyServices.location.js +0 -43
- package/dist/cjs/mixins/OxyServices.nodes.js +0 -175
- package/dist/cjs/mixins/OxyServices.notifications.js +0 -63
- package/dist/cjs/mixins/OxyServices.payment.js +0 -158
- package/dist/cjs/mixins/OxyServices.privacy.js +0 -199
- package/dist/cjs/mixins/OxyServices.reputation.js +0 -289
- package/dist/cjs/mixins/OxyServices.security.js +0 -89
- package/dist/cjs/mixins/OxyServices.store.js +0 -266
- package/dist/cjs/mixins/OxyServices.topics.js +0 -137
- package/dist/cjs/mixins/OxyServices.user.js +0 -954
- package/dist/cjs/mixins/OxyServices.utility.js +0 -1402
- package/dist/cjs/mixins/index.js +0 -136
- package/dist/cjs/mixins/mixinHelpers.js +0 -53
- package/dist/cjs/utils/webauthnOrigin.js +0 -51
- package/dist/esm/.tsbuildinfo +0 -1
- package/dist/esm/OxyServices.base.js +0 -388
- package/dist/esm/i18n/locales/locales/ar-SA.json +0 -306
- package/dist/esm/i18n/locales/locales/ca-ES.json +0 -306
- package/dist/esm/i18n/locales/locales/de-DE.json +0 -306
- package/dist/esm/i18n/locales/locales/en-US.json +0 -2127
- package/dist/esm/i18n/locales/locales/es-ES.json +0 -2127
- package/dist/esm/i18n/locales/locales/fr-FR.json +0 -306
- package/dist/esm/i18n/locales/locales/it-IT.json +0 -306
- package/dist/esm/i18n/locales/locales/ja-JP.json +0 -306
- package/dist/esm/i18n/locales/locales/ko-KR.json +0 -306
- package/dist/esm/i18n/locales/locales/pt-PT.json +0 -306
- package/dist/esm/i18n/locales/locales/zh-CN.json +0 -306
- package/dist/esm/mixins/OxyServices.accounts.js +0 -543
- package/dist/esm/mixins/OxyServices.agency.js +0 -151
- package/dist/esm/mixins/OxyServices.analytics.js +0 -46
- package/dist/esm/mixins/OxyServices.appData.js +0 -103
- package/dist/esm/mixins/OxyServices.assets.js +0 -851
- package/dist/esm/mixins/OxyServices.auth.js +0 -1455
- package/dist/esm/mixins/OxyServices.chains.js +0 -70
- package/dist/esm/mixins/OxyServices.civic.js +0 -605
- package/dist/esm/mixins/OxyServices.connectedApps.js +0 -92
- package/dist/esm/mixins/OxyServices.contacts.js +0 -47
- package/dist/esm/mixins/OxyServices.deviceBoot.js +0 -186
- package/dist/esm/mixins/OxyServices.devices.js +0 -113
- package/dist/esm/mixins/OxyServices.features.js +0 -332
- package/dist/esm/mixins/OxyServices.followGraph.js +0 -218
- package/dist/esm/mixins/OxyServices.identity.js +0 -558
- package/dist/esm/mixins/OxyServices.identityBackup.js +0 -169
- package/dist/esm/mixins/OxyServices.inference.js +0 -56
- package/dist/esm/mixins/OxyServices.language.js +0 -122
- package/dist/esm/mixins/OxyServices.linkedAccounts.js +0 -76
- package/dist/esm/mixins/OxyServices.location.js +0 -40
- package/dist/esm/mixins/OxyServices.nodes.js +0 -172
- package/dist/esm/mixins/OxyServices.notifications.js +0 -60
- package/dist/esm/mixins/OxyServices.payment.js +0 -155
- package/dist/esm/mixins/OxyServices.privacy.js +0 -196
- package/dist/esm/mixins/OxyServices.reputation.js +0 -286
- package/dist/esm/mixins/OxyServices.security.js +0 -86
- package/dist/esm/mixins/OxyServices.store.js +0 -263
- package/dist/esm/mixins/OxyServices.topics.js +0 -134
- package/dist/esm/mixins/OxyServices.user.js +0 -951
- package/dist/esm/mixins/OxyServices.utility.js +0 -1397
- package/dist/esm/mixins/index.js +0 -133
- package/dist/esm/mixins/mixinHelpers.js +0 -48
- package/dist/esm/utils/webauthnOrigin.js +0 -48
- package/dist/types/.tsbuildinfo +0 -1
- package/dist/types/OxyServices.base.d.ts +0 -189
- package/dist/types/mixins/OxyServices.accounts.d.ts +0 -868
- package/dist/types/mixins/OxyServices.agency.d.ts +0 -176
- package/dist/types/mixins/OxyServices.analytics.d.ts +0 -72
- package/dist/types/mixins/OxyServices.appData.d.ts +0 -108
- package/dist/types/mixins/OxyServices.assets.d.ts +0 -371
- package/dist/types/mixins/OxyServices.auth.d.ts +0 -998
- package/dist/types/mixins/OxyServices.chains.d.ts +0 -156
- package/dist/types/mixins/OxyServices.civic.d.ts +0 -520
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -184
- package/dist/types/mixins/OxyServices.contacts.d.ts +0 -100
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +0 -185
- package/dist/types/mixins/OxyServices.devices.d.ts +0 -103
- package/dist/types/mixins/OxyServices.features.d.ts +0 -250
- package/dist/types/mixins/OxyServices.followGraph.d.ts +0 -223
- package/dist/types/mixins/OxyServices.identity.d.ts +0 -337
- package/dist/types/mixins/OxyServices.identityBackup.d.ts +0 -129
- package/dist/types/mixins/OxyServices.inference.d.ts +0 -95
- package/dist/types/mixins/OxyServices.language.d.ts +0 -87
- package/dist/types/mixins/OxyServices.linkedAccounts.d.ts +0 -107
- package/dist/types/mixins/OxyServices.location.d.ts +0 -70
- package/dist/types/mixins/OxyServices.nodes.d.ts +0 -198
- package/dist/types/mixins/OxyServices.notifications.d.ts +0 -143
- package/dist/types/mixins/OxyServices.payment.d.ts +0 -117
- package/dist/types/mixins/OxyServices.privacy.d.ts +0 -146
- package/dist/types/mixins/OxyServices.reputation.d.ts +0 -202
- package/dist/types/mixins/OxyServices.security.d.ts +0 -84
- package/dist/types/mixins/OxyServices.store.d.ts +0 -334
- package/dist/types/mixins/OxyServices.topics.d.ts +0 -110
- package/dist/types/mixins/OxyServices.user.d.ts +0 -600
- package/dist/types/mixins/OxyServices.utility.d.ts +0 -526
- package/dist/types/mixins/index.d.ts +0 -87
- package/dist/types/mixins/mixinHelpers.d.ts +0 -31
- package/dist/types/utils/webauthnOrigin.d.ts +0 -32
- package/src/OxyServices.base.ts +0 -480
- package/src/__tests__/authSocket.test.ts +0 -96
- package/src/__tests__/fixEsmImports.test.ts +0 -132
- package/src/__tests__/httpServiceAbort.test.ts +0 -308
- package/src/__tests__/httpServiceActivityId.test.ts +0 -49
- package/src/__tests__/httpServiceAuthSelfAwait.test.ts +0 -108
- package/src/__tests__/httpServiceCache.test.ts +0 -318
- package/src/__tests__/httpServiceFormEncoded.test.ts +0 -139
- package/src/__tests__/httpServiceQueueDeadlock.test.ts +0 -128
- package/src/__tests__/httpServiceRateLimitedRefresh.test.ts +0 -160
- package/src/__tests__/httpServiceUnwrapEnvelope.test.ts +0 -115
- package/src/__tests__/httpServiceWrites.test.ts +0 -175
- package/src/__tests__/inSessionRefresh.test.ts +0 -313
- package/src/__tests__/linkedClient.test.ts +0 -363
- package/src/__tests__/nearExpiryMintStorm.test.ts +0 -165
- package/src/__tests__/parseHttpErrorBody.test.ts +0 -116
- package/src/__tests__/publicInterfaceParity.test.ts +0 -75
- package/src/__tests__/serverValueImportsDeclared.test.ts +0 -120
- package/src/__tests__/serviceIdentityWhenAnonymous.test.ts +0 -159
- package/src/__tests__/serviceTokenWorkloadFallback.test.ts +0 -85
- package/src/__tests__/userIdentity.test.ts +0 -143
- package/src/boot/__tests__/sessionColdBoot.identity.test.ts +0 -393
- package/src/boot/__tests__/sessionColdBoot.sharedDevice.test.ts +0 -325
- package/src/boot/__tests__/sessionColdBoot.test.ts +0 -570
- package/src/crypto/__tests__/backupMaterial.test.ts +0 -86
- package/src/crypto/__tests__/cryptoPrimitives.test.ts +0 -226
- package/src/crypto/__tests__/identityLink.test.ts +0 -18
- package/src/crypto/__tests__/identityMocks.ts +0 -125
- package/src/crypto/__tests__/identityProof.test.ts +0 -81
- package/src/crypto/__tests__/keyManager.atomicity.test.ts +0 -272
- package/src/crypto/__tests__/keyManager.cacheSafety.test.ts +0 -175
- package/src/crypto/__tests__/keyManager.deviceBackup.test.ts +0 -310
- package/src/crypto/__tests__/keyManager.identityStatus.test.ts +0 -217
- package/src/crypto/__tests__/keyManager.recoveryLadder.test.ts +0 -179
- package/src/crypto/__tests__/keyManager.recoveryMnemonic.test.ts +0 -138
- package/src/crypto/__tests__/keyManager.sharedSlotSync.test.ts +0 -150
- package/src/crypto/__tests__/keyManager.storageMigration.test.ts +0 -227
- package/src/crypto/__tests__/keyManager.test.ts +0 -329
- package/src/crypto/__tests__/polyfill.test.ts +0 -109
- package/src/crypto/__tests__/polyfillOrder.test.ts +0 -114
- package/src/crypto/__tests__/recoveryPhrase.test.ts +0 -61
- package/src/crypto/__tests__/registrationPow.test.ts +0 -61
- package/src/crypto/__tests__/scopedSeed.test.ts +0 -126
- package/src/crypto/__tests__/signChallengeShared.test.ts +0 -63
- package/src/crypto/__tests__/signedRecord.test.ts +0 -230
- package/src/i18n/__tests__/accountCategoryLabels.test.ts +0 -62
- package/src/i18n/__tests__/accountRoleLabels.test.ts +0 -54
- package/src/i18n/__tests__/reputationCategoryLabels.test.ts +0 -56
- package/src/i18n/__tests__/trustTierLabels.test.ts +0 -47
- package/src/i18n/locales/de-DE.json +0 -306
- package/src/i18n/locales/es-ES.json +0 -2127
- package/src/i18n/locales/ja-JP.json +0 -306
- package/src/inference/__tests__/OxyInferenceClient.test.ts +0 -899
- package/src/logger/__tests__/logger.test.ts +0 -207
- package/src/mixins/OxyServices.accounts.ts +0 -1330
- package/src/mixins/OxyServices.agency.ts +0 -353
- package/src/mixins/OxyServices.analytics.ts +0 -53
- package/src/mixins/OxyServices.appData.ts +0 -158
- package/src/mixins/OxyServices.assets.ts +0 -964
- package/src/mixins/OxyServices.auth.ts +0 -2083
- package/src/mixins/OxyServices.chains.ts +0 -134
- package/src/mixins/OxyServices.civic.ts +0 -945
- package/src/mixins/OxyServices.connectedApps.ts +0 -212
- package/src/mixins/OxyServices.contacts.ts +0 -73
- package/src/mixins/OxyServices.devices.ts +0 -120
- package/src/mixins/OxyServices.features.ts +0 -454
- package/src/mixins/OxyServices.followGraph.ts +0 -293
- package/src/mixins/OxyServices.identity.ts +0 -787
- package/src/mixins/OxyServices.identityBackup.ts +0 -250
- package/src/mixins/OxyServices.inference.ts +0 -57
- package/src/mixins/OxyServices.language.ts +0 -144
- package/src/mixins/OxyServices.linkedAccounts.ts +0 -129
- package/src/mixins/OxyServices.location.ts +0 -46
- package/src/mixins/OxyServices.nodes.ts +0 -337
- package/src/mixins/OxyServices.notifications.ts +0 -142
- package/src/mixins/OxyServices.payment.ts +0 -163
- package/src/mixins/OxyServices.privacy.ts +0 -219
- package/src/mixins/OxyServices.reputation.ts +0 -436
- package/src/mixins/OxyServices.security.ts +0 -110
- package/src/mixins/OxyServices.store.ts +0 -585
- package/src/mixins/OxyServices.topics.ts +0 -169
- package/src/mixins/OxyServices.user.ts +0 -1208
- package/src/mixins/OxyServices.utility.ts +0 -1803
- package/src/mixins/__tests__/OxyServices.civic.test.ts +0 -1095
- package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +0 -124
- package/src/mixins/__tests__/OxyServices.identity.test.ts +0 -301
- package/src/mixins/__tests__/OxyServices.nodes.test.ts +0 -341
- package/src/mixins/__tests__/OxyServices.rotateKey.test.ts +0 -323
- package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +0 -192
- package/src/mixins/__tests__/OxyServices.serviceAssetMetadataBySha.test.ts +0 -135
- package/src/mixins/__tests__/OxyServices.serviceLinkedDownloadUrl.test.ts +0 -139
- package/src/mixins/__tests__/accountEvents.test.ts +0 -158
- package/src/mixins/__tests__/accounts.test.ts +0 -727
- package/src/mixins/__tests__/agency.test.ts +0 -120
- package/src/mixins/__tests__/appData.test.ts +0 -203
- package/src/mixins/__tests__/assetCredentials.test.ts +0 -47
- package/src/mixins/__tests__/assetUpload.test.ts +0 -191
- package/src/mixins/__tests__/authRefusalObservability.test.ts +0 -396
- package/src/mixins/__tests__/backgroundCredential.httpIntegration.test.ts +0 -130
- package/src/mixins/__tests__/chains.test.ts +0 -113
- package/src/mixins/__tests__/commonsSignIn.test.ts +0 -1147
- package/src/mixins/__tests__/connectedApps.test.ts +0 -146
- package/src/mixins/__tests__/deviceTokenMintPinned.test.ts +0 -113
- package/src/mixins/__tests__/discoveryErrorHandling.test.ts +0 -265
- package/src/mixins/__tests__/followCacheInvalidation.test.ts +0 -173
- package/src/mixins/__tests__/followGraph.test.ts +0 -147
- package/src/mixins/__tests__/followGraphPagination.test.ts +0 -271
- package/src/mixins/__tests__/getFileDownloadUrl.test.ts +0 -346
- package/src/mixins/__tests__/getFollowStatuses.test.ts +0 -95
- package/src/mixins/__tests__/getUsersByIds.test.ts +0 -176
- package/src/mixins/__tests__/identityBackup.test.ts +0 -315
- package/src/mixins/__tests__/identityLink.test.ts +0 -89
- package/src/mixins/__tests__/identityWriteCacheInvalidation.test.ts +0 -407
- package/src/mixins/__tests__/inferenceFactory.test.ts +0 -58
- package/src/mixins/__tests__/linkedAccounts.test.ts +0 -109
- package/src/mixins/__tests__/notifications.read.test.ts +0 -82
- package/src/mixins/__tests__/onTokensChanged.test.ts +0 -129
- package/src/mixins/__tests__/preSessionSkipAuth.test.ts +0 -284
- package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +0 -155
- package/src/mixins/__tests__/reputation.test.ts +0 -522
- package/src/mixins/__tests__/serviceAuth.test.ts +0 -889
- package/src/mixins/__tests__/serviceTokenJwks.test.ts +0 -208
- package/src/mixins/__tests__/store.test.ts +0 -304
- package/src/mixins/__tests__/topics.test.ts +0 -155
- package/src/mixins/__tests__/userReadCacheBypass.test.ts +0 -129
- package/src/mixins/__tests__/userTokenAuth.test.ts +0 -746
- package/src/mixins/__tests__/verifyChallenge.test.ts +0 -161
- package/src/mixins/__tests__/verifyServiceActingAs.test.ts +0 -167
- package/src/mixins/__tests__/webauthnAuth.test.ts +0 -279
- package/src/mixins/index.ts +0 -203
- package/src/mixins/mixinHelpers.ts +0 -69
- package/src/server/__tests__/auth.test.ts +0 -78
- package/src/server/__tests__/capabilityTicket.test.ts +0 -111
- package/src/server/__tests__/cors.socket.test.ts +0 -225
- package/src/server/__tests__/cors.test.ts +0 -158
- package/src/server/__tests__/rateLimit.test.ts +0 -257
- package/src/server/__tests__/requesterAssertion.test.ts +0 -359
- package/src/server/__tests__/safeFetch.test.ts +0 -232
- package/src/server/__tests__/securityHeaders.test.ts +0 -387
- package/src/server/__tests__/serviceTokenAttribution.test.ts +0 -396
- package/src/server/__tests__/traffic.test.ts +0 -180
- package/src/server/__tests__/trafficNodeHttp.test.ts +0 -38
- package/src/server/__tests__/userInvalidation.test.ts +0 -203
- package/src/server/__tests__/verifySecret.test.ts +0 -40
- package/src/server/__tests__/workloadIdentity.client.test.ts +0 -16
- package/src/server/__tests__/workloadIdentity.test.ts +0 -128
- package/src/session/__tests__/SessionClient.additive.test.ts +0 -153
- package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +0 -114
- package/src/session/__tests__/SessionClient.diagnostics.test.ts +0 -91
- package/src/session/__tests__/SessionClient.directory.test.ts +0 -688
- package/src/session/__tests__/SessionClient.httpIntegration.test.ts +0 -65
- package/src/session/__tests__/SessionClient.identityPin.test.ts +0 -175
- package/src/session/__tests__/SessionClient.rest.test.ts +0 -119
- package/src/session/__tests__/SessionClient.serverEvents.test.ts +0 -72
- package/src/session/__tests__/SessionClient.socket.test.ts +0 -202
- package/src/session/__tests__/SessionClient.socketFactory.test.ts +0 -81
- package/src/session/__tests__/SessionClient.state.test.ts +0 -78
- package/src/session/__tests__/SessionClient.switchTokenOrder.test.ts +0 -170
- package/src/session/__tests__/accountDelegationAccess.test.ts +0 -104
- package/src/session/__tests__/accountDialogController.test.ts +0 -2502
- package/src/session/__tests__/accountDialogShape.test.ts +0 -120
- package/src/session/__tests__/accountSwitchTargets.test.ts +0 -137
- package/src/session/__tests__/authStateStore.test.ts +0 -451
- package/src/session/__tests__/deviceDirectory.test.ts +0 -422
- package/src/session/__tests__/deviceSwitcherRows.test.ts +0 -223
- package/src/session/__tests__/identityPin.test.ts +0 -131
- package/src/session/__tests__/identitySession.test.ts +0 -194
- package/src/session/__tests__/projectSessionState.test.ts +0 -105
- package/src/session/__tests__/refresh.identityPin.test.ts +0 -307
- package/src/session/__tests__/refresh.test.ts +0 -606
- package/src/session/__tests__/sessionIntegration.test.ts +0 -209
- package/src/session/__tests__/sharedDeviceCredential.test.ts +0 -300
- package/src/utils/__tests__/accountUtils.test.ts +0 -151
- package/src/utils/__tests__/activityId.test.ts +0 -45
- package/src/utils/__tests__/asyncUtils.test.ts +0 -279
- package/src/utils/__tests__/authWebUrl.test.ts +0 -15
- package/src/utils/__tests__/cacheKey.test.ts +0 -0
- package/src/utils/__tests__/cacheLifecycle.test.ts +0 -65
- package/src/utils/__tests__/coldBoot.test.ts +0 -496
- package/src/utils/__tests__/commonsApproval.test.ts +0 -60
- package/src/utils/__tests__/identityCacheSweep.test.ts +0 -151
- package/src/utils/__tests__/languageUtils.test.ts +0 -244
- package/src/utils/__tests__/oauthPkce.test.ts +0 -303
- package/src/utils/__tests__/officialOrigins.test.ts +0 -31
- package/src/utils/__tests__/oxyServiceEnvironment.test.ts +0 -7
- package/src/utils/__tests__/profileLinks.test.ts +0 -180
- package/src/utils/__tests__/redactUrl.test.ts +0 -33
- package/src/utils/__tests__/registrableApex.test.ts +0 -62
- package/src/utils/__tests__/requestUtilsAbort.test.ts +0 -155
- package/src/utils/__tests__/textNormalization.test.ts +0 -205
- package/src/utils/__tests__/validationUtils.test.ts +0 -703
- package/src/utils/__tests__/webauthnOrigin.test.ts +0 -83
- package/src/utils/webauthnOrigin.ts +0 -52
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { DeviceSessionState } from '@oxy.so/contracts';
|
|
2
|
-
import { OxyServices } from '../../OxyServices';
|
|
3
|
-
import { SessionClient } from '../SessionClient';
|
|
4
|
-
import { createSessionClientHost } from '../sessionClientHost';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Real-stack integration test: a genuine `HttpService` (via `OxyServices`) →
|
|
8
|
-
* `createSessionClientHost` → `SessionClient`, with `global.fetch` stubbed to
|
|
9
|
-
* return the EXACT wire body the server sends for `GET /session/device/state`:
|
|
10
|
-
* `{ data: { state, activeToken } }`.
|
|
11
|
-
*
|
|
12
|
-
* This is the test that would have caught the P0: `HttpService.unwrapResponse`
|
|
13
|
-
* strips the outer `{ data }` envelope, so `makeRequest` already returns
|
|
14
|
-
* `{ state, activeToken }`. If `SessionClient` reads `.data` a SECOND time (or
|
|
15
|
-
* if `HttpService` stops unwrapping), the sync silently discards and neither
|
|
16
|
-
* the state nor the token reach the client — exactly the prod symptom.
|
|
17
|
-
*/
|
|
18
|
-
const WIRE_STATE: DeviceSessionState = {
|
|
19
|
-
deviceId: 'device-real',
|
|
20
|
-
accounts: [{ accountId: 'acct-1', sessionId: 'sess-1', authuser: 0 }],
|
|
21
|
-
activeAccountId: 'acct-1',
|
|
22
|
-
revision: 42,
|
|
23
|
-
updatedAt: 1720000000000,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const ROUTE_BODY = {
|
|
27
|
-
data: {
|
|
28
|
-
state: WIRE_STATE,
|
|
29
|
-
activeToken: { accessToken: 'planted-access-token', expiresAt: '2026-01-01T00:00:00.000Z' },
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
describe('SessionClient over a real HttpService (unwrap contract)', () => {
|
|
34
|
-
const originalFetch = global.fetch;
|
|
35
|
-
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
global.fetch = originalFetch;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('bootstrap() applies the server state and plants the active token through the real unwrap path', async () => {
|
|
41
|
-
const fetchMock = jest.fn(async () =>
|
|
42
|
-
new Response(JSON.stringify(ROUTE_BODY), {
|
|
43
|
-
status: 200,
|
|
44
|
-
headers: { 'content-type': 'application/json' },
|
|
45
|
-
}),
|
|
46
|
-
);
|
|
47
|
-
global.fetch = fetchMock as unknown as typeof fetch;
|
|
48
|
-
|
|
49
|
-
const oxy = new OxyServices({ baseURL: 'http://api.test.invalid' });
|
|
50
|
-
const host = createSessionClientHost(oxy);
|
|
51
|
-
const client = new SessionClient(host);
|
|
52
|
-
|
|
53
|
-
await client.bootstrap();
|
|
54
|
-
|
|
55
|
-
// The exact URL the server route serves.
|
|
56
|
-
const calledUrl = String((fetchMock.mock.calls[0] ?? [])[0]);
|
|
57
|
-
expect(calledUrl).toContain('/session/device/state');
|
|
58
|
-
|
|
59
|
-
// State reached the client (would be null if `.data` were read twice).
|
|
60
|
-
expect(client.getState()).toEqual(WIRE_STATE);
|
|
61
|
-
|
|
62
|
-
// Active token planted host-side (would be absent on a discarded sync).
|
|
63
|
-
expect(oxy.getAccessToken()).toBe('planted-access-token');
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `SessionClient` under an IDENTITY pin.
|
|
3
|
-
*
|
|
4
|
-
* The bug this guards: an account switch performed by ANOTHER app on the same
|
|
5
|
-
* device arrives as a `session_state` push (or as an `activeToken` on a sync
|
|
6
|
-
* response) and silently re-bound the identity vault's bearer to that account.
|
|
7
|
-
*
|
|
8
|
-
* The contract: device state is still tracked TRUTHFULLY (other apps' accounts
|
|
9
|
-
* and the real `activeAccountId` remain visible in `getState()`), but the bearer
|
|
10
|
-
* NEVER follows it. The pinned token's lifecycle belongs to the cold boot /
|
|
11
|
-
* re-mint lane, which mints it with an explicit `accountId`.
|
|
12
|
-
*/
|
|
13
|
-
import type { DeviceSessionState } from '@oxy.so/contracts';
|
|
14
|
-
import { SessionClient, type SessionClientHost, type TokenTransport } from '../SessionClient';
|
|
15
|
-
|
|
16
|
-
const PINNED = 'vault-user';
|
|
17
|
-
const OTHER = 'other-user';
|
|
18
|
-
|
|
19
|
-
/** A minimal jwt-decode-able token whose `userId` claim is `accountId`. */
|
|
20
|
-
function jwtFor(accountId: string): string {
|
|
21
|
-
const payload = Buffer.from(JSON.stringify({ userId: accountId })).toString('base64url');
|
|
22
|
-
return `h.${payload}.s`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const stateWith = (revision: number, active: string): DeviceSessionState => ({
|
|
26
|
-
deviceId: 'dev-1',
|
|
27
|
-
accounts: [
|
|
28
|
-
{ accountId: PINNED, sessionId: 'sess-vault', authuser: 0 },
|
|
29
|
-
{ accountId: OTHER, sessionId: 'sess-other', authuser: 1 },
|
|
30
|
-
],
|
|
31
|
-
activeAccountId: active,
|
|
32
|
-
revision,
|
|
33
|
-
updatedAt: 1_720_000_000_000,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
function makeHost(initialToken: string | null): SessionClientHost & { planted: () => string | null } {
|
|
37
|
-
let planted = initialToken;
|
|
38
|
-
return {
|
|
39
|
-
makeRequest: jest.fn(),
|
|
40
|
-
getBaseURL: () => 'http://test.invalid',
|
|
41
|
-
getAccessToken: () => planted,
|
|
42
|
-
getDeviceCredential: () => null,
|
|
43
|
-
onTokensChanged: () => () => undefined,
|
|
44
|
-
setTokens: (token: string) => {
|
|
45
|
-
planted = token;
|
|
46
|
-
},
|
|
47
|
-
getCurrentAccountId: () => PINNED,
|
|
48
|
-
planted: () => planted,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** `applyState` is protected; a tiny subclass exposes it for the unit tests. */
|
|
53
|
-
class TestClient extends SessionClient {
|
|
54
|
-
public apply(raw: unknown, origin?: 'request' | 'push', activeToken?: string): boolean {
|
|
55
|
-
return this.applyState(raw, origin, activeToken);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
describe('SessionClient — pinned bearer', () => {
|
|
60
|
-
// Node's real `BroadcastChannel` (which `postCommitPing` opens) is ref'd and
|
|
61
|
-
// would keep the Jest event loop alive forever. Feature-detect it away — the
|
|
62
|
-
// cross-tab wake has its own suite.
|
|
63
|
-
const realBroadcastChannel = (globalThis as { BroadcastChannel?: unknown }).BroadcastChannel;
|
|
64
|
-
beforeAll(() => {
|
|
65
|
-
(globalThis as { BroadcastChannel?: unknown }).BroadcastChannel = undefined;
|
|
66
|
-
});
|
|
67
|
-
afterAll(() => {
|
|
68
|
-
(globalThis as { BroadcastChannel?: unknown }).BroadcastChannel = realBroadcastChannel;
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('tracks a pushed switch in state but does NOT change the planted token', () => {
|
|
72
|
-
const host = makeHost(jwtFor(PINNED));
|
|
73
|
-
const client = new TestClient(host, { getPinnedAccountId: () => PINNED });
|
|
74
|
-
|
|
75
|
-
expect(client.apply(stateWith(1, PINNED), 'push')).toBe(true);
|
|
76
|
-
// Another app switched the device to `other-user`.
|
|
77
|
-
expect(client.apply(stateWith(2, OTHER), 'push')).toBe(true);
|
|
78
|
-
|
|
79
|
-
// The state is truthful...
|
|
80
|
-
expect(client.getState()?.activeAccountId).toBe(OTHER);
|
|
81
|
-
// ...and the bearer is untouched.
|
|
82
|
-
expect(host.planted()).toBe(jwtFor(PINNED));
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('does NOT plant a sync-supplied activeToken for a non-pinned account', () => {
|
|
86
|
-
const host = makeHost(jwtFor(PINNED));
|
|
87
|
-
const client = new TestClient(host, { getPinnedAccountId: () => PINNED });
|
|
88
|
-
|
|
89
|
-
client.apply(stateWith(3, OTHER), 'request', jwtFor(OTHER));
|
|
90
|
-
|
|
91
|
-
expect(host.planted()).toBe(jwtFor(PINNED));
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('DOES plant a sync-supplied activeToken when the active account IS the pinned one', () => {
|
|
95
|
-
const host = makeHost(null);
|
|
96
|
-
const client = new TestClient(host, { getPinnedAccountId: () => PINNED });
|
|
97
|
-
|
|
98
|
-
client.apply(stateWith(4, PINNED), 'request', jwtFor(PINNED));
|
|
99
|
-
|
|
100
|
-
expect(host.planted()).toBe(jwtFor(PINNED));
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('never invokes the TokenTransport (whose job is converging on activeAccountId)', () => {
|
|
104
|
-
const host = makeHost(jwtFor(PINNED));
|
|
105
|
-
const transport: TokenTransport = { ensureActiveToken: jest.fn().mockResolvedValue(undefined) };
|
|
106
|
-
const client = new TestClient(host, { transport, getPinnedAccountId: () => PINNED });
|
|
107
|
-
|
|
108
|
-
client.apply(stateWith(5, OTHER), 'push');
|
|
109
|
-
|
|
110
|
-
expect(transport.ensureActiveToken).not.toHaveBeenCalled();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('notifies subscribers synchronously on a foreign switch (no mint-before-notify gate)', () => {
|
|
114
|
-
const host = makeHost(jwtFor(PINNED));
|
|
115
|
-
const transport: TokenTransport = { ensureActiveToken: jest.fn().mockResolvedValue(undefined) };
|
|
116
|
-
const client = new TestClient(host, { transport, getPinnedAccountId: () => PINNED });
|
|
117
|
-
|
|
118
|
-
const seen: Array<string | null> = [];
|
|
119
|
-
client.subscribe((state) => seen.push(state?.activeAccountId ?? null));
|
|
120
|
-
|
|
121
|
-
client.apply(stateWith(6, OTHER), 'push');
|
|
122
|
-
|
|
123
|
-
expect(seen).toEqual([OTHER]);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('account mode is unchanged: an activeToken for the new active account IS planted', () => {
|
|
127
|
-
const host = makeHost(jwtFor(PINNED));
|
|
128
|
-
const client = new TestClient(host);
|
|
129
|
-
|
|
130
|
-
client.apply(stateWith(7, OTHER), 'request', jwtFor(OTHER));
|
|
131
|
-
|
|
132
|
-
expect(host.planted()).toBe(jwtFor(OTHER));
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('registerAndActivate only ADDS while pinned (never re-elects the device active account)', async () => {
|
|
136
|
-
const host = makeHost(jwtFor(PINNED));
|
|
137
|
-
const makeRequest = jest.fn().mockResolvedValue({
|
|
138
|
-
state: stateWith(8, OTHER),
|
|
139
|
-
activeToken: null,
|
|
140
|
-
});
|
|
141
|
-
const client = new SessionClient(
|
|
142
|
-
{ ...host, makeRequest },
|
|
143
|
-
{ getPinnedAccountId: () => PINNED },
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
await client.registerAndActivate(PINNED);
|
|
147
|
-
|
|
148
|
-
expect(makeRequest).toHaveBeenCalledTimes(1);
|
|
149
|
-
expect(makeRequest).toHaveBeenCalledWith('POST', '/session/device/add', undefined, { cache: false });
|
|
150
|
-
expect(makeRequest).not.toHaveBeenCalledWith(
|
|
151
|
-
'POST',
|
|
152
|
-
'/session/device/switch',
|
|
153
|
-
expect.anything(),
|
|
154
|
-
expect.anything(),
|
|
155
|
-
);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('account mode is unchanged: registerAndActivate still switches to the target', async () => {
|
|
159
|
-
const host = makeHost(jwtFor(PINNED));
|
|
160
|
-
const makeRequest = jest.fn().mockResolvedValue({
|
|
161
|
-
state: stateWith(9, OTHER),
|
|
162
|
-
activeToken: null,
|
|
163
|
-
});
|
|
164
|
-
const client = new SessionClient({ ...host, makeRequest });
|
|
165
|
-
|
|
166
|
-
await client.registerAndActivate(PINNED);
|
|
167
|
-
|
|
168
|
-
expect(makeRequest).toHaveBeenCalledWith(
|
|
169
|
-
'POST',
|
|
170
|
-
'/session/device/switch',
|
|
171
|
-
{ accountId: PINNED },
|
|
172
|
-
{ cache: false },
|
|
173
|
-
);
|
|
174
|
-
});
|
|
175
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import type { DeviceSessionState } from '@oxy.so/contracts';
|
|
2
|
-
import { SessionClient, type SessionClientHost } from '../SessionClient';
|
|
3
|
-
|
|
4
|
-
const STATE = (rev: number): DeviceSessionState => ({
|
|
5
|
-
deviceId: 'd1', accounts: [{ accountId: 'a1', sessionId: 's1', authuser: 0 }], activeAccountId: 'a1', revision: rev, updatedAt: 1720000000000,
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
function makeHost(makeRequest: jest.Mock): SessionClientHost {
|
|
9
|
-
return {
|
|
10
|
-
makeRequest,
|
|
11
|
-
getBaseURL: () => 'http://test.invalid',
|
|
12
|
-
getAccessToken: () => 't',
|
|
13
|
-
getDeviceCredential: () => null,
|
|
14
|
-
onTokensChanged: () => () => undefined,
|
|
15
|
-
setTokens: jest.fn(),
|
|
16
|
-
getCurrentAccountId: () => null,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// `makeRequest` (HttpService) already strips the server's outer `{ data }` envelope, so it
|
|
21
|
-
// returns the unwrapped sync body directly — that is exactly what SessionClient consumes.
|
|
22
|
-
const SYNC = (rev: number) => ({ state: STATE(rev), activeToken: { accessToken: `jwt-${rev}`, expiresAt: 'x' } });
|
|
23
|
-
|
|
24
|
-
describe('SessionClient REST', () => {
|
|
25
|
-
it('adopts state already returned by a mint without another request', () => {
|
|
26
|
-
const makeRequest = jest.fn();
|
|
27
|
-
const c = new SessionClient(makeHost(makeRequest));
|
|
28
|
-
|
|
29
|
-
expect(c.adoptState(STATE(2))).toBe(true);
|
|
30
|
-
expect(c.getState()?.revision).toBe(2);
|
|
31
|
-
expect(makeRequest).not.toHaveBeenCalled();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('bootstrap GETs /session/device/state and applies it', async () => {
|
|
35
|
-
const makeRequest = jest.fn().mockResolvedValueOnce(SYNC(3));
|
|
36
|
-
const host = makeHost(makeRequest);
|
|
37
|
-
const c = new SessionClient(host);
|
|
38
|
-
await c.bootstrap();
|
|
39
|
-
expect(makeRequest).toHaveBeenCalledWith('GET', '/session/device/state', undefined, { cache: false });
|
|
40
|
-
expect(c.getState()?.revision).toBe(3);
|
|
41
|
-
expect(host.setTokens).toHaveBeenCalledWith('jwt-3');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('switchAccount POSTs and applies the returned state', async () => {
|
|
45
|
-
const makeRequest = jest.fn().mockResolvedValueOnce(SYNC(4));
|
|
46
|
-
const host = makeHost(makeRequest);
|
|
47
|
-
const c = new SessionClient(host);
|
|
48
|
-
await c.switchAccount('a1');
|
|
49
|
-
expect(makeRequest).toHaveBeenCalledWith('POST', '/session/device/switch', { accountId: 'a1' }, { cache: false });
|
|
50
|
-
expect(c.getState()?.revision).toBe(4);
|
|
51
|
-
expect(host.setTokens).toHaveBeenCalledWith('jwt-4');
|
|
52
|
-
c.stop();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('signOut one account POSTs { accountId }', async () => {
|
|
56
|
-
const makeRequest = jest.fn().mockResolvedValueOnce(SYNC(5));
|
|
57
|
-
const c = new SessionClient(makeHost(makeRequest));
|
|
58
|
-
await c.signOut({ accountId: 'a1' });
|
|
59
|
-
expect(makeRequest).toHaveBeenCalledWith('POST', '/session/device/signout', { accountId: 'a1' }, { cache: false });
|
|
60
|
-
c.stop();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('signOut all POSTs { all: true }', async () => {
|
|
64
|
-
const makeRequest = jest.fn().mockResolvedValueOnce(SYNC(6));
|
|
65
|
-
const c = new SessionClient(makeHost(makeRequest));
|
|
66
|
-
await c.signOut({ all: true });
|
|
67
|
-
expect(makeRequest).toHaveBeenCalledWith('POST', '/session/device/signout', { all: true }, { cache: false });
|
|
68
|
-
c.stop();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('addCurrentAccount POSTs /session/device/add with no body', async () => {
|
|
72
|
-
const makeRequest = jest.fn().mockResolvedValueOnce(SYNC(2));
|
|
73
|
-
const c = new SessionClient(makeHost(makeRequest));
|
|
74
|
-
await c.addCurrentAccount();
|
|
75
|
-
expect(makeRequest).toHaveBeenCalledWith('POST', '/session/device/add', undefined, { cache: false });
|
|
76
|
-
c.stop();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('does not throw / does not apply when the server returns invalid state', async () => {
|
|
80
|
-
const makeRequest = jest.fn().mockResolvedValueOnce({ bogus: true });
|
|
81
|
-
const c = new SessionClient(makeHost(makeRequest));
|
|
82
|
-
await c.bootstrap();
|
|
83
|
-
expect(c.getState()).toBeNull();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('applies state but does not plant a token when activeToken is null', async () => {
|
|
87
|
-
const makeRequest = jest.fn().mockResolvedValueOnce({ state: STATE(7), activeToken: null });
|
|
88
|
-
const host = makeHost(makeRequest);
|
|
89
|
-
const c = new SessionClient(host);
|
|
90
|
-
await c.bootstrap();
|
|
91
|
-
expect(c.getState()?.revision).toBe(7);
|
|
92
|
-
expect(host.setTokens).not.toHaveBeenCalled();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('accepts a NEW device\'s lower-revision state and plants its token (cross-device revision reset)', async () => {
|
|
96
|
-
const deviceA: DeviceSessionState = {
|
|
97
|
-
deviceId: 'A', accounts: [{ accountId: 'a1', sessionId: 's1', authuser: 0 }], activeAccountId: 'a1', revision: 10, updatedAt: 1720000000000,
|
|
98
|
-
};
|
|
99
|
-
const deviceB: DeviceSessionState = {
|
|
100
|
-
deviceId: 'B', accounts: [{ accountId: 'a2', sessionId: 's2', authuser: 0 }], activeAccountId: 'a2', revision: 1, updatedAt: 1720000001000,
|
|
101
|
-
};
|
|
102
|
-
const makeRequest = jest.fn()
|
|
103
|
-
.mockResolvedValueOnce({ state: deviceA, activeToken: { accessToken: 'jwt-A', expiresAt: 'x' } })
|
|
104
|
-
.mockResolvedValueOnce({ state: deviceB, activeToken: { accessToken: 'jwt-B', expiresAt: 'x' } });
|
|
105
|
-
const host = makeHost(makeRequest);
|
|
106
|
-
const c = new SessionClient(host);
|
|
107
|
-
|
|
108
|
-
await c.bootstrap();
|
|
109
|
-
expect(c.getState()?.deviceId).toBe('A');
|
|
110
|
-
expect(host.setTokens).toHaveBeenLastCalledWith('jwt-A');
|
|
111
|
-
|
|
112
|
-
// Device B is freshly converged (revision 1) — it must win over device A's
|
|
113
|
-
// stale-but-higher revision, and its token must be planted.
|
|
114
|
-
await c.bootstrap();
|
|
115
|
-
expect(c.getState()?.deviceId).toBe('B');
|
|
116
|
-
expect(c.getState()?.revision).toBe(1);
|
|
117
|
-
expect(host.setTokens).toHaveBeenLastCalledWith('jwt-B');
|
|
118
|
-
});
|
|
119
|
-
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { DeviceSessionState } from '@oxy.so/contracts';
|
|
2
|
-
import type { MinimalSocket, SocketIOFactory } from '../socketLoader';
|
|
3
|
-
import { SessionClient, type SessionClientHost } from '../SessionClient';
|
|
4
|
-
|
|
5
|
-
type Handler = (...args: unknown[]) => void;
|
|
6
|
-
class FakeSocket implements MinimalSocket {
|
|
7
|
-
connected = false;
|
|
8
|
-
handlers = new Map<string, Handler[]>();
|
|
9
|
-
on(event: string, cb: Handler) { const l = this.handlers.get(event) ?? []; l.push(cb); this.handlers.set(event, l); }
|
|
10
|
-
off(event: string, cb?: Handler) { if (!cb) { this.handlers.delete(event); return; } this.handlers.set(event, (this.handlers.get(event) ?? []).filter((h) => h !== cb)); }
|
|
11
|
-
emit(_event: string, ..._args: unknown[]) { /* client→server emit, unused here */ }
|
|
12
|
-
connect() { this.connected = true; }
|
|
13
|
-
disconnect() { this.connected = false; }
|
|
14
|
-
emitServer(event: string, payload: unknown) { for (const h of this.handlers.get(event) ?? []) h(payload); }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const STATE = (rev: number): DeviceSessionState => ({ deviceId: 'd1', accounts: [{ accountId: 'a1', sessionId: 's1', authuser: 0 }], activeAccountId: 'a1', revision: rev, updatedAt: 1720000000000 });
|
|
18
|
-
const SYNC = (rev: number) => ({ state: STATE(rev), activeToken: { accessToken: `jwt-${rev}`, expiresAt: 'x' } });
|
|
19
|
-
|
|
20
|
-
function makeHost(over: Partial<SessionClientHost> = {}): SessionClientHost {
|
|
21
|
-
return {
|
|
22
|
-
makeRequest: jest.fn().mockResolvedValue(SYNC(1)),
|
|
23
|
-
getBaseURL: () => 'http://test.invalid',
|
|
24
|
-
getAccessToken: () => 'tok',
|
|
25
|
-
getDeviceCredential: () => null,
|
|
26
|
-
onTokensChanged: () => () => undefined,
|
|
27
|
-
setTokens: jest.fn(),
|
|
28
|
-
getCurrentAccountId: () => 'a1',
|
|
29
|
-
...over,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('SessionClient.onServerEvent', () => {
|
|
34
|
-
it('delivers a server event to a listener registered BEFORE the socket exists', async () => {
|
|
35
|
-
let created: FakeSocket | null = null;
|
|
36
|
-
const factory: SocketIOFactory = jest.fn(() => { created = new FakeSocket(); created.connected = true; return created; });
|
|
37
|
-
const client = new SessionClient(makeHost(), { socketFactory: factory });
|
|
38
|
-
const seen: unknown[] = [];
|
|
39
|
-
client.onServerEvent('civic:attested', (p) => seen.push(p));
|
|
40
|
-
await client.start();
|
|
41
|
-
created?.emitServer('civic:attested', { byUserId: 'u2' });
|
|
42
|
-
expect(seen).toEqual([{ byUserId: 'u2' }]);
|
|
43
|
-
client.stop();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('delivers to a listener registered AFTER the socket exists, and unsubscribe stops delivery', async () => {
|
|
47
|
-
let created: FakeSocket | null = null;
|
|
48
|
-
const factory: SocketIOFactory = jest.fn(() => { created = new FakeSocket(); created.connected = true; return created; });
|
|
49
|
-
const client = new SessionClient(makeHost(), { socketFactory: factory });
|
|
50
|
-
await client.start();
|
|
51
|
-
const seen: unknown[] = [];
|
|
52
|
-
const unsub = client.onServerEvent('civic:attested', (p) => seen.push(p));
|
|
53
|
-
created?.emitServer('civic:attested', 1);
|
|
54
|
-
unsub();
|
|
55
|
-
created?.emitServer('civic:attested', 2);
|
|
56
|
-
expect(seen).toEqual([1]);
|
|
57
|
-
client.stop();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('one listener throwing does not break the others', async () => {
|
|
61
|
-
let created: FakeSocket | null = null;
|
|
62
|
-
const factory: SocketIOFactory = jest.fn(() => { created = new FakeSocket(); created.connected = true; return created; });
|
|
63
|
-
const client = new SessionClient(makeHost(), { socketFactory: factory });
|
|
64
|
-
await client.start();
|
|
65
|
-
const seen: unknown[] = [];
|
|
66
|
-
client.onServerEvent('civic:attested', () => { throw new Error('boom'); });
|
|
67
|
-
client.onServerEvent('civic:attested', (p) => seen.push(p));
|
|
68
|
-
created?.emitServer('civic:attested', 'ok');
|
|
69
|
-
expect(seen).toEqual(['ok']);
|
|
70
|
-
client.stop();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import type { DeviceSessionState } from '@oxy.so/contracts';
|
|
2
|
-
|
|
3
|
-
type Handler = (...args: unknown[]) => void;
|
|
4
|
-
class FakeSocket {
|
|
5
|
-
connected = false;
|
|
6
|
-
handlers = new Map<string, Handler[]>();
|
|
7
|
-
on(event: string, cb: Handler) { const l = this.handlers.get(event) ?? []; l.push(cb); this.handlers.set(event, l); }
|
|
8
|
-
off(event: string, cb?: Handler) { if (!cb) { this.handlers.delete(event); return; } this.handlers.set(event, (this.handlers.get(event) ?? []).filter((h) => h !== cb)); }
|
|
9
|
-
connect() { this.connected = true; this.trigger('connect'); }
|
|
10
|
-
disconnect() { this.connected = false; }
|
|
11
|
-
trigger(event: string, ...args: unknown[]) { for (const h of this.handlers.get(event) ?? []) h(...args); }
|
|
12
|
-
}
|
|
13
|
-
let fakeSocket: FakeSocket;
|
|
14
|
-
const ioMock = jest.fn((_uri: string, opts?: Record<string, unknown>) => {
|
|
15
|
-
// honor autoConnect like real socket.io (connect immediately unless autoConnect:false)
|
|
16
|
-
if (!opts || opts.autoConnect !== false) fakeSocket.connected = true;
|
|
17
|
-
return fakeSocket;
|
|
18
|
-
});
|
|
19
|
-
jest.mock('socket.io-client', () => ({ __esModule: true, io: (...args: unknown[]) => ioMock(...(args as [string, Record<string, unknown>?])) }));
|
|
20
|
-
|
|
21
|
-
import { SessionClient, type SessionClientHost } from '../SessionClient';
|
|
22
|
-
|
|
23
|
-
const STATE = (rev: number): DeviceSessionState => ({ deviceId: 'd1', accounts: [{ accountId: 'a1', sessionId: 's1', authuser: 0 }], activeAccountId: 'a1', revision: rev, updatedAt: 1720000000000 });
|
|
24
|
-
// `makeRequest` (HttpService) already strips the server's outer `{ data }` envelope, so it
|
|
25
|
-
// returns the unwrapped sync body directly — that is exactly what SessionClient consumes.
|
|
26
|
-
const SYNC = (rev: number) => ({ state: STATE(rev), activeToken: { accessToken: `jwt-${rev}`, expiresAt: 'x' } });
|
|
27
|
-
|
|
28
|
-
function makeHost(over: Partial<SessionClientHost> = {}): SessionClientHost {
|
|
29
|
-
return {
|
|
30
|
-
makeRequest: jest.fn().mockResolvedValue(SYNC(1)),
|
|
31
|
-
getBaseURL: () => 'http://test.invalid',
|
|
32
|
-
getAccessToken: () => 'tok',
|
|
33
|
-
getDeviceCredential: () => null,
|
|
34
|
-
onTokensChanged: () => () => undefined,
|
|
35
|
-
setTokens: jest.fn(),
|
|
36
|
-
getCurrentAccountId: () => 'a1',
|
|
37
|
-
...over,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
beforeEach(() => { fakeSocket = new FakeSocket(); ioMock.mockClear(); });
|
|
42
|
-
|
|
43
|
-
describe('SessionClient socket', () => {
|
|
44
|
-
it('start() bootstraps then opens ONE socket to the base URL with a token-in-handshake auth callback', async () => {
|
|
45
|
-
const host = makeHost();
|
|
46
|
-
const c = new SessionClient(host);
|
|
47
|
-
await c.start();
|
|
48
|
-
expect(host.makeRequest).toHaveBeenCalledWith('GET', '/session/device/state', undefined, { cache: false });
|
|
49
|
-
expect(ioMock).toHaveBeenCalledTimes(1);
|
|
50
|
-
const [uri, opts] = ioMock.mock.calls[0];
|
|
51
|
-
expect(uri).toBe('http://test.invalid');
|
|
52
|
-
const authCb = jest.fn();
|
|
53
|
-
(opts?.auth as (cb: (d: { token: string }) => void) => void)(authCb);
|
|
54
|
-
expect(authCb).toHaveBeenCalledWith({ token: 'tok' });
|
|
55
|
-
c.stop();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('applies a pushed session_state event', async () => {
|
|
59
|
-
const c = new SessionClient(makeHost());
|
|
60
|
-
await c.start();
|
|
61
|
-
fakeSocket.trigger('session_state', STATE(9));
|
|
62
|
-
expect(c.getState()?.revision).toBe(9);
|
|
63
|
-
c.stop();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('fetches the active token via bootstrap when a pushed state changes the active account', async () => {
|
|
67
|
-
const makeRequest = jest.fn().mockResolvedValue(SYNC(1));
|
|
68
|
-
const host = makeHost({ makeRequest, getCurrentAccountId: () => 'other-account' });
|
|
69
|
-
const c = new SessionClient(host);
|
|
70
|
-
await c.start();
|
|
71
|
-
makeRequest.mockClear();
|
|
72
|
-
fakeSocket.trigger('session_state', STATE(9));
|
|
73
|
-
await Promise.resolve();
|
|
74
|
-
expect(makeRequest).toHaveBeenCalledWith('GET', '/session/device/state', undefined, { cache: false });
|
|
75
|
-
c.stop();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('C1 regression: plants the active token on a socket-pushed switch even when the post-push bootstrap returns the SAME revision as the push', async () => {
|
|
79
|
-
const setTokens = jest.fn();
|
|
80
|
-
const makeRequest = jest
|
|
81
|
-
.fn()
|
|
82
|
-
.mockResolvedValueOnce(SYNC(1)) // initial bootstrap in start()
|
|
83
|
-
.mockResolvedValue(SYNC(9)); // post-push bootstrap: same revision as the socket push below
|
|
84
|
-
const host = makeHost({ makeRequest, setTokens, getCurrentAccountId: () => 'other-account' });
|
|
85
|
-
const c = new SessionClient(host);
|
|
86
|
-
await c.start();
|
|
87
|
-
makeRequest.mockClear();
|
|
88
|
-
setTokens.mockClear();
|
|
89
|
-
fakeSocket.trigger('session_state', STATE(9));
|
|
90
|
-
await Promise.resolve();
|
|
91
|
-
await Promise.resolve();
|
|
92
|
-
expect(makeRequest).toHaveBeenCalledWith('GET', '/session/device/state', undefined, { cache: false });
|
|
93
|
-
expect(setTokens).toHaveBeenCalledWith('jwt-9');
|
|
94
|
-
c.stop();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('does not re-fetch when the pushed active account matches the host-held account', async () => {
|
|
98
|
-
const makeRequest = jest.fn().mockResolvedValue(SYNC(1));
|
|
99
|
-
const host = makeHost({ makeRequest, getCurrentAccountId: () => 'a1' });
|
|
100
|
-
const c = new SessionClient(host);
|
|
101
|
-
await c.start();
|
|
102
|
-
makeRequest.mockClear();
|
|
103
|
-
fakeSocket.trigger('session_state', STATE(9));
|
|
104
|
-
await Promise.resolve();
|
|
105
|
-
expect(makeRequest).not.toHaveBeenCalled();
|
|
106
|
-
c.stop();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('does not open a socket at all when signed out (bearer-only)', async () => {
|
|
110
|
-
const c = new SessionClient(makeHost({ getAccessToken: () => null }));
|
|
111
|
-
await c.start();
|
|
112
|
-
expect(ioMock).not.toHaveBeenCalled();
|
|
113
|
-
c.stop();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('reconnects an existing socket when a fresh token arrives after a transient drop', async () => {
|
|
117
|
-
const listeners: Array<(t: string | null) => void> = [];
|
|
118
|
-
const host = makeHost({ onTokensChanged: (l) => { listeners.push(l); return () => undefined; } });
|
|
119
|
-
const c = new SessionClient(host);
|
|
120
|
-
await c.start();
|
|
121
|
-
expect(fakeSocket.connected).toBe(true); // authenticated connect on start
|
|
122
|
-
fakeSocket.connected = false; // simulate a transient socket drop
|
|
123
|
-
listeners.forEach((l) => l('fresh-token'));
|
|
124
|
-
expect(fakeSocket.connected).toBe(true);
|
|
125
|
-
c.stop();
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('stop() disconnects the socket', async () => {
|
|
129
|
-
const c = new SessionClient(makeHost());
|
|
130
|
-
await c.start();
|
|
131
|
-
c.stop();
|
|
132
|
-
expect(fakeSocket.connected).toBe(false);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('session_accounts_changed for the current user refetches device state (GET /session/device/state)', async () => {
|
|
136
|
-
const makeRequest = jest.fn().mockResolvedValue(SYNC(1));
|
|
137
|
-
const host = makeHost({ makeRequest, getCurrentAccountId: () => 'a1' });
|
|
138
|
-
const c = new SessionClient(host);
|
|
139
|
-
await c.start();
|
|
140
|
-
makeRequest.mockClear();
|
|
141
|
-
fakeSocket.trigger('session_accounts_changed', { userId: 'a1', revision: 5, reason: 'add' });
|
|
142
|
-
await Promise.resolve();
|
|
143
|
-
expect(makeRequest).toHaveBeenCalledWith('GET', '/session/device/state', undefined, { cache: false });
|
|
144
|
-
c.stop();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('session_accounts_changed for a DIFFERENT user is ignored (no refetch)', async () => {
|
|
148
|
-
const makeRequest = jest.fn().mockResolvedValue(SYNC(1));
|
|
149
|
-
const host = makeHost({ makeRequest, getCurrentAccountId: () => 'a1' });
|
|
150
|
-
const c = new SessionClient(host);
|
|
151
|
-
await c.start();
|
|
152
|
-
makeRequest.mockClear();
|
|
153
|
-
fakeSocket.trigger('session_accounts_changed', { userId: 'someone-else', revision: 5, reason: 'switch' });
|
|
154
|
-
await Promise.resolve();
|
|
155
|
-
expect(makeRequest).not.toHaveBeenCalled();
|
|
156
|
-
c.stop();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('session_accounts_changed drops a malformed payload without refetching', async () => {
|
|
160
|
-
const makeRequest = jest.fn().mockResolvedValue(SYNC(1));
|
|
161
|
-
const host = makeHost({ makeRequest, getCurrentAccountId: () => 'a1' });
|
|
162
|
-
const c = new SessionClient(host);
|
|
163
|
-
await c.start();
|
|
164
|
-
makeRequest.mockClear();
|
|
165
|
-
fakeSocket.trigger('session_accounts_changed', { userId: 'a1', reason: 'not-a-real-reason' });
|
|
166
|
-
await Promise.resolve();
|
|
167
|
-
expect(makeRequest).not.toHaveBeenCalled();
|
|
168
|
-
c.stop();
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('a socket-pushed empty state fires onUnauthenticated with the PUSH origin (bug #4)', async () => {
|
|
172
|
-
const onUnauthenticated = jest.fn();
|
|
173
|
-
const c = new SessionClient(makeHost(), { onUnauthenticated });
|
|
174
|
-
await c.start();
|
|
175
|
-
|
|
176
|
-
const EMPTY: DeviceSessionState = { deviceId: 'd1', accounts: [], activeAccountId: null, revision: 9, updatedAt: 1720000000001 };
|
|
177
|
-
fakeSocket.trigger('session_state', EMPTY);
|
|
178
|
-
|
|
179
|
-
expect(onUnauthenticated).toHaveBeenCalledWith('push');
|
|
180
|
-
c.stop();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('a REST signOut-all empty response fires onUnauthenticated with the REQUEST origin', async () => {
|
|
184
|
-
const onUnauthenticated = jest.fn();
|
|
185
|
-
const EMPTY_SYNC = {
|
|
186
|
-
state: { deviceId: 'd1', accounts: [], activeAccountId: null, revision: 9, updatedAt: 1720000000001 },
|
|
187
|
-
activeToken: null,
|
|
188
|
-
};
|
|
189
|
-
// bootstrap during start() returns a populated state; the signout (and any
|
|
190
|
-
// stray reconcile) returns empty.
|
|
191
|
-
const makeRequest = jest.fn().mockResolvedValue(EMPTY_SYNC).mockResolvedValueOnce(SYNC(1));
|
|
192
|
-
const c = new SessionClient(makeHost({ makeRequest }), { onUnauthenticated });
|
|
193
|
-
await c.start();
|
|
194
|
-
|
|
195
|
-
await c.signOut({ all: true });
|
|
196
|
-
// Flush any fire-and-forget post-commit reconcile before asserting/teardown.
|
|
197
|
-
await Promise.resolve();
|
|
198
|
-
|
|
199
|
-
expect(onUnauthenticated).toHaveBeenCalledWith('request');
|
|
200
|
-
c.stop();
|
|
201
|
-
});
|
|
202
|
-
});
|