@oxyhq/core 1.11.15 → 1.11.17

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/index.js CHANGED
@@ -29,8 +29,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
29
29
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
30
30
  };
31
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.isRateLimitError = exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = exports.darkenColor = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.isRTLLocale = exports.normalizeLanguageCode = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.SUPPORTED_LANGUAGES = exports.DeviceManager = exports.TopicSource = exports.TopicType = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.RecoveryPhraseService = exports.SignatureService = exports.KeyManager = exports.ServiceCredentialMismatchError = exports.createCrossDomainAuth = exports.CrossDomainAuth = exports.createAuthManager = exports.AuthManager = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
33
- exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.logPerformance = exports.logPayment = exports.logDevice = exports.logUser = exports.logSession = exports.logApi = exports.logAuth = exports.LogLevel = exports.logger = exports.retryAsync = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.packageInfo = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.translate = exports.createDebugLogger = exports.debugError = exports.debugWarn = exports.debugLog = exports.isDev = exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = void 0;
32
+ exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = exports.darkenColor = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.isRTLLocale = exports.normalizeLanguageCode = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.SUPPORTED_LANGUAGES = exports.DeviceManager = exports.TopicSource = exports.TopicType = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.RecoveryPhraseService = exports.SignatureService = exports.KeyManager = exports.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.createCrossDomainAuth = exports.CrossDomainAuth = exports.createAuthManager = exports.AuthManager = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
33
+ exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.logPerformance = exports.logPayment = exports.logDevice = exports.logUser = exports.logSession = exports.logApi = exports.logAuth = exports.LogLevel = exports.logger = exports.retryAsync = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.packageInfo = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.translate = exports.createDebugLogger = exports.debugError = exports.debugWarn = exports.debugLog = exports.isDev = exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = exports.isRateLimitError = void 0;
34
34
  // Ensure crypto polyfills are loaded before anything else
35
35
  require("./crypto/polyfill");
36
36
  // --- Core API Client ---
