@oxy.so/core 1.8.0 → 1.9.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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/crypto/polyfill.js +21 -6
- package/dist/cjs/i18n/locales/en-US.json +3 -1
- package/dist/cjs/i18n/locales/es-ES.json +3 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +3 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +3 -1
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/session/accountDialogController.js +73 -4
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/crypto/polyfill.js +19 -4
- package/dist/esm/i18n/locales/en-US.json +3 -1
- package/dist/esm/i18n/locales/es-ES.json +3 -1
- package/dist/esm/i18n/locales/locales/en-US.json +3 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +3 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/session/accountDialogController.js +72 -4
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/crypto/polyfill.d.ts +17 -2
- package/dist/types/models/interfaces.d.ts +6 -1
- package/dist/types/session/accountDialogController.d.ts +50 -3
- package/package.json +2 -2
- package/src/crypto/__tests__/polyfill.test.ts +1 -1
- package/src/crypto/__tests__/polyfillOrder.test.ts +114 -0
- package/src/crypto/polyfill.ts +19 -4
- package/src/i18n/locales/en-US.json +3 -1
- package/src/i18n/locales/es-ES.json +3 -1
- package/src/index.ts +4 -1
- package/src/models/interfaces.ts +6 -1
- package/src/session/__tests__/accountDialogController.test.ts +105 -0
- package/src/session/__tests__/accountDialogShape.test.ts +1 -0
- package/src/session/accountDialogController.ts +87 -5
|
@@ -14,8 +14,23 @@
|
|
|
14
14
|
* 2. Node — backed by the built-in `node:crypto` module (`webcrypto`, else
|
|
15
15
|
* `randomFillSync`). This is what a Node runtime WITHOUT a global WebCrypto
|
|
16
16
|
* (Node 18 script entrypoints, some embedded hosts) falls back to.
|
|
17
|
-
* 3. React Native — `expo-crypto.getRandomBytes
|
|
18
|
-
*
|
|
17
|
+
* 3. React Native — `expo-crypto.getRandomBytes`, through the dependency-free
|
|
18
|
+
* `@oxy.so/protocol/random` entry.
|
|
19
|
+
*
|
|
20
|
+
* # Evaluation order — this module must import NOTHING that can capture the global
|
|
21
|
+
*
|
|
22
|
+
* `@noble/hashes` 1.x reads `globalThis.crypto` exactly once, when its
|
|
23
|
+
* `crypto.js` is evaluated, and `randomBytes` uses that captured binding for
|
|
24
|
+
* the lifetime of the app. ES imports are evaluated before the importing
|
|
25
|
+
* module's body, so every module this file imports runs BEFORE the shim below
|
|
26
|
+
* is installed. This file used to import `@oxy.so/protocol`'s root entry,
|
|
27
|
+
* which reaches `@noble/curves` → `@noble/hashes` through the envelope signer:
|
|
28
|
+
* on Hermes (no `globalThis.crypto` at startup) noble captured `undefined`
|
|
29
|
+
* before the shim existed, and every identity creation on Android failed with
|
|
30
|
+
* `crypto.getRandomValues must be defined`. Its imports are therefore limited
|
|
31
|
+
* to `buffer` and `@oxy.so/protocol/random`, both free of crypto libraries,
|
|
32
|
+
* and `index.ts` imports this file first. `__tests__/polyfillOrder.test.ts`
|
|
33
|
+
* guards both halves.
|
|
19
34
|
*
|
|
20
35
|
* Historically step (2) delegated to `@oxy.so/protocol`'s RN-only
|
|
21
36
|
* `getRandomBytesRN`, which THROWS on Node — so any Node host lacking a global
|
|
@@ -26,7 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
41
|
exports.Buffer = void 0;
|
|
27
42
|
const buffer_1 = require("buffer");
|
|
28
43
|
Object.defineProperty(exports, "Buffer", { enumerable: true, get: function () { return buffer_1.Buffer; } });
|
|
29
|
-
const
|
|
44
|
+
const random_1 = require("@oxy.so/protocol/random");
|
|
30
45
|
const getGlobalObject = () => {
|
|
31
46
|
if (typeof globalThis !== 'undefined')
|
|
32
47
|
return globalThis;
|
|
@@ -111,12 +126,12 @@ const cryptoPolyfill = {
|
|
|
111
126
|
// Node: back the CSPRNG with `node:crypto`. This is the path that matters on
|
|
112
127
|
// Node runtimes shipping WITHOUT a global WebCrypto — where delegating to
|
|
113
128
|
// the RN-only expo-crypto stub would throw.
|
|
114
|
-
if ((0,
|
|
129
|
+
if ((0, random_1.isNodeJS)() && fillFromNodeCrypto(array)) {
|
|
115
130
|
return array;
|
|
116
131
|
}
|
|
117
132
|
// React Native (and any non-Node host without WebCrypto): synchronous
|
|
118
|
-
// expo-crypto via @oxy.so/protocol's RN `platform/
|
|
119
|
-
const bytes = (0,
|
|
133
|
+
// expo-crypto via @oxy.so/protocol's RN `platform/random` variant.
|
|
134
|
+
const bytes = (0, random_1.getRandomBytesRN)(array.byteLength);
|
|
120
135
|
const uint8View = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
|
|
121
136
|
uint8View.set(bytes);
|
|
122
137
|
return array;
|
|
@@ -1850,6 +1850,7 @@
|
|
|
1850
1850
|
"switch": {
|
|
1851
1851
|
"title": "Switch account",
|
|
1852
1852
|
"count": "{{count}} accounts",
|
|
1853
|
+
"countOne": "{{count}} account",
|
|
1853
1854
|
"empty": "Accounts you own or share"
|
|
1854
1855
|
}
|
|
1855
1856
|
},
|
|
@@ -1970,7 +1971,8 @@
|
|
|
1970
1971
|
"loading": "Loading sessions…",
|
|
1971
1972
|
"empty": "No active sessions",
|
|
1972
1973
|
"signOutAllOnThisDevice": "Sign out of all other devices",
|
|
1973
|
-
"signOutAllSubtitle": "End {{count}} other device session(s)"
|
|
1974
|
+
"signOutAllSubtitle": "End {{count}} other device session(s)",
|
|
1975
|
+
"otherSession": "Another session"
|
|
1974
1976
|
},
|
|
1975
1977
|
"confirms": {
|
|
1976
1978
|
"removeDeviceTitle": "Remove device",
|
|
@@ -1850,6 +1850,7 @@
|
|
|
1850
1850
|
"switch": {
|
|
1851
1851
|
"title": "Cambiar de cuenta",
|
|
1852
1852
|
"count": "{{count}} cuentas",
|
|
1853
|
+
"countOne": "{{count}} cuenta",
|
|
1853
1854
|
"empty": "Cuentas que posees o compartes"
|
|
1854
1855
|
}
|
|
1855
1856
|
},
|
|
@@ -1970,7 +1971,8 @@
|
|
|
1970
1971
|
"loading": "Cargando sesiones…",
|
|
1971
1972
|
"empty": "No hay sesiones activas",
|
|
1972
1973
|
"signOutAllOnThisDevice": "Cerrar sesión en los demás dispositivos",
|
|
1973
|
-
"signOutAllSubtitle": "Finalizar {{count}} sesión(es) de otros dispositivos"
|
|
1974
|
+
"signOutAllSubtitle": "Finalizar {{count}} sesión(es) de otros dispositivos",
|
|
1975
|
+
"otherSession": "Otra sesión"
|
|
1974
1976
|
},
|
|
1975
1977
|
"confirms": {
|
|
1976
1978
|
"removeDeviceTitle": "Eliminar dispositivo",
|
|
@@ -1850,6 +1850,7 @@
|
|
|
1850
1850
|
"switch": {
|
|
1851
1851
|
"title": "Switch account",
|
|
1852
1852
|
"count": "{{count}} accounts",
|
|
1853
|
+
"countOne": "{{count}} account",
|
|
1853
1854
|
"empty": "Accounts you own or share"
|
|
1854
1855
|
}
|
|
1855
1856
|
},
|
|
@@ -1970,7 +1971,8 @@
|
|
|
1970
1971
|
"loading": "Loading sessions…",
|
|
1971
1972
|
"empty": "No active sessions",
|
|
1972
1973
|
"signOutAllOnThisDevice": "Sign out of all other devices",
|
|
1973
|
-
"signOutAllSubtitle": "End {{count}} other device session(s)"
|
|
1974
|
+
"signOutAllSubtitle": "End {{count}} other device session(s)",
|
|
1975
|
+
"otherSession": "Another session"
|
|
1974
1976
|
},
|
|
1975
1977
|
"confirms": {
|
|
1976
1978
|
"removeDeviceTitle": "Remove device",
|
|
@@ -1850,6 +1850,7 @@
|
|
|
1850
1850
|
"switch": {
|
|
1851
1851
|
"title": "Cambiar de cuenta",
|
|
1852
1852
|
"count": "{{count}} cuentas",
|
|
1853
|
+
"countOne": "{{count}} cuenta",
|
|
1853
1854
|
"empty": "Cuentas que posees o compartes"
|
|
1854
1855
|
}
|
|
1855
1856
|
},
|
|
@@ -1970,7 +1971,8 @@
|
|
|
1970
1971
|
"loading": "Cargando sesiones…",
|
|
1971
1972
|
"empty": "No hay sesiones activas",
|
|
1972
1973
|
"signOutAllOnThisDevice": "Cerrar sesión en los demás dispositivos",
|
|
1973
|
-
"signOutAllSubtitle": "Finalizar {{count}} sesión(es) de otros dispositivos"
|
|
1974
|
+
"signOutAllSubtitle": "Finalizar {{count}} sesión(es) de otros dispositivos",
|
|
1975
|
+
"otherSession": "Otra sesión"
|
|
1974
1976
|
},
|
|
1975
1977
|
"confirms": {
|
|
1976
1978
|
"removeDeviceTitle": "Eliminar dispositivo",
|
package/dist/cjs/index.js
CHANGED
|
@@ -24,7 +24,10 @@ exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = e
|
|
|
24
24
|
exports.registrableApex = exports.getAccountColor = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.isDev = exports.consoleSink = exports.resetLoggerConfig = exports.getLoggerConfig = exports.configureLogger = exports.createLogger = exports.logger = exports.normalizeMultilineText = exports.normalizeInlineText = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE = exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE = exports.DISPLAY_NAME_DISALLOWED_SOURCE = exports.DISPLAY_NAME_ALLOWED_SCRIPTS = exports.isValidDisplayName = exports.isValidPassword = exports.isValidEmail = exports.DISPLAY_NAME_INVALID_MESSAGE = exports.MAX_DISPLAY_NAME_LENGTH = exports.PASSWORD_REGEX = exports.EMAIL_REGEX = exports.retryAsync = exports.validateRequiredFields = exports.parseHttpErrorBody = exports.isHttpRequestError = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = void 0;
|
|
25
25
|
exports.decideSharedDeviceJoin = exports.createSharedMirroringAuthStateStore = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.resolveAccountDelegationAccess = exports.canSwitchIntoAccount = exports.isSwitchTargetAccount = exports.showsPrincipalHeaders = exports.buildSwitcherRows = exports.resolveDeviceContext = exports.resolveActiveContext = exports.projectDevicePrincipals = exports.directoryHandle = exports.directoryDisplayName = exports.canActivateContext = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.consumeOAuthReturnPath = exports.persistOAuthReturnPath = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.canonicalizeOAuthRedirectUri = exports.normalizeOAuthRedirectUri = exports.OXY_OAUTH_RETURN_PATH_STORAGE_KEY = exports.OXY_OAUTH_REDIRECT_URI_STORAGE_KEY = exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.OXY_AUTHORIZE_URL = exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.isOxyRpOrigin = exports.IDENTITY_WEB_ORIGIN = exports.CENTRAL_IDP_APEX = void 0;
|
|
26
26
|
exports.packageInfo = exports.runSessionColdBoot = exports.OXY_INFERENCE_BASE_URL = exports.OxyInferenceProtocolError = exports.OxyInferenceError = exports.OxyInferenceClient = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AccountNotOnDeviceError = exports.establishIdentitySession = exports.resolveIdentityPin = exports.IDENTITY_PIN_STORAGE_KEY = exports.identityPinMatches = exports.createMemoryIdentityPinStore = exports.createNativeIdentityPinStore = exports.createWebIdentityPinStore = exports.readLocalDeviceCredential = exports.publishProvenDeviceCredential = exports.normalizeSharedDeviceSessionRead = exports.decideSharedDevicePublish = void 0;
|
|
27
|
-
// Ensure crypto polyfills are loaded before anything else
|
|
27
|
+
// Ensure crypto polyfills are loaded before anything else. This MUST stay the
|
|
28
|
+
// first import: `@noble/hashes` captures `globalThis.crypto` when it is
|
|
29
|
+
// evaluated, so the shim has to exist before any module below reaches it (see
|
|
30
|
+
// the evaluation-order note in `./crypto/polyfill`).
|
|
28
31
|
require("./crypto/polyfill");
|
|
29
32
|
// ---------------------------------------------------------------------------
|
|
30
33
|
// API client
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
*/
|
|
59
59
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
60
|
exports.AccountDialogController = void 0;
|
|
61
|
+
exports.backViewOf = backViewOf;
|
|
61
62
|
exports.createAccountDialogController = createAccountDialogController;
|
|
62
63
|
const logger_1 = require("../logger");
|
|
63
64
|
const errorUtils_1 = require("../utils/errorUtils");
|
|
@@ -333,16 +334,25 @@ class AccountDialogController {
|
|
|
333
334
|
}
|
|
334
335
|
// Signed out. The directory is bearer-read and `SessionClient` drops the one
|
|
335
336
|
// it holds when the device empties, so there is nothing to clear here — only
|
|
336
|
-
// the in-flight bookkeeping, which no longer describes anything.
|
|
337
|
+
// the in-flight bookkeeping, which no longer describes anything. The account
|
|
338
|
+
// menu described the account that just left, so it gives way to the entry.
|
|
337
339
|
this.error = null;
|
|
338
340
|
this.loading = false;
|
|
341
|
+
this.view = this.resolveView(this.view);
|
|
339
342
|
this.emit();
|
|
340
343
|
}
|
|
341
344
|
// =========================================================================
|
|
342
345
|
// View actions
|
|
343
346
|
// =========================================================================
|
|
344
|
-
/**
|
|
345
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Set the dialog view directly.
|
|
349
|
+
*
|
|
350
|
+
* `'accounts'` is only honoured with a session. Signed out it lands on the
|
|
351
|
+
* sign-in entry instead — the menu it would open holds "Switch account",
|
|
352
|
+
* storage and "Sign out" for an account that does not exist.
|
|
353
|
+
*/
|
|
354
|
+
setView(requested) {
|
|
355
|
+
const view = this.resolveView(requested);
|
|
346
356
|
if (this.view === view)
|
|
347
357
|
return;
|
|
348
358
|
this.view = view;
|
|
@@ -355,6 +365,37 @@ class AccountDialogController {
|
|
|
355
365
|
}
|
|
356
366
|
this.emit();
|
|
357
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Go back one step: `signup` and `qr` return to the sign-in entry, and the
|
|
370
|
+
* entry returns to the account menu when somebody is signed in.
|
|
371
|
+
*
|
|
372
|
+
* Returns `false` — changing nothing — when the current view is the dialog's
|
|
373
|
+
* first one, so the host closes the dialog instead. Leaving an active
|
|
374
|
+
* request withdraws it; otherwise its authorize code stays approvable until
|
|
375
|
+
* it expires.
|
|
376
|
+
*/
|
|
377
|
+
back() {
|
|
378
|
+
const target = backViewOf(this.view, this.hasSession());
|
|
379
|
+
if (target === null)
|
|
380
|
+
return false;
|
|
381
|
+
if (this.signIn.phase === 'starting' || this.signIn.phase === 'waiting') {
|
|
382
|
+
this.cancelSignIn();
|
|
383
|
+
}
|
|
384
|
+
this.setView(target);
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
/** `'accounts'` needs a session; without one the sign-in entry stands in for it. */
|
|
388
|
+
resolveView(view) {
|
|
389
|
+
return view === 'accounts' && !this.hasSession() ? 'signin' : view;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Whether an account is signed in here — the precondition of the account
|
|
393
|
+
* menu. The bearer, not the directory: the directory is bearer-read, and the
|
|
394
|
+
* menu's content (hero, storage, sign-out) is all about the bearer's account.
|
|
395
|
+
*/
|
|
396
|
+
hasSession() {
|
|
397
|
+
return this.isAuthenticated();
|
|
398
|
+
}
|
|
358
399
|
/** Switch to the "add account" view (the sign-in entry chooser). */
|
|
359
400
|
add() {
|
|
360
401
|
this.setView('add');
|
|
@@ -1289,8 +1330,13 @@ class AccountDialogController {
|
|
|
1289
1330
|
}
|
|
1290
1331
|
computeSnapshot() {
|
|
1291
1332
|
const directory = this.sessionClient.getDirectory();
|
|
1333
|
+
const hasSession = this.hasSession();
|
|
1334
|
+
// Resolved here as well as in `setView`: the controller is constructed on
|
|
1335
|
+
// `'accounts'` before anyone knows whether a bearer exists.
|
|
1336
|
+
const view = this.resolveView(this.view);
|
|
1292
1337
|
return {
|
|
1293
|
-
view
|
|
1338
|
+
view,
|
|
1339
|
+
backView: backViewOf(view, hasSession),
|
|
1294
1340
|
directory,
|
|
1295
1341
|
activeContext: (0, deviceDirectory_1.resolveActiveContext)(directory),
|
|
1296
1342
|
loading: this.loading,
|
|
@@ -1316,6 +1362,29 @@ class AccountDialogController {
|
|
|
1316
1362
|
}
|
|
1317
1363
|
}
|
|
1318
1364
|
exports.AccountDialogController = AccountDialogController;
|
|
1365
|
+
/**
|
|
1366
|
+
* The view one step back from `view`, or `null` when `view` is the dialog's
|
|
1367
|
+
* first view.
|
|
1368
|
+
*
|
|
1369
|
+
* The sign-in entry is `'add'` ("add another account") when somebody is signed
|
|
1370
|
+
* in and `'signin'` when nobody is; `signup` and `qr` are reached FROM that
|
|
1371
|
+
* entry, so they return to it. The entry itself returns to the account menu —
|
|
1372
|
+
* which exists only with a session. This used to be a host-side
|
|
1373
|
+
* "back = accounts" that assumed a signed-in origin, so a signed-out Back from
|
|
1374
|
+
* "Create your account" opened a menu holding "Sign out" for nobody.
|
|
1375
|
+
*/
|
|
1376
|
+
function backViewOf(view, hasSession) {
|
|
1377
|
+
switch (view) {
|
|
1378
|
+
case 'signup':
|
|
1379
|
+
case 'qr':
|
|
1380
|
+
return hasSession ? 'add' : 'signin';
|
|
1381
|
+
case 'add':
|
|
1382
|
+
return hasSession ? 'accounts' : null;
|
|
1383
|
+
case 'signin':
|
|
1384
|
+
case 'accounts':
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1319
1388
|
/** Factory mirroring `createSessionClient`, for ergonomic wiring by consumers. */
|
|
1320
1389
|
function createAccountDialogController(options) {
|
|
1321
1390
|
return new AccountDialogController(options);
|