@oxyhq/core 1.11.12 → 1.11.14

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 (130) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/CrossDomainAuth.js +3 -1
  3. package/dist/cjs/HttpService.js +214 -33
  4. package/dist/cjs/OxyServices.base.js +9 -0
  5. package/dist/cjs/OxyServices.js +8 -3
  6. package/dist/cjs/crypto/index.js +3 -1
  7. package/dist/cjs/crypto/keyManager.js +476 -172
  8. package/dist/cjs/crypto/polyfill.js +14 -65
  9. package/dist/cjs/crypto/recoveryPhrase.js +30 -11
  10. package/dist/cjs/crypto/signatureService.js +25 -60
  11. package/dist/cjs/i18n/locales/en-US.json +46 -1
  12. package/dist/cjs/i18n/locales/es-ES.json +46 -1
  13. package/dist/cjs/i18n/locales/locales/en-US.json +46 -1
  14. package/dist/cjs/i18n/locales/locales/es-ES.json +46 -1
  15. package/dist/cjs/index.js +10 -2
  16. package/dist/cjs/mixins/OxyServices.assets.js +9 -4
  17. package/dist/cjs/mixins/OxyServices.auth.js +147 -14
  18. package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
  19. package/dist/cjs/mixins/OxyServices.features.js +0 -11
  20. package/dist/cjs/mixins/OxyServices.fedcm.js +4 -3
  21. package/dist/cjs/mixins/OxyServices.language.js +5 -36
  22. package/dist/cjs/mixins/OxyServices.redirect.js +6 -2
  23. package/dist/cjs/mixins/OxyServices.security.js +13 -2
  24. package/dist/cjs/mixins/OxyServices.user.js +59 -38
  25. package/dist/cjs/mixins/OxyServices.utility.js +416 -110
  26. package/dist/cjs/mixins/index.js +11 -3
  27. package/dist/cjs/utils/accountUtils.js +71 -2
  28. package/dist/cjs/utils/deviceManager.js +5 -36
  29. package/dist/cjs/utils/languageUtils.js +22 -0
  30. package/dist/cjs/utils/platformCrypto.js +165 -0
  31. package/dist/cjs/utils/platformCrypto.native.js +123 -0
  32. package/dist/esm/.tsbuildinfo +1 -1
  33. package/dist/esm/CrossDomainAuth.js +3 -1
  34. package/dist/esm/HttpService.js +215 -34
  35. package/dist/esm/OxyServices.base.js +9 -0
  36. package/dist/esm/OxyServices.js +8 -3
  37. package/dist/esm/crypto/index.js +1 -1
  38. package/dist/esm/crypto/keyManager.js +473 -138
  39. package/dist/esm/crypto/polyfill.js +14 -32
  40. package/dist/esm/crypto/recoveryPhrase.js +30 -11
  41. package/dist/esm/crypto/signatureService.js +25 -27
  42. package/dist/esm/i18n/locales/en-US.json +46 -1
  43. package/dist/esm/i18n/locales/es-ES.json +46 -1
  44. package/dist/esm/i18n/locales/locales/en-US.json +46 -1
  45. package/dist/esm/i18n/locales/locales/es-ES.json +46 -1
  46. package/dist/esm/index.js +4 -3
  47. package/dist/esm/mixins/OxyServices.assets.js +9 -4
  48. package/dist/esm/mixins/OxyServices.auth.js +145 -14
  49. package/dist/esm/mixins/OxyServices.contacts.js +47 -0
  50. package/dist/esm/mixins/OxyServices.features.js +0 -11
  51. package/dist/esm/mixins/OxyServices.fedcm.js +4 -3
  52. package/dist/esm/mixins/OxyServices.language.js +5 -3
  53. package/dist/esm/mixins/OxyServices.redirect.js +6 -2
  54. package/dist/esm/mixins/OxyServices.security.js +13 -2
  55. package/dist/esm/mixins/OxyServices.user.js +59 -38
  56. package/dist/esm/mixins/OxyServices.utility.js +416 -77
  57. package/dist/esm/mixins/index.js +11 -3
  58. package/dist/esm/utils/accountUtils.js +67 -1
  59. package/dist/esm/utils/deviceManager.js +5 -3
  60. package/dist/esm/utils/languageUtils.js +21 -0
  61. package/dist/esm/utils/platformCrypto.js +125 -0
  62. package/dist/esm/utils/platformCrypto.native.js +80 -0
  63. package/dist/types/.tsbuildinfo +1 -1
  64. package/dist/types/HttpService.d.ts +47 -3
  65. package/dist/types/OxyServices.base.d.ts +7 -0
  66. package/dist/types/OxyServices.d.ts +50 -7
  67. package/dist/types/crypto/index.d.ts +1 -1
  68. package/dist/types/crypto/keyManager.d.ts +110 -9
  69. package/dist/types/crypto/polyfill.d.ts +3 -1
  70. package/dist/types/crypto/recoveryPhrase.d.ts +31 -7
  71. package/dist/types/crypto/signatureService.d.ts +4 -0
  72. package/dist/types/index.d.ts +7 -5
  73. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
  74. package/dist/types/mixins/OxyServices.assets.d.ts +6 -10
  75. package/dist/types/mixins/OxyServices.auth.d.ts +82 -5
  76. package/dist/types/mixins/OxyServices.contacts.d.ts +99 -0
  77. package/dist/types/mixins/OxyServices.developer.d.ts +1 -0
  78. package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
  79. package/dist/types/mixins/OxyServices.features.d.ts +2 -7
  80. package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
  81. package/dist/types/mixins/OxyServices.karma.d.ts +1 -0
  82. package/dist/types/mixins/OxyServices.language.d.ts +1 -0
  83. package/dist/types/mixins/OxyServices.location.d.ts +1 -0
  84. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
  85. package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
  86. package/dist/types/mixins/OxyServices.popup.d.ts +1 -0
  87. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
  88. package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
  89. package/dist/types/mixins/OxyServices.security.d.ts +1 -0
  90. package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
  91. package/dist/types/mixins/OxyServices.user.d.ts +28 -11
  92. package/dist/types/mixins/OxyServices.utility.d.ts +145 -10
  93. package/dist/types/mixins/index.d.ts +52 -4
  94. package/dist/types/models/interfaces.d.ts +62 -3
  95. package/dist/types/utils/accountUtils.d.ts +41 -1
  96. package/dist/types/utils/languageUtils.d.ts +1 -0
  97. package/dist/types/utils/platformCrypto.d.ts +87 -0
  98. package/dist/types/utils/platformCrypto.native.d.ts +54 -0
  99. package/package.json +45 -2
  100. package/src/CrossDomainAuth.ts +12 -10
  101. package/src/HttpService.ts +251 -40
  102. package/src/OxyServices.base.ts +10 -0
  103. package/src/OxyServices.ts +26 -7
  104. package/src/crypto/__tests__/keyManager.test.ts +336 -0
  105. package/src/crypto/index.ts +6 -1
  106. package/src/crypto/keyManager.ts +529 -151
  107. package/src/crypto/polyfill.ts +14 -34
  108. package/src/crypto/recoveryPhrase.ts +56 -17
  109. package/src/crypto/signatureService.ts +25 -30
  110. package/src/i18n/locales/en-US.json +46 -1
  111. package/src/i18n/locales/es-ES.json +46 -1
  112. package/src/index.ts +19 -4
  113. package/src/mixins/OxyServices.assets.ts +15 -11
  114. package/src/mixins/OxyServices.auth.ts +175 -15
  115. package/src/mixins/OxyServices.contacts.ts +73 -0
  116. package/src/mixins/OxyServices.features.ts +2 -12
  117. package/src/mixins/OxyServices.fedcm.ts +4 -3
  118. package/src/mixins/OxyServices.language.ts +6 -4
  119. package/src/mixins/OxyServices.redirect.ts +6 -2
  120. package/src/mixins/OxyServices.security.ts +18 -8
  121. package/src/mixins/OxyServices.user.ts +72 -49
  122. package/src/mixins/OxyServices.utility.ts +562 -89
  123. package/src/mixins/__tests__/serviceAuth.test.ts +623 -0
  124. package/src/mixins/index.ts +58 -7
  125. package/src/models/interfaces.ts +65 -3
  126. package/src/utils/accountUtils.ts +82 -2
  127. package/src/utils/deviceManager.ts +7 -4
  128. package/src/utils/languageUtils.ts +23 -2
  129. package/src/utils/platformCrypto.native.ts +101 -0
  130. package/src/utils/platformCrypto.ts +145 -0