@@ -50,6 +50,8 @@ Object.defineProperty(exports, "CrossDomainAuth", { enumerable: true, get: funct
50
50
  Object.defineProperty(exports, "createCrossDomainAuth", { enumerable: true, get: function () { return CrossDomainAuth_1.createCrossDomainAuth; } });
51
51
  var OxyServices_auth_1 = require("./mixins/OxyServices.auth");
52
52
  Object.defineProperty(exports, "ServiceCredentialMismatchError", { enumerable: true, get: function () { return OxyServices_auth_1.ServiceCredentialMismatchError; } });
53
+ var OxyServices_appData_1 = require("./mixins/OxyServices.appData");
54
+ Object.defineProperty(exports, "OxyAppDataIdentifierError", { enumerable: true, get: function () { return OxyServices_appData_1.OxyAppDataIdentifierError; } });
53
55
  // --- Crypto / Identity ---
54
56
  var crypto_1 = require("./crypto");
55
57
  Object.defineProperty(exports, "KeyManager", { enumerable: true, get: function () { return crypto_1.KeyManager; } });
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ /**
3
+ * Per-user App-Data Mixin
4
+ *
5
+ * Thin client around `/users/me/app-data/...` — a generic per-user JSON KV
6
+ * store on the API. Authenticated callers can read, write, list, and delete
7
+ * entries scoped to their own user account.
8
+ *
9
+ * Identifier rules (must match the API):
10
+ * - Both `namespace` and `key` must match `/^[a-z0-9_-]{1,64}$/u`.
11
+ *
12
+ * Limits (enforced by the API):
13
+ * - Serialized JSON values are capped at 64 KB.
14
+ * - Writes are rate-limited to 100 / minute / user.
15
+ *
16
+ * Intended use cases are small bits of cross-device app state — e.g. Academy
17
+ * course progress, "last viewed" markers, dismissed banner flags. Do not use
18
+ * this for large blobs or anything that needs server-side querying; it's a
19
+ * write-it-and-read-it-back store.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.OxyAppDataIdentifierError = void 0;
23
+ exports.OxyServicesAppDataMixin = OxyServicesAppDataMixin;
24
+ /**
25
+ * Identifier validator — mirror of the API regex. Validating client-side
26
+ * gives consumers a clean throw before the request even leaves the device.
27
+ */
28
+ const APP_DATA_IDENTIFIER_PATTERN = /^[a-z0-9_-]{1,64}$/u;
29
+ /** Thrown when a namespace or key fails the kebab/snake-case validator. */
30
+ class OxyAppDataIdentifierError extends Error {
31
+ constructor(field, value) {
32
+ super(`Invalid app-data ${field} "${value}": must match [a-z0-9_-]{1,64} (lowercase letters, digits, dashes, underscores).`);
33
+ this.name = 'OxyAppDataIdentifierError';
34
+ }
35
+ }
36
+ exports.OxyAppDataIdentifierError = OxyAppDataIdentifierError;
37
+ function assertIdentifier(field, value) {
38
+ if (!APP_DATA_IDENTIFIER_PATTERN.test(value)) {
39
+ throw new OxyAppDataIdentifierError(field, value);
40
+ }
41
+ }
42
+ function OxyServicesAppDataMixin(Base) {
43
+ return class extends Base {
44
+ constructor(...args) {
45
+ super(...args);
46
+ }
47
+ /**
48
+ * Read the value stored under `(namespace, key)` for the current user.
49
+ *
50
+ * @returns The previously-stored value, or `null` if nothing has been
51
+ * stored yet. Never throws on "not found" — a missing entry is
52
+ * semantically a `null` value.
53
+ */
54
+ async getAppData(namespace, key) {
55
+ assertIdentifier('namespace', namespace);
56
+ assertIdentifier('key', key);
57
+ return this.withAuthRetry(async () => {
58
+ const response = await this.makeRequest('GET', `/users/me/app-data/${encodeURIComponent(namespace)}/${encodeURIComponent(key)}`, undefined, { cache: false });
59
+ return response?.value ?? null;
60
+ }, 'getAppData');
61
+ }
62
+ /**
63
+ * Upsert the value under `(namespace, key)` for the current user.
64
+ *
65
+ * Returns the value the server confirmed it stored — typically the same
66
+ * value the caller passed in, but consumers should prefer the returned
67
+ * value (the API is the source of truth).
68
+ *
69
+ * @throws OxyAppDataIdentifierError when namespace or key is malformed.
70
+ */
71
+ async setAppData(namespace, key, value) {
72
+ assertIdentifier('namespace', namespace);
73
+ assertIdentifier('key', key);
74
+ return this.withAuthRetry(async () => {
75
+ const response = await this.makeRequest('PUT', `/users/me/app-data/${encodeURIComponent(namespace)}/${encodeURIComponent(key)}`, { value }, { cache: false });
76
+ // The server echoes the stored value back; fall back to the caller's
77
+ // input only if the server somehow omitted it (defensive — the route
78
+ // always sets it).
79
+ return (response?.value ?? value);
80
+ }, 'setAppData');
81
+ }
82
+ /**
83
+ * Delete the value stored under `(namespace, key)` for the current user.
84
+ *
85
+ * Idempotent — resolves successfully whether or not the entry existed.
86
+ */
87
+ async deleteAppData(namespace, key) {
88
+ assertIdentifier('namespace', namespace);
89
+ assertIdentifier('key', key);
90
+ await this.withAuthRetry(async () => {
91
+ await this.makeRequest('DELETE', `/users/me/app-data/${encodeURIComponent(namespace)}/${encodeURIComponent(key)}`, undefined, { cache: false });
92
+ }, 'deleteAppData');
93
+ }
94
+ /**
95
+ * List every entry stored under `namespace` for the current user.
96
+ *
97
+ * Returns an empty object when the namespace contains no entries (the
98
+ * endpoint never 404s on an empty namespace).
99
+ */
100
+ async listAppData(namespace) {
101
+ assertIdentifier('namespace', namespace);
102
+ return this.withAuthRetry(async () => {
103
+ const response = await this.makeRequest('GET', `/users/me/app-data/${encodeURIComponent(namespace)}`, undefined, { cache: false });
104
+ return response?.entries ?? {};
105
+ }, 'listAppData');
106
+ }
107
+ };
108
+ }
@@ -322,6 +322,11 @@ function OxyServicesAuthMixin(Base) {
322
322
  }
323
323
  /**
324
324
  * Get access token by session ID
325
+ *
326
+ * SECURITY: this endpoint requires the caller to already hold a
327
+ * bearer token whose user owns the referenced session (C1 hardening
328
+ * in the API). For the device-flow / QR sign-in case where the
329
+ * client has no bearer token yet, use `claimSessionByToken` instead.
325
330
  */
326
331
  async getTokenBySession(sessionId) {
327
332
  try {
@@ -333,6 +338,40 @@ function OxyServicesAuthMixin(Base) {
333
338
  throw this.handleError(error);
334
339
  }
335
340
  }
341
+ /**
342
+ * Exchange a device-flow sessionToken for the first access token.
343
+ *
344
+ * The originating client holds a 128-bit `sessionToken` that nobody
345
+ * else has seen — it was generated client-side, sent once on
346
+ * `POST /auth/session/create`, and is never echoed back. After
347
+ * another authenticated device approves the session via
348
+ * `POST /auth/session/authorize/{sessionToken}` (bearer-authed) and
349
+ * the auth socket / poll loop notifies this client, the client
350
+ * exchanges its `sessionToken` here for the first access token,
351
+ * refresh token, sessionId, and the authorized user.
352
+ *
353
+ * This call requires no Authorization header — the high-entropy
354
+ * `sessionToken` IS the credential (RFC 8628 §3.4). The exchange is
355
+ * single-use; replay attempts are rejected with 401.
356
+ *
357
+ * @param sessionToken - The same sessionToken the SDK passed to
358
+ * `POST /auth/session/create` at the start of the flow.
359
+ * @param options.deviceFingerprint - Optional fingerprint of the
360
+ * originating client device.
361
+ */
362
+ async claimSessionByToken(sessionToken, options = {}) {
363
+ try {
364
+ const res = await this.makeRequest('POST', '/auth/session/claim', {
365
+ sessionToken,
366
+ ...(options.deviceFingerprint ? { deviceFingerprint: options.deviceFingerprint } : {}),
367
+ }, { cache: false, retry: false });
368
+ this.setTokens(res.accessToken, res.refreshToken);
369
+ return res;
370
+ }
371
+ catch (error) {
372
+ throw this.handleError(error);
373
+ }
374
+ }
336
375
  /**
337
376
  * Get sessions by session ID
338
377
  */
@@ -29,6 +29,7 @@ const OxyServices_features_1 = require("./OxyServices.features");
29
29
  const OxyServices_topics_1 = require("./OxyServices.topics");
30
30
  const OxyServices_managedAccounts_1 = require("./OxyServices.managedAccounts");
31
31
  const OxyServices_contacts_1 = require("./OxyServices.contacts");
32
+ const OxyServices_appData_1 = require("./OxyServices.appData");
32
33
  /**
33
34
  * Mixin pipeline - applied in order from first to last.
34
35
  *
@@ -68,6 +69,7 @@ const MIXIN_PIPELINE = [
68
69
  OxyServices_topics_1.OxyServicesTopicsMixin,
69
70
  OxyServices_managedAccounts_1.OxyServicesManagedAccountsMixin,
70
71
  OxyServices_contacts_1.OxyServicesContactsMixin,
72
+ OxyServices_appData_1.OxyServicesAppDataMixin,
71
73
  // Utility (last, can use all above)
72
74
  OxyServices_utility_1.OxyServicesUtilityMixin,
73
75
  ];