@oxy.so/core 7.13.0 → 7.14.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 (57) hide show
  1. package/dist/cjs/api/follows.js +12 -11
  2. package/dist/cjs/api/users.js +1 -1
  3. package/dist/cjs/crypto/aead.js +3 -3
  4. package/dist/cjs/crypto/contentHash.js +4 -4
  5. package/dist/cjs/crypto/identityDerivations.js +5 -5
  6. package/dist/cjs/crypto/identityLink.js +3 -3
  7. package/dist/cjs/crypto/identityProof.js +3 -3
  8. package/dist/cjs/crypto/index.js +2 -2
  9. package/dist/cjs/crypto/kdf.js +3 -3
  10. package/dist/cjs/crypto/polyfill.js +13 -13
  11. package/dist/cjs/crypto/recoveryPhrase.js +12 -12
  12. package/dist/cjs/crypto/registrationPow.js +4 -4
  13. package/dist/cjs/i18n/locales/compact/es-ES.generated.js +1 -1
  14. package/dist/cjs/i18n/locales/en-US.js +0 -2
  15. package/dist/cjs/i18n/locales/es-ES.js +0 -2
  16. package/dist/cjs/index.js +2 -2
  17. package/dist/cjs/utils/identityCacheSweep.js +9 -1
  18. package/dist/esm/api/follows.js +12 -11
  19. package/dist/esm/api/users.js +2 -2
  20. package/dist/esm/crypto/aead.js +1 -1
  21. package/dist/esm/crypto/contentHash.js +2 -2
  22. package/dist/esm/crypto/identityDerivations.js +2 -2
  23. package/dist/esm/crypto/identityLink.js +2 -2
  24. package/dist/esm/crypto/identityProof.js +2 -2
  25. package/dist/esm/crypto/index.js +2 -2
  26. package/dist/esm/crypto/kdf.js +2 -2
  27. package/dist/esm/crypto/polyfill.js +13 -13
  28. package/dist/esm/crypto/recoveryPhrase.js +1 -1
  29. package/dist/esm/crypto/registrationPow.js +3 -3
  30. package/dist/esm/i18n/locales/compact/es-ES.generated.js +1 -1
  31. package/dist/esm/i18n/locales/en-US.js +0 -2
  32. package/dist/esm/i18n/locales/es-ES.js +0 -2
  33. package/dist/esm/index.js +2 -2
  34. package/dist/esm/utils/identityCacheSweep.js +8 -1
  35. package/dist/types/crypto/polyfill.d.ts +13 -13
  36. package/dist/types/crypto/registrationPow.d.ts +1 -1
  37. package/dist/types/i18n/locales/en-US.d.ts +0 -2
  38. package/dist/types/i18n/locales/es-ES.d.ts +0 -2
  39. package/dist/types/utils/identityCacheSweep.d.ts +5 -0
  40. package/package.json +8 -8
  41. package/src/api/follows.ts +21 -12
  42. package/src/api/users.ts +6 -2
  43. package/src/crypto/aead.ts +1 -1
  44. package/src/crypto/contentHash.ts +2 -2
  45. package/src/crypto/identityDerivations.ts +2 -2
  46. package/src/crypto/identityLink.ts +2 -2
  47. package/src/crypto/identityProof.ts +2 -2
  48. package/src/crypto/index.ts +2 -2
  49. package/src/crypto/kdf.ts +2 -2
  50. package/src/crypto/polyfill.ts +13 -13
  51. package/src/crypto/recoveryPhrase.ts +1 -1
  52. package/src/crypto/registrationPow.ts +3 -3
  53. package/src/i18n/locales/compact/es-ES.generated.ts +1 -1
  54. package/src/i18n/locales/en-US.ts +0 -2
  55. package/src/i18n/locales/es-ES.ts +0 -2
  56. package/src/index.ts +2 -2
  57. package/src/utils/identityCacheSweep.ts +9 -1
@@ -1897,7 +1897,6 @@ const dict = {
1897
1897
  transfer: 'Transfer ownership',
1898
1898
  },