@@ -6,45 +6,15 @@
6
6
  * across all platforms (Node.js, Browser, React Native).
7
7
  *
8
8
  * - Browser/Node.js: Uses native crypto
9
- * - React Native: Falls back to expo-crypto if native crypto unavailable
9
+ * - React Native: Uses expo-crypto (statically imported via the
10
+ * per-platform `platformCrypto` module — see that file's doc-comment for
11
+ * how platform routing works).
10
12
  */
11
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
- if (k2 === undefined) k2 = k;
13
- var desc = Object.getOwnPropertyDescriptor(m, k);
14
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
- desc = { enumerable: true, get: function() { return m[k]; } };
16
- }
17
- Object.defineProperty(o, k2, desc);
18
- }) : (function(o, m, k, k2) {
19
- if (k2 === undefined) k2 = k;
20
- o[k2] = m[k];
21
- }));
22
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
- Object.defineProperty(o, "default", { enumerable: true, value: v });
24
- }) : function(o, v) {
25
- o["default"] = v;
26
- });
27
- var __importStar = (this && this.__importStar) || (function () {
28
- var ownKeys = function(o) {
29
- ownKeys = Object.getOwnPropertyNames || function (o) {
30
- var ar = [];
31
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
- return ar;
33
- };
34
- return ownKeys(o);
35
- };
36
- return function (mod) {
37
- if (mod && mod.__esModule) return mod;
38
- var result = {};
39
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
- __setModuleDefault(result, mod);
41
- return result;
42
- };
43
- })();
44
13
  Object.defineProperty(exports, "__esModule", { value: true });
45
14
  exports.Buffer = void 0;
46
15
  const buffer_1 = require("buffer");
47
16
  Object.defineProperty(exports, "Buffer", { enumerable: true, get: function () { return buffer_1.Buffer; } });
17
+ const platformCrypto_1 = require("../utils/platformCrypto");
48
18
  const getGlobalObject = () => {
49
19
  if (typeof globalThis !== 'undefined')
50
20
  return globalThis;
@@ -61,37 +31,19 @@ const globalObject = getGlobalObject();
61
31
  if (!globalObject.Buffer) {
62
32
  globalObject.Buffer = buffer_1.Buffer;
63
33
  }
64
- // Cache for expo-crypto module (lazy loaded only in React Native)
65
- let expoCryptoModule = null;
66
- let expoCryptoLoadPromise = null;
67
34
  /**
68
- * Eagerly start loading expo-crypto. The module is cached once resolved so
69
- * the synchronous getRandomValues shim can read from it immediately.
70
- * Uses dynamic import with variable indirection to prevent ESM bundlers
71
- * (Vite, webpack) from statically resolving the specifier.
35
+ * Synchronous random-bytes shim. On RN, this delegates to
36
+ * `expo-crypto.getRandomBytes` (statically imported by the RN variant of
37
+ * `platformCrypto`, so available without any async warm-up). On Node /
38
+ * browser, this throws but is never called there because both platforms
39
+ * already provide `globalThis.crypto.getRandomValues` natively.
72
40
  */
73
- function startExpoCryptoLoad() {
74
- if (expoCryptoLoadPromise)
75
- return;
76
- expoCryptoLoadPromise = (async () => {
77
- try {
78
- const moduleName = 'expo-crypto';
79
- expoCryptoModule = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
80
- }
81
- catch {
82
- // expo-crypto not available — expected in non-RN environments
83
- }
84
- })();
85
- }
86
41
  function getRandomBytesSync(byteCount) {
87
- // Kick off loading if not already started (should have been started at module init)
88
- startExpoCryptoLoad();
89
- if (expoCryptoModule) {
90
- return expoCryptoModule.getRandomBytes(byteCount);
91
- }
92
- throw new Error('No crypto.getRandomValues implementation available. ' +
93
- 'In React Native, install expo-crypto. ' +
94
- 'If expo-crypto is installed, ensure the polyfill module is imported early enough for the async load to complete.');
42
+ // `getRandomBytesRN` throws on non-RN platforms. That's fine: this
43
+ // function is only ever called as a fallback when the native
44
+ // `globalThis.crypto.getRandomValues` is missing, which on a normal
45
+ // Node/browser host never happens.
46
+ return (0, platformCrypto_1.getRandomBytesRN)(byteCount);
95
47
  }
96
48
  const cryptoPolyfill = {
97
49
  getRandomValues(array) {
@@ -103,11 +55,8 @@ const cryptoPolyfill = {
103
55
  };
104
56
  // Only polyfill if crypto or crypto.getRandomValues is not available
105
57
  if (typeof globalObject.crypto === 'undefined') {
106
- // Start loading expo-crypto eagerly so it is ready by the time getRandomValues is called
107
- startExpoCryptoLoad();
108
58
  globalObject.crypto = cryptoPolyfill;
109
59
  }
110
60
  else if (typeof globalObject.crypto.getRandomValues !== 'function') {
111
- startExpoCryptoLoad();
112
61
  globalObject.crypto.getRandomValues = cryptoPolyfill.getRandomValues;
113
62
  }
@@ -57,10 +57,15 @@ function toHex(data) {
57
57
  }
58
58
  class RecoveryPhraseService {
59
59
  /**
60
- * Generate a new identity with a recovery phrase
61
- * Returns the mnemonic phrase (should only be shown once to the user)
60
+ * Generate a new identity with a recovery phrase.
61
+ * The mnemonic phrase MUST be shown to the user exactly once after this
62
+ * call resolves — if it is lost, the account becomes unrecoverable.
63
+ *
64
+ * Refuses to overwrite an existing identity unless `options.overwrite === true`.
65
+ *
66
+ * @throws IdentityAlreadyExistsError if an identity already exists and overwrite is not set
62
67
  */
63
- static async generateIdentityWithRecovery() {
68
+ static async generateIdentityWithRecovery(options) {
64
69
  // Generate 128-bit entropy for 12-word mnemonic
65
70
  const mnemonic = bip39.generateMnemonic(128);
66
71
  // Derive private key from mnemonic
@@ -69,8 +74,11 @@ class RecoveryPhraseService {
69
74
  // Use first 32 bytes of seed as private key
70
75
  const seedSlice = seed.subarray ? seed.subarray(0, 32) : seed.slice(0, 32);
71
76
  const privateKeyHex = toHex(seedSlice);
72
- // Import the derived key pair
73
- const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex);
77
+ // Import the derived key pair. KeyManager.importKeyPair will refuse to
78
+ // clobber an existing identity unless overwrite is explicitly requested.
79
+ const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex, {
80
+ overwrite: options?.overwrite === true,
81
+ });
74
82
  return {
75
83
  phrase: mnemonic,
76
84
  words: mnemonic.split(' '),
@@ -78,15 +86,19 @@ class RecoveryPhraseService {
78
86
  };
79
87
  }
80
88
  /**
81
- * Generate a 24-word recovery phrase for higher security
89
+ * Generate a 24-word recovery phrase for higher security.
90
+ *
91
+ * Same overwrite-protection semantics as `generateIdentityWithRecovery`.
82
92
  */
83
- static async generateIdentityWithRecovery24() {
93
+ static async generateIdentityWithRecovery24(options) {
84
94
  // Generate 256-bit entropy for 24-word mnemonic
85
95
  const mnemonic = bip39.generateMnemonic(256);
86
96
  const seed = await bip39.mnemonicToSeed(mnemonic);
87
97
  const seedSlice = seed.subarray ? seed.subarray(0, 32) : seed.slice(0, 32);
88
98
  const privateKeyHex = toHex(seedSlice);
89
- const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex);
99
+ const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex, {
100
+ overwrite: options?.overwrite === true,
101
+ });
90
102
  return {
91
103
  phrase: mnemonic,
92
104
  words: mnemonic.split(' '),
@@ -94,9 +106,14 @@ class RecoveryPhraseService {
94
106
  };
95
107
  }
96
108
  /**
97
- * Restore an identity from a recovery phrase
109
+ * Restore an identity from a recovery phrase.
110
+ *
111
+ * Refuses to overwrite a DIFFERENT existing identity unless
112
+ * `options.overwrite === true`. Re-importing the same phrase that
113
+ * matches the current identity is always allowed (it's a no-op refresh
114
+ * of the backup record).
98
115
  */
99
- static async restoreFromPhrase(phrase) {
116
+ static async restoreFromPhrase(phrase, options) {
100
117
  // Normalize and validate the phrase
101
118
  const normalizedPhrase = phrase.trim().toLowerCase();
102
119
  if (!bip39.validateMnemonic(normalizedPhrase)) {
@@ -107,7 +124,9 @@ class RecoveryPhraseService {
107
124
  const seedSlice = seed.subarray ? seed.subarray(0, 32) : seed.slice(0, 32);
108
125
  const privateKeyHex = toHex(seedSlice);
109
126
  // Import and store the key pair
110
- const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex);
127
+ const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex, {
128
+ overwrite: options?.overwrite === true,
129
+ });
111
130
  return publicKey;
112
131
  }
113
132
  /**
@@ -5,78 +5,32 @@
5
5
  * Handles signing and verification of messages using ECDSA secp256k1.
6
6
  * Used for authenticating requests and proving identity ownership.
7
7
  */
8
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
8
  Object.defineProperty(exports, "__esModule", { value: true });
42
9
  exports.SignatureService = void 0;
43
10
  const elliptic_1 = require("elliptic");
44
11
  const keyManager_1 = require("./keyManager");
45
12
  const platform_1 = require("../utils/platform");
46
- // Lazy imports for platform-specific crypto
47
- let ExpoCrypto = null;
48
- let NodeCrypto = null;
13
+ const platformCrypto_1 = require("../utils/platformCrypto");
14
+ const loggerUtils_1 = require("../utils/loggerUtils");
15
+ const debugUtils_1 = require("../shared/utils/debugUtils");
49
16
  const ec = new elliptic_1.ec('secp256k1');
50
- async function initExpoCrypto() {
51
- if (!ExpoCrypto) {
52
- const moduleName = 'expo-crypto';
53
- ExpoCrypto = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
54
- }
55
- return ExpoCrypto;
56
- }
57
- async function initNodeCrypto() {
58
- if (!NodeCrypto) {
59
- const moduleName = 'crypto';
60
- NodeCrypto = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
61
- }
62
- return NodeCrypto;
63
- }
64
17
  /**
65
18
  * Compute SHA-256 hash of a string
66
19
  */
67
20
  async function sha256(message) {
68
21
  // In React Native, use expo-crypto
69
22
  if ((0, platform_1.isReactNative)()) {
70
- const Crypto = await initExpoCrypto();
23
+ const Crypto = await (0, platformCrypto_1.loadExpoCrypto)();
71
24
  return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, message);
72
25
  }
73
26
  if ((0, platform_1.isNodeJS)()) {
74
27
  try {
75
- const nodeCrypto = await initNodeCrypto();
28
+ const nodeCrypto = await (0, platformCrypto_1.loadNodeCrypto)();
76
29
  return nodeCrypto.createHash('sha256').update(message).digest('hex');
77
30
  }
78
- catch {
79
- // Fall through to Web Crypto API
31
+ catch (error) {
32
+ // Node crypto failed to load — log and fall through to Web Crypto API
33
+ loggerUtils_1.logger.warn('[oxy.crypto] Node crypto unavailable, falling back to Web Crypto', { component: 'SignatureService' }, error);
80
34
  }
81
35
  }
82
36
  // Browser: use Web Crypto API
@@ -94,7 +48,7 @@ class SignatureService {
94
48
  static async generateChallenge() {
95
49
  // In React Native, use expo-crypto
96
50
  if ((0, platform_1.isReactNative)()) {
97
- const Crypto = await initExpoCrypto();
51
+ const Crypto = await (0, platformCrypto_1.loadExpoCrypto)();
98
52
  const randomBytes = await Crypto.getRandomBytesAsync(32);
99
53
  return Array.from(new Uint8Array(randomBytes))
100
54
  .map((b) => b.toString(16).padStart(2, '0'))
@@ -102,11 +56,12 @@ class SignatureService {
102
56
  }
103
57
  if ((0, platform_1.isNodeJS)()) {
104
58
  try {
105
- const nodeCrypto = await initNodeCrypto();
59
+ const nodeCrypto = await (0, platformCrypto_1.loadNodeCrypto)();
106
60
  return nodeCrypto.randomBytes(32).toString('hex');
107
61
  }
108
- catch {
109
- // Fall through to Web Crypto API
62
+ catch (error) {
63
+ // Node crypto failed to load — log and fall through to Web Crypto API
64
+ loggerUtils_1.logger.warn('[oxy.crypto] Node crypto unavailable, falling back to Web Crypto', { component: 'SignatureService' }, error);
110
65
  }
111
66
  }
112
67
  // Browser: use Web Crypto API
@@ -147,6 +102,10 @@ class SignatureService {
147
102
  }
148
103
  /**
149
104
  * Verify a signature against a message and public key
105
+ *
106
+ * Returns false on any error (invalid signature, malformed input, etc.).
107
+ * Errors are logged at debug level so they're available when troubleshooting
108
+ * signature mismatches but don't surface to the caller.
150
109
  */
151
110
  static async verify(message, signature, publicKey) {
152
111
  try {
@@ -154,7 +113,10 @@ class SignatureService {
154
113
  const messageHash = await sha256(message);
155
114
  return key.verify(messageHash, signature);
156
115
  }
157
- catch {
116
+ catch (error) {
117
+ if ((0, debugUtils_1.isDev)()) {
118
+ loggerUtils_1.logger.debug('[oxy.crypto] verify() returned false', { component: 'SignatureService' }, error);
119
+ }
158
120
  return false;
159
121
  }
160
122
  }
@@ -180,7 +142,10 @@ class SignatureService {
180
142
  const messageHash = crypto.createHash('sha256').update(message).digest('hex');
181
143
  return key.verify(messageHash, signature);
182
144
  }
183
- catch {
145
+ catch (error) {
146
+ if ((0, debugUtils_1.isDev)()) {
147
+ loggerUtils_1.logger.debug('[oxy.crypto] verifySync() returned false', { component: 'SignatureService' }, error);
148
+ }
184
149
  return false;
185
150
  }
186
151
  }
@@ -655,6 +655,21 @@
655
655
  "confirms": {
656
656
  "removeAvatar": "Remove your profile picture?"
657
657
  },
658
+ "crop": {
659
+ "title": "Crop avatar",
660
+ "subtitle": "Pinch to zoom, drag to position",
661
+ "noImage": "No image to crop",
662
+ "reset": "Reset",
663
+ "resetToCenter": "Reset to center",
664
+ "confirm": "Use photo",
665
+ "cancel": "Cancel",
666
+ "saving": "Saving…",
667
+ "helper": "The cropped circle is what will appear on your profile. Pinch to zoom, drag to position.",
668
+ "zoom": "{{value}}×",
669
+ "a11yImage": "Crop preview. Pinch to zoom and drag to reposition the image.",
670
+ "a11yReset": "Reset crop to default position",
671
+ "a11yResetAnnouncement": "Crop reset"
672
+ },
658
673
  "toasts": {
659
674
  "profileUpdated": "Profile updated successfully",
660
675
  "updateFailed": "Failed to update profile",
@@ -664,7 +679,10 @@
664
679
  "avatarSelected": "Avatar selected",
665
680
  "avatarUpdated": "Avatar updated",
666
681
  "updateAvatarFailed": "Failed to update avatar",
667
- "noActiveSession": "No active session"
682
+ "noActiveSession": "No active session",
683
+ "cropMeasureFailed": "Could not measure the image",
684
+ "cropNotReady": "Image not ready yet",
685
+ "cropFailed": "Failed to crop image"
668
686
  }
669
687
  },
670
688
  "accountOverview": {
@@ -1046,6 +1064,8 @@
1046
1064
  "save": "Save",
1047
1065
  "saved": "Saved successfully",
1048
1066
  "saving": "Saving...",
1067
+ "unnamed": "Unnamed",
1068
+ "accountFallback": "Account {{handle}}",
1049
1069
  "links": {
1050
1070
  "recoverAccount": "Recover your account",
1051
1071
  "signUp": "Sign Up"
@@ -1308,9 +1328,34 @@
1308
1328
  "loadingMore": "Loading more...",
1309
1329
  "loadingPhotoLayout": "Loading photo layout...",
1310
1330
  "uploading": "Uploading",
1331
+ "upload": "Upload",
1332
+ "uploadPhoto": "Upload Photo",
1311
1333
  "uploadPhotos": "Upload Photos",
1312
1334
  "uploadFiles": "Upload Files",
1313
1335
  "clearSearch": "Clear Search",
1336
+ "choosePhoto": "Choose Photo",
1337
+ "done": "Done",
1338
+ "doneWithCount": "Done ({{count}})",
1339
+ "photoPicker": {
1340
+ "emptyTitle": "No photos yet",
1341
+ "emptySubtitle": "Upload from your device to get started"
1342
+ },
1343
+ "a11y": {
1344
+ "viewAll": "Show all files",
1345
+ "viewPhotos": "Show photos only",
1346
+ "viewVideos": "Show videos only",
1347
+ "viewDocuments": "Show documents only",
1348
+ "viewAudio": "Show audio only",
1349
+ "sortBy": "Sort by {{field}}, {{order}}",
1350
+ "uploadFile": "Upload file from device",
1351
+ "uploadFromDevice": "Upload photo from device",
1352
+ "photoCellSelected": "Photo {{name}}, selected",
1353
+ "photoCellUnselected": "Photo {{name}}, not selected",
1354
+ "selectionCount": "{{count}} photo selected",
1355
+ "selectionCount_plural": "{{count}} photos selected",
1356
+ "cancelPicker": "Cancel photo selection",
1357
+ "confirmSelection": "Confirm selection"
1358
+ },
1314
1359
  "emptyPhotos": {
1315
1360
  "title": "No Photos Yet",
1316
1361
  "ownDescription": "Upload photos to get started. You can select multiple photos at once.",
@@ -241,6 +241,21 @@
241
241
  "confirms": {
242
242
  "removeAvatar": "¿Eliminar tu foto de perfil?"
243
243
  },
244
+ "crop": {
245
+ "title": "Recortar avatar",
246
+ "subtitle": "Pellizca para acercar, arrastra para colocar",
247
+ "noImage": "No hay imagen para recortar",
248
+ "reset": "Restablecer",
249
+ "resetToCenter": "Restablecer al centro",
250
+ "confirm": "Usar foto",
251
+ "cancel": "Cancelar",
252
+ "saving": "Guardando…",
253
+ "helper": "El círculo recortado es lo que aparecerá en tu perfil. Pellizca para acercar, arrastra para colocar.",
254
+ "zoom": "{{value}}×",
255
+ "a11yImage": "Vista previa del recorte. Pellizca para acercar y arrastra para reposicionar la imagen.",
256
+ "a11yReset": "Restablecer el recorte a la posición predeterminada",
257
+ "a11yResetAnnouncement": "Recorte restablecido"
258
+ },
244
259
  "toasts": {
245
260
  "profileUpdated": "Perfil actualizado correctamente",
246
261
  "updateFailed": "Error al actualizar el perfil",
@@ -250,7 +265,10 @@
250
265
  "avatarSelected": "Avatar seleccionado",
251
266
  "avatarUpdated": "Avatar actualizado",
252
267
  "updateAvatarFailed": "Error al actualizar el avatar",
253
- "noActiveSession": "No hay sesión activa"
268
+ "noActiveSession": "No hay sesión activa",
269
+ "cropMeasureFailed": "No se pudo medir la imagen",
270
+ "cropNotReady": "La imagen aún no está lista",
271
+ "cropFailed": "No se pudo recortar la imagen"
254
272
  }
255
273
  },
256
274
  "common": {
@@ -270,6 +288,8 @@
270
288
  "save": "Guardar",
271
289
  "saved": "Guardado correctamente",
272
290
  "saving": "Guardando...",
291
+ "unnamed": "Sin nombre",
292
+ "accountFallback": "Cuenta {{handle}}",
273
293
  "links": {
274
294
  "recoverAccount": "Recuperar tu cuenta",
275
295
  "signUp": "Registrarse"
@@ -1296,9 +1316,34 @@
1296
1316
  "loadingMore": "Cargando más...",
1297
1317
  "loadingPhotoLayout": "Cargando diseño de fotos...",
1298
1318
  "uploading": "Subiendo",
1319
+ "upload": "Subir",
1320
+ "uploadPhoto": "Subir foto",
1299
1321
  "uploadPhotos": "Subir fotos",
1300
1322
  "uploadFiles": "Subir archivos",
1301
1323
  "clearSearch": "Limpiar búsqueda",
1324
+ "choosePhoto": "Elegir foto",
1325
+ "done": "Listo",
1326
+ "doneWithCount": "Listo ({{count}})",
1327
+ "photoPicker": {
1328
+ "emptyTitle": "Aún no hay fotos",
1329
+ "emptySubtitle": "Sube desde tu dispositivo para empezar"
1330
+ },
1331
+ "a11y": {
1332
+ "viewAll": "Mostrar todos los archivos",
1333
+ "viewPhotos": "Mostrar solo fotos",
1334
+ "viewVideos": "Mostrar solo vídeos",
1335
+ "viewDocuments": "Mostrar solo documentos",
1336
+ "viewAudio": "Mostrar solo audio",
1337
+ "sortBy": "Ordenar por {{field}}, {{order}}",
1338
+ "uploadFile": "Subir archivo desde el dispositivo",
1339
+ "uploadFromDevice": "Subir foto desde el dispositivo",
1340
+ "photoCellSelected": "Foto {{name}}, seleccionada",
1341
+ "photoCellUnselected": "Foto {{name}}, no seleccionada",
1342
+ "selectionCount": "{{count}} foto seleccionada",
1343
+ "selectionCount_plural": "{{count}} fotos seleccionadas",
1344
+ "cancelPicker": "Cancelar selección de foto",
1345
+ "confirmSelection": "Confirmar selección"
1346
+ },
1302
1347
  "emptyPhotos": {
1303
1348
  "title": "Aún no hay fotos",
1304
1349
  "ownDescription": "Sube fotos para empezar. Puedes seleccionar varias fotos a la vez.",
@@ -655,6 +655,21 @@
655
655
  "confirms": {
656
656
  "removeAvatar": "Remove your profile picture?"
657
657
  },
658
+ "crop": {
659
+ "title": "Crop avatar",
660
+ "subtitle": "Pinch to zoom, drag to position",
661
+ "noImage": "No image to crop",
662
+ "reset": "Reset",
663
+ "resetToCenter": "Reset to center",
664
+ "confirm": "Use photo",
665
+ "cancel": "Cancel",
666
+ "saving": "Saving…",
667
+ "helper": "The cropped circle is what will appear on your profile. Pinch to zoom, drag to position.",
668
+ "zoom": "{{value}}×",
669
+ "a11yImage": "Crop preview. Pinch to zoom and drag to reposition the image.",
670
+ "a11yReset": "Reset crop to default position",
671
+ "a11yResetAnnouncement": "Crop reset"
672
+ },
658
673
  "toasts": {
659
674
  "profileUpdated": "Profile updated successfully",
660
675
  "updateFailed": "Failed to update profile",
@@ -664,7 +679,10 @@
664
679
  "avatarSelected": "Avatar selected",
665
680
  "avatarUpdated": "Avatar updated",
666
681
  "updateAvatarFailed": "Failed to update avatar",
667
- "noActiveSession": "No active session"
682
+ "noActiveSession": "No active session",
683
+ "cropMeasureFailed": "Could not measure the image",
684
+ "cropNotReady": "Image not ready yet",
685
+ "cropFailed": "Failed to crop image"
668
686
  }
669
687
  },
670
688
  "accountOverview": {
@@ -1046,6 +1064,8 @@
1046
1064
  "save": "Save",
1047
1065
  "saved": "Saved successfully",
1048
1066
  "saving": "Saving...",
1067
+ "unnamed": "Unnamed",
1068
+ "accountFallback": "Account {{handle}}",
1049
1069
  "links": {
1050
1070
  "recoverAccount": "Recover your account",
1051
1071
  "signUp": "Sign Up"
@@ -1308,9 +1328,34 @@
1308
1328
  "loadingMore": "Loading more...",
1309
1329
  "loadingPhotoLayout": "Loading photo layout...",
1310
1330
  "uploading": "Uploading",
1331
+ "upload": "Upload",
1332
+ "uploadPhoto": "Upload Photo",
1311
1333
  "uploadPhotos": "Upload Photos",
1312
1334
  "uploadFiles": "Upload Files",
1313
1335
  "clearSearch": "Clear Search",
1336
+ "choosePhoto": "Choose Photo",
1337
+ "done": "Done",
1338
+ "doneWithCount": "Done ({{count}})",
1339
+ "photoPicker": {
1340
+ "emptyTitle": "No photos yet",
1341
+ "emptySubtitle": "Upload from your device to get started"
1342
+ },
1343
+ "a11y": {
1344
+ "viewAll": "Show all files",
1345
+ "viewPhotos": "Show photos only",
1346
+ "viewVideos": "Show videos only",
1347
+ "viewDocuments": "Show documents only",
1348
+ "viewAudio": "Show audio only",
1349
+ "sortBy": "Sort by {{field}}, {{order}}",
1350
+ "uploadFile": "Upload file from device",
1351
+ "uploadFromDevice": "Upload photo from device",
1352
+ "photoCellSelected": "Photo {{name}}, selected",
1353
+ "photoCellUnselected": "Photo {{name}}, not selected",
1354
+ "selectionCount": "{{count}} photo selected",
1355
+ "selectionCount_plural": "{{count}} photos selected",
1356
+ "cancelPicker": "Cancel photo selection",
1357
+ "confirmSelection": "Confirm selection"
1358
+ },
1314
1359
  "emptyPhotos": {
1315
1360
  "title": "No Photos Yet",
1316
1361
  "ownDescription": "Upload photos to get started. You can select multiple photos at once.",
@@ -241,6 +241,21 @@
241
241
  "confirms": {
242
242
  "removeAvatar": "¿Eliminar tu foto de perfil?"
243
243
  },
244
+ "crop": {
245
+ "title": "Recortar avatar",
246
+ "subtitle": "Pellizca para acercar, arrastra para colocar",
247
+ "noImage": "No hay imagen para recortar",
248
+ "reset": "Restablecer",
249
+ "resetToCenter": "Restablecer al centro",
250
+ "confirm": "Usar foto",
251
+ "cancel": "Cancelar",
252
+ "saving": "Guardando…",
253
+ "helper": "El círculo recortado es lo que aparecerá en tu perfil. Pellizca para acercar, arrastra para colocar.",
254
+ "zoom": "{{value}}×",
255
+ "a11yImage": "Vista previa del recorte. Pellizca para acercar y arrastra para reposicionar la imagen.",
256
+ "a11yReset": "Restablecer el recorte a la posición predeterminada",
257
+ "a11yResetAnnouncement": "Recorte restablecido"
258
+ },
244
259
  "toasts": {
245
260
  "profileUpdated": "Perfil actualizado correctamente",
246
261
  "updateFailed": "Error al actualizar el perfil",
@@ -250,7 +265,10 @@
250
265
  "avatarSelected": "Avatar seleccionado",
251
266
  "avatarUpdated": "Avatar actualizado",
252
267
  "updateAvatarFailed": "Error al actualizar el avatar",
253
- "noActiveSession": "No hay sesión activa"
268
+ "noActiveSession": "No hay sesión activa",
269
+ "cropMeasureFailed": "No se pudo medir la imagen",
270
+ "cropNotReady": "La imagen aún no está lista",
271
+ "cropFailed": "No se pudo recortar la imagen"
254
272
  }
255
273
  },
256
274
  "common": {
@@ -270,6 +288,8 @@
270
288
  "save": "Guardar",
271
289
  "saved": "Guardado correctamente",
272
290
  "saving": "Guardando...",
291
+ "unnamed": "Sin nombre",
292
+ "accountFallback": "Cuenta {{handle}}",
273
293
  "links": {
274
294
  "recoverAccount": "Recuperar tu cuenta",
275
295
  "signUp": "Registrarse"
@@ -1296,9 +1316,34 @@
1296
1316
  "loadingMore": "Cargando más...",
1297
1317
  "loadingPhotoLayout": "Cargando diseño de fotos...",
1298
1318
  "uploading": "Subiendo",
1319
+ "upload": "Subir",
1320
+ "uploadPhoto": "Subir foto",
1299
1321
  "uploadPhotos": "Subir fotos",
1300
1322
  "uploadFiles": "Subir archivos",
1301
1323
  "clearSearch": "Limpiar búsqueda",
1324
+ "choosePhoto": "Elegir foto",
1325
+ "done": "Listo",
1326
+ "doneWithCount": "Listo ({{count}})",
1327
+ "photoPicker": {
1328
+ "emptyTitle": "Aún no hay fotos",
1329
+ "emptySubtitle": "Sube desde tu dispositivo para empezar"
1330
+ },
1331
+ "a11y": {
1332
+ "viewAll": "Mostrar todos los archivos",
1333
+ "viewPhotos": "Mostrar solo fotos",
1334
+ "viewVideos": "Mostrar solo vídeos",
1335
+ "viewDocuments": "Mostrar solo documentos",
1336
+ "viewAudio": "Mostrar solo audio",
1337
+ "sortBy": "Ordenar por {{field}}, {{order}}",
1338
+ "uploadFile": "Subir archivo desde el dispositivo",
1339
+ "uploadFromDevice": "Subir foto desde el dispositivo",
1340
+ "photoCellSelected": "Foto {{name}}, seleccionada",
1341
+ "photoCellUnselected": "Foto {{name}}, no seleccionada",
1342
+ "selectionCount": "{{count}} foto seleccionada",
1343
+ "selectionCount_plural": "{{count}} fotos seleccionadas",
1344
+ "cancelPicker": "Cancelar selección de foto",
1345
+ "confirmSelection": "Confirmar selección"
1346
+ },
1302
1347
  "emptyPhotos": {
1303
1348
  "title": "Aún no hay fotos",
1304
1349
  "ownDescription": "Sube fotos para empezar. Puedes seleccionar varias fotos a la vez.",