1899
1899
  errors: {
1900
- emailRequired: 'Enter an email to invite.',
1901
1900
  emailInvalid: 'Enter a valid email address, like name@example.com.',
1902
1901
  missingAccount: 'No account selected.',
1903
1902
  noPermission: 'You do not have permission to view members.',
@@ -2334,7 +2333,6 @@ const dict = {
2334
2333
  newLabel: 'New password',
2335
2334
  repeatLabel: 'Repeat the password',
2336
2335
  tooShort: 'Use at least {{min}} characters.',
2337
- tooLong: 'Use at most {{max}} characters.',
2338
2336
  mismatch: "The passwords don't match.",
2339
2337
  signOutOthers: 'Sign out everywhere else',
2340
2338
  save: 'Save password',
@@ -1897,7 +1897,6 @@ const dict = {
1897
1897
  transfer: 'Transferir propiedad',
1898
1898
  },
1899
1899
  errors: {
1900
- emailRequired: 'Introduce un email para invitar.',
1901
1900
  emailInvalid: 'Introduce un email válido, como nombre@ejemplo.com.',
1902
1901
  missingAccount: 'Ninguna cuenta seleccionada.',
1903
1902
  noPermission: 'No tienes permiso para ver los miembros.',
@@ -2334,7 +2333,6 @@ const dict = {
2334
2333
  newLabel: 'Contraseña nueva',
2335
2334
  repeatLabel: 'Repite la contraseña',
2336
2335
  tooShort: 'Usa al menos {{min}} caracteres.',
2337
- tooLong: 'Usa como máximo {{max}} caracteres.',
2338
2336
  mismatch: 'Las contraseñas no coinciden.',
2339
2337
  signOutOthers: 'Cerrar sesión en todos los demás sitios',
2340
2338
  save: 'Guardar contraseña',
package/dist/cjs/index.js CHANGED
@@ -28,8 +28,8 @@ exports.packageInfo = exports.consumeOAuthReturnPath = exports.persistOAuthRetur
28
28
  // The crypto shim MUST stay the first import. The root itself ships no crypto
29
29
  // library (`__tests__/rootEntryGraph.test.ts`), but `@oxy.so/protocol`'s root
30
30
  // entry (reached by the OAuth/PKCE and device helpers) does, and Metro does not
31
- // tree-shake: on Hermes, `@noble/hashes` would capture an absent
32
- // `globalThis.crypto` at evaluation. See the note in `./crypto/polyfill`.
31
+ // tree-shake: on Hermes, the shim has to exist before any of it evaluates. See
32
+ // the note in `./crypto/polyfill`.
33
33
  require("./crypto/polyfill");
34
34
  // ---------------------------------------------------------------------------
35
35
  // API client
@@ -53,6 +53,7 @@
53
53
  */
54
54
  Object.defineProperty(exports, "__esModule", { value: true });
55
55
  exports.OXY_IDENTITY_CACHE_PREFIXES = void 0;
56
+ exports.oxyUserByIdPath = oxyUserByIdPath;
56
57
  exports.oxyUserByIdCacheKey = oxyUserByIdCacheKey;
57
58
  exports.evictOxyIdentityCache = evictOxyIdentityCache;
58
59
  /**
@@ -69,12 +70,19 @@ exports.OXY_IDENTITY_CACHE_PREFIXES = [
69
70
  // `resolveProfile` — keyed by fediverse handle in the query payload.
70
71
  'GET:/profiles/resolve',
71
72
  ];
73
+ /**
74
+ * The path `getUserById` reads, its id encoded. Built in one place so the
75
+ * request and every key that evicts it cannot spell the id differently.
76
+ */
77
+ function oxyUserByIdPath(userId) {
78
+ return `/users/${encodeURIComponent(userId)}`;
79
+ }
72
80
  /**
73
81
  * Build the exact cache key `getUserById` reads under. The only identity key
74
82
  * derivable from a user id, so the only one that does not need a prefix sweep.
75
83
  */
76
84
  function oxyUserByIdCacheKey(userId) {
77
- return `GET:/users/${userId}`;
85
+ return `GET:${oxyUserByIdPath(userId)}`;
78
86
  }
79
87
  /**
80
88
  * Sweep an `OxyServices` GET response cache of everything that could carry the
@@ -1,5 +1,5 @@
1
1
  import { buildPaginationParams, buildQueryParams, buildUrl, } from '../utils/apiUtils.js';
2
- const enc = encodeURIComponent;
2
+ import { oxyUserByIdCacheKey, oxyUserByIdPath } from '../utils/identityCacheSweep.js';
3
3
  const GRAPH_TTL = 2 * 60 * 1000;
4
4
  const STATUS_TTL = 60 * 1000;
5
5
  /**
@@ -14,7 +14,7 @@ export class FollowsApi {
14
14
  // ── People ───────────────────────────────────────────────────────────────
15
15
  /** Follow a user. */
16
16
  async follow(userId) {
17
- const result = await this.ctx.request('POST', `/users/${enc(userId)}/follow`, undefined, { cache: false });
17
+ const result = await this.ctx.request('POST', `${oxyUserByIdPath(userId)}/follow`, undefined, { cache: false });
18
18
  this.invalidate([userId]);
19
19
  return result;
20
20
  }
@@ -28,7 +28,7 @@ export class FollowsApi {
28
28
  }
29
29
  /** Unfollow a user. */
30
30
  async unfollow(userId) {
31
- const result = await this.ctx.request('DELETE', `/users/${enc(userId)}/follow`, undefined, { cache: false });
31
+ const result = await this.ctx.request('DELETE', `${oxyUserByIdPath(userId)}/follow`, undefined, { cache: false });
32
32
  this.invalidate([userId]);
33
33
  return result;
34
34
  }
@@ -42,7 +42,7 @@ export class FollowsApi {
42
42
  }
43
43
  /** Whether the signed-in user follows `userId`. Cached 1 minute, busted by every write. */
44
44
  async status(userId) {
45
- return this.ctx.request('GET', `/users/${enc(userId)}/follow-status`, undefined, {
45
+ return this.ctx.request('GET', `${oxyUserByIdPath(userId)}/follow-status`, undefined, {
46
46
  cache: true,
47
47
  cacheTTL: STATUS_TTL,
48
48
  });
@@ -72,12 +72,12 @@ export class FollowsApi {
72
72
  * `limit`/`offset`/`sort` combination is its own cache entry (2 minutes).
73
73
  */
74
74
  async followers(userId, params) {
75
- const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/followers`, params);
75
+ const { data, total, hasMore } = await this.userPage(`${oxyUserByIdPath(userId)}/followers`, params);
76
76
  return { followers: data, total, hasMore };
77
77
  }
78
78
  /** Who a user follows. `sort` as in `followers`. */
79
79
  async following(userId, params) {
80
- const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/following`, params);
80
+ const { data, total, hasMore } = await this.userPage(`${oxyUserByIdPath(userId)}/following`, params);
81
81
  return { following: data, total, hasMore };
82
82
  }
83
83
  /**
@@ -85,7 +85,7 @@ export class FollowsApi {
85
85
  * `userId`.
86
86
  */
87
87
  async mutuals(userId, params) {
88
- const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/mutuals`, params);
88
+ const { data, total, hasMore } = await this.userPage(`${oxyUserByIdPath(userId)}/mutuals`, params);
89
89
  return { mutuals: data, total, hasMore };
90
90
  }
91
91
  /**
@@ -220,16 +220,17 @@ export class FollowsApi {
220
220
  const keys = ['GET:/users/me/graph'];
221
221
  const prefixes = ['GET:/profiles/username/', 'GET:/profiles/resolve'];
222
222
  for (const id of targetUserIds) {
223
- keys.push(`GET:/users/${enc(id)}/follow-status`);
223
+ const user = oxyUserByIdPath(id);
224
+ keys.push(`GET:${user}/follow-status`);
224
225
  // Profiles embed the viewer-relative `relationship`.
225
- keys.push(`GET:/users/${enc(id)}`);
226
+ keys.push(oxyUserByIdCacheKey(id));
226
227
  // The target gained or lost a follower, and "followers you know" with it.
227
- prefixes.push(`GET:/users/${enc(id)}/followers`, `GET:/users/${enc(id)}/mutuals`);
228
+ prefixes.push(`GET:${user}/followers`, `GET:${user}/mutuals`);
228
229
  }
229
230
  // The viewer's own following list changed.
230
231
  const viewerId = this.ctx.oxy.session.userId;
231
232
  if (viewerId)
232
- prefixes.push(`GET:/users/${enc(viewerId)}/following`);
233
+ prefixes.push(`GET:${oxyUserByIdPath(viewerId)}/following`);
233
234
  this.ctx.http.invalidateCache({ keys, prefixes });
234
235
  }
235
236
  }
@@ -1,7 +1,7 @@
1
1
  import { recommendationRequestCheck, parseContract } from '@oxy.so/contracts/client';
2
2
  import { buildQueryParams } from '../utils/apiUtils.js';
3
3
  import { normalizeUserIdentity, normalizeUserIdentityOrNull } from '../utils/userIdentity.js';
4
- import { OXY_IDENTITY_CACHE_PREFIXES, oxyUserByIdCacheKey } from '../utils/identityCacheSweep.js';
4
+ import { OXY_IDENTITY_CACHE_PREFIXES, oxyUserByIdCacheKey, oxyUserByIdPath, } from '../utils/identityCacheSweep.js';
5
5
  import { OXY_ACCOUNT_LIST_CACHE_KEY, OXY_ACCOUNT_LIST_CACHE_QUERY_PREFIX, oxyAccountDetailCacheKey, } from '../utils/accountCacheSweep.js';
6
6
  import { extractErrorStatus } from '../utils/errorUtils.js';
7
7
  import { runBatchLookup } from '../utils/batchLookup.js';
@@ -26,7 +26,7 @@ export class UsersApi {
26
26
  * registry-fresh read that neither serves nor overwrites a cached entry.
27
27
  */
28
28
  async get(userId, options) {
29
- const user = await this.ctx.request('GET', `/users/${enc(userId)}`, undefined, {
29
+ const user = await this.ctx.request('GET', oxyUserByIdPath(userId), undefined, {
30
30
  cache: options?.cache ?? true,
31
31
  cacheTTL: PROFILE_TTL,
32
32
  });
@@ -22,7 +22,7 @@
22
22
  // Loading the polyfill guarantees `globalThis.crypto.getRandomValues` exists on
23
23
  // every platform (native crypto on web/Node; expo-crypto-backed shim on RN).
24
24
  import './polyfill.js';
25
- import { xchacha20poly1305 } from '@noble/ciphers/chacha';
25
+ import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
26
26
  /** Key length for XChaCha20-Poly1305, in bytes (256-bit). */
27
27
  export const AEAD_KEY_LENGTH = 32;
28
28
  /** Nonce length for XChaCha20-Poly1305, in bytes (192-bit). */
@@ -6,8 +6,8 @@
6
6
  * directly: `@noble/hashes` must load after this entry's polyfill, and through
7
7
  * the ONE lazy root, so it never lands in an app's startup JavaScript.
8
8
  */
9
- import { sha256 } from '@noble/hashes/sha256';
10
- import { bytesToHex } from '@noble/hashes/utils';
9
+ import { sha256 } from '@noble/hashes/sha2.js';
10
+ import { bytesToHex } from '@noble/hashes/utils.js';
11
11
  export function createContentHasher() {
12
12
  const hash = sha256.create();
13
13
  return {
@@ -10,8 +10,8 @@
10
10
  *
11
11
  * ESM/CJS safe: static `import` only, no `require()`.
12
12
  */
13
- import { hmac } from '@noble/hashes/hmac';
14
- import { sha256, sha512 } from '@noble/hashes/sha2';
13
+ import { hmac } from '@noble/hashes/hmac.js';
14
+ import { sha256, sha512 } from '@noble/hashes/sha2.js';
15
15
  import { deriveSecp256k1PublicKey, normalizeSecp256k1PrivateKey, signSecp256k1Digest, tweakAddSecp256k1PrivateKey, } from '@oxy.so/protocol/secp256k1';
16
16
  import { hkdfSha256 } from './kdf.js';
17
17
  /**
@@ -5,8 +5,8 @@
5
5
  * Commons from its own; the person checks they match before the link is
6
6
  * confirmed, so a photographed QR cannot slip another key in.
7
7
  */
8
- import { sha256 } from '@noble/hashes/sha256';
9
- import { utf8ToBytes } from '@noble/hashes/utils';
8
+ import { sha256 } from '@noble/hashes/sha2.js';
9
+ import { utf8ToBytes } from '@noble/hashes/utils.js';
10
10
  const DOMAIN = 'oxy-identity-link-code/v1';
11
11
  export function deriveIdentityLinkCode(linkId, publicKey) {
12
12
  const digest = sha256(utf8ToBytes(`${DOMAIN}|${linkId}|${publicKey.trim().toLowerCase()}`));
@@ -6,8 +6,8 @@
6
6
  * its own JSON.
7
7
  */
8
8
  import './polyfill.js';
9
- import { sha256 } from '@noble/hashes/sha256';
10
- import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
9
+ import { sha256 } from '@noble/hashes/sha2.js';
10
+ import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils.js';
11
11
  import { signMessage } from '@oxy.so/protocol/sign';
12
12
  import { buildIdentityProofMessage, canonicalJson, IDENTITY_PROOF_VERSION, } from '@oxy.so/contracts/client';
13
13
  /** SHA-256 hex of the canonical JSON of `payload` — the `payloadDigest` claim. */
@@ -10,8 +10,8 @@
10
10
  * internal barrel) made Metro hoist the shared stack into the always-loaded
11
11
  * common chunk, which is the cost the lazy loading exists to avoid.
12
12
  */
13
- // MUST stay the first import: `@noble/hashes` captures `globalThis.crypto` when
14
- // it is evaluated, so the shim has to exist before anything below reaches it.
13
+ // MUST stay the first import: the shim has to exist before any module below
14
+ // evaluates or draws randomness (see `./polyfill`).
15
15
  import './polyfill.js';
16
16
  export { KeyManager, IdentityAlreadyExistsError, IdentityPersistError, IdentityUnavailableError, } from './keyManager.js';
17
17
  export { readIdentityMarker, updateIdentityMarker } from './identityMarker.js';
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * ESM/CJS safe: static `import` only, no `require()`.
11
11
  */
12
- import { hkdf } from '@noble/hashes/hkdf';
13
- import { sha256 } from '@noble/hashes/sha256';
12
+ import { hkdf } from '@noble/hashes/hkdf.js';
13
+ import { sha256 } from '@noble/hashes/sha2.js';
14
14
  /**
15
15
  * Derive `length` bytes of keying material from `ikm` using HKDF-SHA256
16
16
  * (RFC 5869 — extract-then-expand).
@@ -17,20 +17,20 @@
17
17
  * global WebCrypto, and a `require('node:crypto')` here made web bundlers ship
18
18
  * Node's crypto, stream and buffer polyfills (~600 KB) to every browser.
19
19
  *
20
- * # Evaluation order — this module must import NOTHING that can capture the global
20
+ * # Evaluation order — this module must import NOTHING that reaches a crypto library
21
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.
22
+ * ES imports are evaluated before the importing module's body, so every module
23
+ * this file imports runs BEFORE the shim below is installed. This file used to
24
+ * import `@oxy.so/protocol`'s root entry, which reaches `@noble/curves` →
25
+ * `@noble/hashes` through the envelope signer, and `@noble/hashes` 1.x read
26
+ * `globalThis.crypto` once, at evaluation: on Hermes (no `globalThis.crypto` at
27
+ * startup) it captured `undefined` before the shim existed, and every identity
28
+ * creation on Android failed with `crypto.getRandomValues must be defined`.
29
+ * Noble 2.x reads the global on every call, so that capture is gone, but the
30
+ * shim still has to exist before any module body that reads it runs. Its
31
+ * imports are therefore limited to `buffer` and `@oxy.so/protocol/random`,
32
+ * both free of crypto libraries, and `index.ts` imports this file first.
33
+ * `__tests__/polyfillOrder.test.ts` guards both halves.
34
34
  *
35
35
  */
36
36
  import _cjs_buffer from 'buffer';
@@ -13,7 +13,7 @@
13
13
  * app. Adding another language means one more subpath import, ideally lazy.
14
14
  */
15
15
  import { generateMnemonic, mnemonicToSeed, validateMnemonic } from '@scure/bip39';
16
- import { wordlist } from '@scure/bip39/wordlists/english';
16
+ import { wordlist } from '@scure/bip39/wordlists/english.js';
17
17
  import { KeyManager } from './keyManager.js';
18
18
  import { hkdfSha256 } from './kdf.js';
19
19
  /**
@@ -7,8 +7,8 @@
7
7
  * `auth/registrationPow.ts` — this file is only the solve loop.
8
8
  */
9
9
  import './polyfill.js';
10
- import { sha256 } from '@noble/hashes/sha256';
11
- import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
10
+ import { sha256 } from '@noble/hashes/sha2.js';
11
+ import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils.js';
12
12
  import { meetsRegistrationPowDifficulty, registrationPowMessage, REGISTRATION_POW_DIFFICULTY_BITS, } from '@oxy.so/protocol';
13
13
  /**
14
14
  * How many attempts the grind loop runs before yielding to the event loop.
@@ -28,7 +28,7 @@ function yieldToEventLoop() {
28
28
  * Grind a nonce for `publicKey`/`timestamp` that clears
29
29
  * {@link REGISTRATION_POW_DIFFICULTY_BITS}, returned as a decimal counter.
30
30
  *
31
- * Hashes with `@noble/hashes/sha256` — pure JS, synchronous, identical output
31
+ * Hashes with `@noble/hashes/sha2.js`'s `sha256` — pure JS, synchronous, identical output
32
32
  * on web/Node/React Native with no native module and no per-attempt bridge
33
33
  * crossing (see the difficulty constant's own doc for why that distinction is
34
34
  * the whole point). Nonces are sequential (0, 1, 2, …) rather than random: the