@oxyhq/core 9.2.3 → 10.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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/i18n/index.js +22 -2
- package/dist/cjs/i18n/locales/en-US.json +4 -1
- package/dist/cjs/i18n/locales/es-ES.json +4 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +4 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +4 -1
- package/dist/cjs/index.js +10 -5
- package/dist/cjs/mixins/OxyServices.assets.js +10 -6
- package/dist/cjs/mixins/OxyServices.language.js +11 -10
- package/dist/cjs/mixins/OxyServices.privacy.js +6 -0
- package/dist/cjs/mixins/OxyServices.user.js +40 -0
- package/dist/cjs/server/safeFetch.js +3 -3
- package/dist/cjs/session/SessionClient.js +1 -1
- package/dist/cjs/shared/utils/colorUtils.js +9 -9
- package/dist/cjs/utils/languageUtils.js +195 -158
- package/dist/cjs/utils/platform.js +9 -2
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/i18n/index.js +22 -2
- package/dist/esm/i18n/locales/en-US.json +4 -1
- package/dist/esm/i18n/locales/es-ES.json +4 -1
- package/dist/esm/i18n/locales/locales/en-US.json +4 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +4 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/mixins/OxyServices.assets.js +10 -6
- package/dist/esm/mixins/OxyServices.language.js +12 -11
- package/dist/esm/mixins/OxyServices.privacy.js +6 -0
- package/dist/esm/mixins/OxyServices.user.js +40 -0
- package/dist/esm/server/safeFetch.js +3 -3
- package/dist/esm/session/SessionClient.js +1 -1
- package/dist/esm/shared/utils/colorUtils.js +9 -9
- package/dist/esm/utils/languageUtils.js +189 -156
- package/dist/esm/utils/platform.js +9 -2
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/mixins/OxyServices.language.d.ts +4 -4
- package/dist/types/mixins/OxyServices.user.d.ts +30 -0
- package/dist/types/models/interfaces.d.ts +8 -0
- package/dist/types/session/accountDialogController.d.ts +1 -1
- package/dist/types/utils/languageUtils.d.ts +86 -24
- package/package.json +3 -3
- package/src/HttpService.ts +2 -2
- package/src/crypto/keyManager.ts +3 -3
- package/src/crypto/recoveryPhrase.ts +1 -1
- package/src/i18n/index.ts +21 -2
- package/src/i18n/locales/en-US.json +4 -1
- package/src/i18n/locales/es-ES.json +4 -1
- package/src/index.ts +8 -2
- package/src/mixins/OxyServices.assets.ts +15 -11
- package/src/mixins/OxyServices.language.ts +31 -17
- package/src/mixins/OxyServices.privacy.ts +6 -0
- package/src/mixins/OxyServices.security.ts +1 -1
- package/src/mixins/OxyServices.user.ts +57 -0
- package/src/models/interfaces.ts +8 -0
- package/src/server/safeFetch.ts +3 -3
- package/src/session/SessionClient.ts +1 -1
- package/src/session/accountDialogController.ts +1 -1
- package/src/shared/utils/colorUtils.ts +11 -11
- package/src/shared/utils/errorUtils.ts +1 -1
- package/src/utils/__tests__/languageUtils.test.ts +219 -0
- package/src/utils/avatarUtils.ts +1 -1
- package/src/utils/languageUtils.ts +223 -162
- package/src/utils/platform.ts +21 -3
- package/src/utils/requestUtils.ts +3 -3
- package/src/utils/sessionUtils.ts +2 -2
package/dist/types/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type { ServiceTokenResponse } from './mixins/OxyServices.auth';
|
|
|
26
26
|
export type { CommonsSignInHandle, CommonsSignInStatus, CommonsApprovalInfo, CommonsSignInActionResult, } from './mixins/OxyServices.auth';
|
|
27
27
|
export type { ServiceApp, ServiceActingAsVerification } from './mixins/OxyServices.utility';
|
|
28
28
|
export type { ContactDiscoveryMatch, ContactDiscoveryResponse, } from './mixins/OxyServices.contacts';
|
|
29
|
-
export type { BulkFollowEntry, BulkFollowResult, BulkUnfollowEntry, BulkUnfollowResult, } from './mixins/OxyServices.user';
|
|
29
|
+
export type { BulkFollowEntry, BulkFollowResult, BulkUnfollowEntry, BulkUnfollowResult, ViewerGraph, } from './mixins/OxyServices.user';
|
|
30
30
|
export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData';
|
|
31
31
|
export { getNormalizedUserId, normalizeUserIdentity, normalizeUserIdentityOrNull, } from './utils/userIdentity';
|
|
32
32
|
export { getCanonicalUserHandle, getNormalizedUserHandle, } from './utils/userHandle';
|
|
@@ -58,8 +58,8 @@ export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferenc
|
|
|
58
58
|
export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
|
|
59
59
|
export { TopicType, TopicSource } from './models/Topic';
|
|
60
60
|
export type { TopicData, TopicTranslation } from './models/Topic';
|
|
61
|
-
export { SUPPORTED_LANGUAGES, getLanguageMetadata, getLanguageName, getNativeLanguageName,
|
|
62
|
-
export type {
|
|
61
|
+
export { SUPPORTED_LANGUAGES, FALLBACK_LOCALE, getBaseLanguage, normalizeLocale, isSupportedLocale, getLanguageMetadata, getLanguageName, getNativeLanguageName, isRTLLocale, getUserLanguages, getPrimaryLanguage, } from './utils/languageUtils';
|
|
62
|
+
export type { SupportedLanguage } from './utils/languageUtils';
|
|
63
63
|
export { getPlatformOS, setPlatformOS, isWeb, isNative, isIOS, isAndroid, isWebBrowser, } from './utils/platform';
|
|
64
64
|
export type { PlatformOS } from './utils/platform';
|
|
65
65
|
export { darkenColor, lightenColor, hexToRgb, rgbToHex, withOpacity, isLightColor, getContrastTextColor, } from './shared/utils/colorUtils';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SupportedLanguage } from '../utils/languageUtils';
|
|
2
2
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
3
3
|
export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
4
4
|
new (...args: any[]): {
|
|
@@ -11,9 +11,9 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
|
|
|
11
11
|
removeItem: (key: string) => Promise<void>;
|
|
12
12
|
}>;
|
|
13
13
|
/**
|
|
14
|
-
* Get the current
|
|
14
|
+
* Get the current locale from the user profile or local storage.
|
|
15
15
|
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
16
|
-
* @returns The current
|
|
16
|
+
* @returns The current BCP-47 locale (e.g., 'en-US') or null if not set
|
|
17
17
|
*/
|
|
18
18
|
getCurrentLanguage(storageKeyPrefix?: string): Promise<string | null>;
|
|
19
19
|
/**
|
|
@@ -21,7 +21,7 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
|
|
|
21
21
|
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
22
22
|
* @returns Language metadata object or null if not set
|
|
23
23
|
*/
|
|
24
|
-
getCurrentLanguageMetadata(storageKeyPrefix?: string): Promise<
|
|
24
|
+
getCurrentLanguageMetadata(storageKeyPrefix?: string): Promise<SupportedLanguage | null>;
|
|
25
25
|
/**
|
|
26
26
|
* Get the current language name (e.g., 'English')
|
|
27
27
|
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
@@ -21,6 +21,22 @@ export interface BulkFollowResult {
|
|
|
21
21
|
/** Number of users newly followed by this request. */
|
|
22
22
|
followedCount: number;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The authenticated viewer's OWN social graph, ids-only — the response of
|
|
26
|
+
* `GET /users/me/graph`. Consolidates the accounts the viewer follows, the
|
|
27
|
+
* subset who follow back (mutuals), and the accounts the viewer has blocked
|
|
28
|
+
* into one payload so a consumer can fetch its whole viewer graph in a single
|
|
29
|
+
* round trip instead of three. Each list is server-bounded; bare ids only (no
|
|
30
|
+
* hydrated DTOs) because the consumer hydrates/ranks itself.
|
|
31
|
+
*/
|
|
32
|
+
export interface ViewerGraph {
|
|
33
|
+
/** Accounts the viewer follows (most-recent first, bounded). */
|
|
34
|
+
followingIds: string[];
|
|
35
|
+
/** Accounts the viewer follows that ALSO follow the viewer back (bounded). */
|
|
36
|
+
mutualIds: string[];
|
|
37
|
+
/** Accounts the viewer has blocked (bounded). */
|
|
38
|
+
blockedIds: string[];
|
|
39
|
+
}
|
|
24
40
|
/** Per-user outcome returned by `POST /users/unfollow/bulk`. */
|
|
25
41
|
export interface BulkUnfollowEntry {
|
|
26
42
|
/** The user ID that was processed. */
|
|
@@ -383,6 +399,20 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
383
399
|
getFollowsOfFollowsIds(params?: {
|
|
384
400
|
limit?: number;
|
|
385
401
|
}): Promise<string[]>;
|
|
402
|
+
/**
|
|
403
|
+
* Get the authenticated VIEWER's OWN social graph — the accounts they follow,
|
|
404
|
+
* the subset who follow back (mutuals), and the accounts they have blocked —
|
|
405
|
+
* as ONE ids-only payload. The viewer is derived server-side from the SDK's
|
|
406
|
+
* auth token (never a param).
|
|
407
|
+
*
|
|
408
|
+
* Consolidates what were three separate round trips (`getUserFollowing` /
|
|
409
|
+
* `getMutualUserIds` / `getBlockedUsers`) into a single request so a consumer
|
|
410
|
+
* can prime its whole viewer graph at once. Mirrors {@link getMutualUserIds}'s
|
|
411
|
+
* caching posture (2-minute identity-scoped cache); the follow/unfollow/block/
|
|
412
|
+
* unblock write methods bust this entry so a local mutation is reflected
|
|
413
|
+
* immediately. An anonymous caller resolves to empty lists.
|
|
414
|
+
*/
|
|
415
|
+
getViewerGraph(): Promise<ViewerGraph>;
|
|
386
416
|
/**
|
|
387
417
|
* Get notifications
|
|
388
418
|
*/
|
|
@@ -131,6 +131,14 @@ export interface User {
|
|
|
131
131
|
managedBy?: string;
|
|
132
132
|
/** Real-estate taxonomy when this user is a `kind: 'organization'` account. */
|
|
133
133
|
organizationCategory?: OrganizationCategory;
|
|
134
|
+
/**
|
|
135
|
+
* The account's languages as full BCP-47 locales (`language-REGION`, e.g.
|
|
136
|
+
* `en-US`, `es-MX`, `pt-BR`), ordered with the PRIMARY (UI) locale first.
|
|
137
|
+
* `languages[0]` is the primary locale — there is no singular `language`
|
|
138
|
+
* field. Resolve via `getUserLanguages` / `getPrimaryLanguage`, which
|
|
139
|
+
* normalize, validate against the supported catalog, and de-duplicate.
|
|
140
|
+
*/
|
|
141
|
+
languages?: string[];
|
|
134
142
|
notificationPreferences?: NotificationPreferences;
|
|
135
143
|
userPreferences?: UserPreferences;
|
|
136
144
|
[key: string]: unknown;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import type { OxyServices } from '../OxyServices';
|
|
29
29
|
import type { SessionLoginResponse, MinimalUserData } from '../models/session';
|
|
30
|
-
import { SessionClient } from './SessionClient';
|
|
30
|
+
import type { SessionClient } from './SessionClient';
|
|
31
31
|
import { type SwitchableAccount } from './accountProjection';
|
|
32
32
|
/** The dialog's top-level view. */
|
|
33
33
|
export type AccountDialogView = 'accounts' | 'signin' | 'qr' | 'add';
|
|
@@ -1,38 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Locale utilities for OxyServices.
|
|
3
|
+
*
|
|
4
|
+
* The Oxy platform models account languages as full BCP-47 locales
|
|
5
|
+
* (`language-REGION`, e.g. `es-ES`, `es-MX`, `pt-BR`). Region is significant:
|
|
6
|
+
* "Spanish (Spain)" is a different locale than "Spanish (Mexico)". A user's
|
|
7
|
+
* account locales live on `User.languages` as an ordered list with the PRIMARY
|
|
8
|
+
* (UI) locale first — there is no singular `language` field.
|
|
9
|
+
*
|
|
10
|
+
* This module is the single source of truth for the supported-locale catalog
|
|
11
|
+
* and every locale operation the SDK exposes: parsing base subtags, canonical
|
|
12
|
+
* normalization, validation, metadata lookup, and resolving a user's locales.
|
|
13
|
+
* It is pure and side-effect free.
|
|
4
14
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
15
|
+
import type { User } from '../models/interfaces';
|
|
16
|
+
/**
|
|
17
|
+
* A supported BCP-47 locale in the Oxy catalog.
|
|
18
|
+
*/
|
|
19
|
+
export interface SupportedLanguage {
|
|
20
|
+
/** Canonical BCP-47 locale tag, `language-REGION` (e.g. `'es-ES'`). */
|
|
21
|
+
code: string;
|
|
22
|
+
/** ISO 639-1 base language subtag, lowercased (e.g. `'es'`). */
|
|
23
|
+
language: string;
|
|
24
|
+
/** ISO 3166-1 alpha-2 region subtag, uppercased (e.g. `'ES'`). */
|
|
25
|
+
region: string;
|
|
26
|
+
/** English display name, `'Language (Region)'` (e.g. `'Spanish (Spain)'`). */
|
|
7
27
|
name: string;
|
|
28
|
+
/** Endonym — the name as written in the locale itself (e.g. `'Español (España)'`). */
|
|
8
29
|
nativeName: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
color: string;
|
|
30
|
+
/** `true` when the locale is written right-to-left. Omitted for LTR locales. */
|
|
31
|
+
rtl?: boolean;
|
|
12
32
|
}
|
|
13
|
-
export declare const SUPPORTED_LANGUAGES: LanguageMetadata[];
|
|
14
33
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
34
|
+
* The supported-locale catalog. Ordered by prominence for display; the order
|
|
35
|
+
* is not otherwise significant. Every entry is a full `language-REGION` tag,
|
|
36
|
+
* with the base subtag lowercased and the region uppercased.
|
|
37
|
+
*/
|
|
38
|
+
export declare const SUPPORTED_LANGUAGES: readonly SupportedLanguage[];
|
|
39
|
+
/** Canonical fallback locale used when a display value cannot be resolved. */
|
|
40
|
+
export declare const FALLBACK_LOCALE = "en-US";
|
|
41
|
+
/**
|
|
42
|
+
* Extract the base language subtag from a locale, lowercased.
|
|
43
|
+
*
|
|
44
|
+
* Tolerant of bare base subtags and of extra subtags (script/variant):
|
|
45
|
+
* `'es-ES'` → `'es'`, `'es'` → `'es'`, `'zh-Hant-TW'` → `'zh'`. Returns an
|
|
46
|
+
* empty string for empty input.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getBaseLanguage(locale: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Normalize a locale string to its canonical `language-REGION` form when it is
|
|
51
|
+
* a supported locale, otherwise `undefined`.
|
|
52
|
+
*
|
|
53
|
+
* Canonicalization lowercases the base subtag and uppercases the region
|
|
54
|
+
* subtag (`'es-es'` → `'es-ES'`, `'EN-us'` → `'en-US'`) and validates the
|
|
55
|
+
* result against {@link SUPPORTED_LANGUAGES}. A bare base subtag (`'es'`) has
|
|
56
|
+
* no region and is therefore not a locale — it returns `undefined`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function normalizeLocale(input: string): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Whether `input` resolves to a supported BCP-47 locale.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isSupportedLocale(input: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve catalog metadata for a locale.
|
|
65
|
+
*
|
|
66
|
+
* @param code - A locale tag (any case; e.g. `'es-ES'`, `'es-es'`).
|
|
67
|
+
* @returns The {@link SupportedLanguage} entry, or `null` when the tag is empty
|
|
68
|
+
* or not a supported locale.
|
|
18
69
|
*/
|
|
19
|
-
export declare function getLanguageMetadata(
|
|
70
|
+
export declare function getLanguageMetadata(code: string | null | undefined): SupportedLanguage | null;
|
|
20
71
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @returns Language name (e.g., 'English') or the code if not found
|
|
72
|
+
* English display name for a locale (e.g. `'Spanish (Spain)'`).
|
|
73
|
+
* Falls back to the input tag, then {@link FALLBACK_LOCALE}.
|
|
24
74
|
*/
|
|
25
|
-
export declare function getLanguageName(
|
|
75
|
+
export declare function getLanguageName(code: string | null | undefined): string;
|
|
26
76
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* @returns Native language name (e.g., 'Español') or the code if not found
|
|
77
|
+
* Native display name (endonym) for a locale (e.g. `'Español (España)'`).
|
|
78
|
+
* Falls back to the input tag, then {@link FALLBACK_LOCALE}.
|
|
30
79
|
*/
|
|
31
|
-
export declare function getNativeLanguageName(
|
|
80
|
+
export declare function getNativeLanguageName(code: string | null | undefined): string;
|
|
32
81
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @returns Normalized BCP-47 language code
|
|
82
|
+
* Whether a locale (or bare base subtag) is written right-to-left. Unknown
|
|
83
|
+
* tags are treated as left-to-right.
|
|
36
84
|
*/
|
|
37
|
-
export declare function normalizeLanguageCode(lang?: string | null): string;
|
|
38
85
|
export declare function isRTLLocale(locale?: string | null): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the ordered list of account locales for a user, primary first.
|
|
88
|
+
*
|
|
89
|
+
* Reads `user.languages` (the only source — there is no singular `language`
|
|
90
|
+
* field), then normalizes each entry to its canonical `language-REGION` form,
|
|
91
|
+
* drops non-string and unsupported entries, and de-duplicates while preserving
|
|
92
|
+
* first-seen order. Pure and side-effect free — never throws on bad input.
|
|
93
|
+
*/
|
|
94
|
+
export declare function getUserLanguages(user: Pick<User, 'languages'> | null | undefined): string[];
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the single PRIMARY locale for a user (the first entry from
|
|
97
|
+
* {@link getUserLanguages}), or `undefined` when the user has no supported
|
|
98
|
+
* locale.
|
|
99
|
+
*/
|
|
100
|
+
export declare function getPrimaryLanguage(user: Pick<User, 'languages'> | null | undefined): string | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -94,8 +94,8 @@
|
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
"dependencies": {
|
|
97
|
-
"@oxyhq/contracts": "^0.
|
|
98
|
-
"@oxyhq/protocol": "^0.1.
|
|
97
|
+
"@oxyhq/contracts": "^0.14.0",
|
|
98
|
+
"@oxyhq/protocol": "^0.1.5",
|
|
99
99
|
"bip39": "^3.1.0",
|
|
100
100
|
"buffer": "^6.0.3",
|
|
101
101
|
"elliptic": "^6.6.1",
|
package/src/HttpService.ts
CHANGED
|
@@ -232,7 +232,7 @@ export class HttpService {
|
|
|
232
232
|
private logger: SimpleLogger;
|
|
233
233
|
private config: OxyConfig;
|
|
234
234
|
private tokenRefreshPromise: Promise<string | null> | null = null;
|
|
235
|
-
private tokenRefreshCooldownUntil
|
|
235
|
+
private tokenRefreshCooldownUntil = 0;
|
|
236
236
|
private authRefreshHandler: AuthRefreshHandler | null = null;
|
|
237
237
|
private accessTokenProvider: AccessTokenProvider | null = null;
|
|
238
238
|
private deviceSecretMintInFlight: Promise<DeviceSecretMintOutcome> | null = null;
|
|
@@ -242,7 +242,7 @@ export class HttpService {
|
|
|
242
242
|
* re-emitted. Throttles the {@link CACHE_SOFT_MAX_ENTRIES} warning to at most
|
|
243
243
|
* one per {@link CACHE_SIZE_WARNING_THROTTLE_MS} window.
|
|
244
244
|
*/
|
|
245
|
-
private cacheSizeWarningSilentUntil
|
|
245
|
+
private cacheSizeWarningSilentUntil = 0;
|
|
246
246
|
|
|
247
247
|
/**
|
|
248
248
|
* Fan-out listeners notified on EVERY access-token change on this instance:
|
package/src/crypto/keyManager.ts
CHANGED
|
@@ -1178,9 +1178,9 @@ export class KeyManager {
|
|
|
1178
1178
|
* @param userConfirmed - If true, user has explicitly confirmed deletion (default: false)
|
|
1179
1179
|
*/
|
|
1180
1180
|
static async deleteIdentity(
|
|
1181
|
-
skipBackup
|
|
1182
|
-
force
|
|
1183
|
-
userConfirmed
|
|
1181
|
+
skipBackup = false,
|
|
1182
|
+
force = false,
|
|
1183
|
+
userConfirmed = false
|
|
1184
1184
|
): Promise<void> {
|
|
1185
1185
|
if (isWebPlatform()) {
|
|
1186
1186
|
return; // Identity storage is only available on native platforms, nothing to delete
|
|
@@ -160,7 +160,7 @@ export class RecoveryPhraseService {
|
|
|
160
160
|
/**
|
|
161
161
|
* Get suggestions for a partial word
|
|
162
162
|
*/
|
|
163
|
-
static getSuggestions(partial: string, limit
|
|
163
|
+
static getSuggestions(partial: string, limit = 5): string[] {
|
|
164
164
|
const lowerPartial = partial.toLowerCase();
|
|
165
165
|
return bip39.wordlists.english
|
|
166
166
|
.filter((word: string) => word.startsWith(lowerPartial))
|
package/src/i18n/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import jaJP from './locales/ja-JP.json';
|
|
|
9
9
|
import koKR from './locales/ko-KR.json';
|
|
10
10
|
import zhCN from './locales/zh-CN.json';
|
|
11
11
|
import arSA from './locales/ar-SA.json';
|
|
12
|
+
import { getBaseLanguage } from '../utils/languageUtils';
|
|
12
13
|
|
|
13
14
|
export type LocaleDict = Record<string, any>;
|
|
14
15
|
|
|
@@ -39,12 +40,30 @@ const DICTS: Record<string, LocaleDict> = {
|
|
|
39
40
|
|
|
40
41
|
const FALLBACK = 'en-US';
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a locale tag to the key of the dictionary that should serve it.
|
|
45
|
+
*
|
|
46
|
+
* Account locales are full BCP-47 tags (e.g. `es-MX`, `pt-BR`, `fr-CA`), but a
|
|
47
|
+
* translation dictionary is shipped per base language. Resolution order:
|
|
48
|
+
* 1. Exact dictionary for the tag (e.g. `es-ES`).
|
|
49
|
+
* 2. Dictionary for the base subtag (e.g. `es-MX` → `es`).
|
|
50
|
+
* 3. The English fallback.
|
|
51
|
+
*/
|
|
52
|
+
function resolveLang(locale: string | undefined): string {
|
|
53
|
+
if (locale) {
|
|
54
|
+
if (DICTS[locale]) return locale;
|
|
55
|
+
const base = getBaseLanguage(locale);
|
|
56
|
+
if (DICTS[base]) return base;
|
|
57
|
+
}
|
|
58
|
+
return FALLBACK;
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
function getNested(obj: any, path: string): any {
|
|
43
62
|
return path.split('.').reduce((acc, key) => (acc && acc[key] != null ? acc[key] : undefined), obj);
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
export function translate(locale: string | undefined, key: string, vars?: Record<string, string | number>): string {
|
|
47
|
-
const lang = locale
|
|
66
|
+
const lang = resolveLang(locale);
|
|
48
67
|
const dict = DICTS[lang] || DICTS[FALLBACK];
|
|
49
68
|
let val = getNested(dict, key);
|
|
50
69
|
// Per-key fallback to the English dictionary when a key is missing from the
|
|
@@ -64,6 +83,6 @@ export function translate(locale: string | undefined, key: string, vars?: Record
|
|
|
64
83
|
}
|
|
65
84
|
|
|
66
85
|
export function hasKey(locale: string | undefined, key: string): boolean {
|
|
67
|
-
const lang = locale
|
|
86
|
+
const lang = resolveLang(locale);
|
|
68
87
|
return getNested(DICTS[lang], key) != null || getNested(DICTS[FALLBACK], key) != null;
|
|
69
88
|
}
|
|
@@ -169,9 +169,12 @@
|
|
|
169
169
|
},
|
|
170
170
|
"language": {
|
|
171
171
|
"title": "Language",
|
|
172
|
-
"subtitle": "Choose your
|
|
172
|
+
"subtitle": "Choose the languages you use. The first is your primary interface language.",
|
|
173
173
|
"current": "Current Language",
|
|
174
|
+
"selected": "Your languages",
|
|
174
175
|
"available": "Available Languages",
|
|
176
|
+
"primary": "Primary",
|
|
177
|
+
"remove": "Remove",
|
|
175
178
|
"changed": "Language changed to {{lang}}",
|
|
176
179
|
"saveFailed": "Failed to save language preference",
|
|
177
180
|
"search": "Search languages"
|
|
@@ -123,9 +123,12 @@
|
|
|
123
123
|
},
|
|
124
124
|
"language": {
|
|
125
125
|
"title": "Idioma",
|
|
126
|
-
"subtitle": "Elige tu idioma
|
|
126
|
+
"subtitle": "Elige los idiomas que usas. El primero es tu idioma de interfaz principal.",
|
|
127
127
|
"current": "Idioma actual",
|
|
128
|
+
"selected": "Tus idiomas",
|
|
128
129
|
"available": "Idiomas disponibles",
|
|
130
|
+
"primary": "Principal",
|
|
131
|
+
"remove": "Quitar",
|
|
129
132
|
"changed": "Idioma cambiado a {{lang}}",
|
|
130
133
|
"saveFailed": "Error al guardar la preferencia de idioma",
|
|
131
134
|
"search": "Buscar idiomas"
|
package/src/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ export type {
|
|
|
53
53
|
BulkFollowResult,
|
|
54
54
|
BulkUnfollowEntry,
|
|
55
55
|
BulkUnfollowResult,
|
|
56
|
+
ViewerGraph,
|
|
56
57
|
} from './mixins/OxyServices.user';
|
|
57
58
|
export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData';
|
|
58
59
|
|
|
@@ -323,13 +324,18 @@ export type { TopicData, TopicTranslation } from './models/Topic';
|
|
|
323
324
|
// ---------------------------------------------------------------------------
|
|
324
325
|
export {
|
|
325
326
|
SUPPORTED_LANGUAGES,
|
|
327
|
+
FALLBACK_LOCALE,
|
|
328
|
+
getBaseLanguage,
|
|
329
|
+
normalizeLocale,
|
|
330
|
+
isSupportedLocale,
|
|
326
331
|
getLanguageMetadata,
|
|
327
332
|
getLanguageName,
|
|
328
333
|
getNativeLanguageName,
|
|
329
|
-
normalizeLanguageCode,
|
|
330
334
|
isRTLLocale,
|
|
335
|
+
getUserLanguages,
|
|
336
|
+
getPrimaryLanguage,
|
|
331
337
|
} from './utils/languageUtils';
|
|
332
|
-
export type {
|
|
338
|
+
export type { SupportedLanguage } from './utils/languageUtils';
|
|
333
339
|
|
|
334
340
|
// ---------------------------------------------------------------------------
|
|
335
341
|
// Platform detection
|
|
@@ -435,12 +435,16 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
435
435
|
if (error instanceof Error) {
|
|
436
436
|
errorMessage = error.message || errorMessage;
|
|
437
437
|
} else if (error && typeof error === 'object') {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
438
|
+
const errObj = error as Record<string, unknown>;
|
|
439
|
+
if ('message' in errObj) {
|
|
440
|
+
errorMessage = String(errObj.message) || errorMessage;
|
|
441
|
+
} else if (typeof errObj.error === 'string') {
|
|
442
|
+
errorMessage = errObj.error;
|
|
443
|
+
} else if (errObj.data && typeof errObj.data === 'object') {
|
|
444
|
+
const dataObj = errObj.data as Record<string, unknown>;
|
|
445
|
+
if (dataObj.message) {
|
|
446
|
+
errorMessage = String(dataObj.message);
|
|
447
|
+
}
|
|
444
448
|
}
|
|
445
449
|
} else if (error) {
|
|
446
450
|
errorMessage = String(error) || errorMessage;
|
|
@@ -462,8 +466,8 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
462
466
|
throw handledError;
|
|
463
467
|
}
|
|
464
468
|
|
|
465
|
-
const newError = new Error(errorMessage);
|
|
466
|
-
|
|
469
|
+
const newError: Error & { fileContext?: Record<string, unknown> } = new Error(errorMessage);
|
|
470
|
+
newError.fileContext = contextError.fileContext;
|
|
467
471
|
throw this.handleError(newError);
|
|
468
472
|
}
|
|
469
473
|
}
|
|
@@ -546,7 +550,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
546
550
|
/**
|
|
547
551
|
* Delete asset with optional force
|
|
548
552
|
*/
|
|
549
|
-
async assetDelete(fileId: string, force
|
|
553
|
+
async assetDelete(fileId: string, force = false): Promise<any> {
|
|
550
554
|
try {
|
|
551
555
|
const params: any = force ? { force: 'true' } : undefined;
|
|
552
556
|
const result = await this.makeRequest('DELETE', `/assets/${fileId}`, params, { cache: false });
|
|
@@ -590,7 +594,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
590
594
|
}
|
|
591
595
|
}
|
|
592
596
|
|
|
593
|
-
async uploadAvatar(file: AssetUploadInput, userId: string, app
|
|
597
|
+
async uploadAvatar(file: AssetUploadInput, userId: string, app = 'profiles'): Promise<any> {
|
|
594
598
|
try {
|
|
595
599
|
const asset = await this.assetUpload(file, 'public');
|
|
596
600
|
await this.assetLink(asset.file.id, app, 'avatar', userId, 'public');
|
|
@@ -600,7 +604,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
600
604
|
}
|
|
601
605
|
}
|
|
602
606
|
|
|
603
|
-
async uploadProfileBanner(file: AssetUploadInput, userId: string, app
|
|
607
|
+
async uploadProfileBanner(file: AssetUploadInput, userId: string, app = 'profiles'): Promise<any> {
|
|
604
608
|
try {
|
|
605
609
|
const asset = await this.assetUpload(file, 'public');
|
|
606
610
|
await this.assetLink(asset.file.id, app, 'profile-banner', userId, 'public');
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Language Methods Mixin
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
4
|
+
import { normalizeLocale, getPrimaryLanguage, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils';
|
|
5
|
+
import type { SupportedLanguage } from '../utils/languageUtils';
|
|
6
6
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
7
7
|
import { loadAsyncStorage } from '@oxyhq/protocol';
|
|
8
8
|
import { isDev } from '../shared/utils/debugUtils';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Cross-mixin surface consumed by the language methods. `getCurrentUser` is
|
|
12
|
+
* provided by the user mixin at runtime — both live on the composed
|
|
13
|
+
* `OxyServices` instance — but it is not visible on `OxyServicesBase` at the
|
|
14
|
+
* type level. We narrow `this` through this interface (mirroring the
|
|
15
|
+
* `OxyAuthInstance` pattern in `OxyServices.utility.ts`) instead of casting to
|
|
16
|
+
* `any`. Only the fields this mixin reads are declared, keeping it decoupled
|
|
17
|
+
* from the full `User` shape.
|
|
18
|
+
*/
|
|
19
|
+
interface LanguageMixinCrossAccess {
|
|
20
|
+
getCurrentUser(): Promise<{ languages?: string[] }>;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
export function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
11
24
|
return class extends Base {
|
|
12
25
|
constructor(...args: any[]) {
|
|
@@ -62,29 +75,30 @@ export function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
/**
|
|
65
|
-
* Get the current
|
|
78
|
+
* Get the current locale from the user profile or local storage.
|
|
66
79
|
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
67
|
-
* @returns The current
|
|
80
|
+
* @returns The current BCP-47 locale (e.g., 'en-US') or null if not set
|
|
68
81
|
*/
|
|
69
|
-
async getCurrentLanguage(storageKeyPrefix
|
|
82
|
+
async getCurrentLanguage(storageKeyPrefix = 'oxy_session'): Promise<string | null> {
|
|
70
83
|
try {
|
|
71
|
-
// First try
|
|
84
|
+
// First try the authenticated user's primary account locale.
|
|
72
85
|
try {
|
|
73
|
-
const user = await (this as
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
return
|
|
86
|
+
const user = await (this as unknown as LanguageMixinCrossAccess).getCurrentUser();
|
|
87
|
+
const primary = getPrimaryLanguage(user);
|
|
88
|
+
if (primary) {
|
|
89
|
+
return primary;
|
|
77
90
|
}
|
|
78
|
-
} catch
|
|
79
|
-
//
|
|
91
|
+
} catch {
|
|
92
|
+
// Not authenticated or the profile fetch failed — fall through to the
|
|
93
|
+
// locally stored preference below.
|
|
80
94
|
}
|
|
81
95
|
|
|
82
|
-
// Fall back to
|
|
96
|
+
// Fall back to the locally stored locale preference.
|
|
83
97
|
const storage = await this.getStorage();
|
|
84
98
|
const storageKey = `${storageKeyPrefix}_language`;
|
|
85
99
|
const storedLanguage = await storage.getItem(storageKey);
|
|
86
100
|
if (storedLanguage) {
|
|
87
|
-
return
|
|
101
|
+
return normalizeLocale(storedLanguage) ?? storedLanguage;
|
|
88
102
|
}
|
|
89
103
|
|
|
90
104
|
return null;
|
|
@@ -101,7 +115,7 @@ export function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
101
115
|
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
102
116
|
* @returns Language metadata object or null if not set
|
|
103
117
|
*/
|
|
104
|
-
async getCurrentLanguageMetadata(storageKeyPrefix
|
|
118
|
+
async getCurrentLanguageMetadata(storageKeyPrefix = 'oxy_session'): Promise<SupportedLanguage | null> {
|
|
105
119
|
const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
|
|
106
120
|
return getLanguageMetadata(languageCode);
|
|
107
121
|
}
|
|
@@ -111,7 +125,7 @@ export function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
111
125
|
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
112
126
|
* @returns Language name or null if not set
|
|
113
127
|
*/
|
|
114
|
-
async getCurrentLanguageName(storageKeyPrefix
|
|
128
|
+
async getCurrentLanguageName(storageKeyPrefix = 'oxy_session'): Promise<string | null> {
|
|
115
129
|
const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
|
|
116
130
|
if (!languageCode) return null;
|
|
117
131
|
return getLanguageName(languageCode);
|
|
@@ -122,7 +136,7 @@ export function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
122
136
|
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
123
137
|
* @returns Native language name or null if not set
|
|
124
138
|
*/
|
|
125
|
-
async getCurrentNativeLanguageName(storageKeyPrefix
|
|
139
|
+
async getCurrentNativeLanguageName(storageKeyPrefix = 'oxy_session'): Promise<string | null> {
|
|
126
140
|
const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
|
|
127
141
|
if (!languageCode) return null;
|
|
128
142
|
return getNativeLanguageName(languageCode);
|
|
@@ -82,6 +82,9 @@ export function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
82
82
|
cache: false,
|
|
83
83
|
});
|
|
84
84
|
this.clearCacheEntry('GET:/privacy/blocked');
|
|
85
|
+
// The block changed the viewer's graph (`blockedIds`) — bust the cached
|
|
86
|
+
// consolidated `GET /users/me/graph` so the next read reflects it.
|
|
87
|
+
this.clearCacheEntry('GET:/users/me/graph');
|
|
85
88
|
return result;
|
|
86
89
|
} catch (error) {
|
|
87
90
|
throw this.handleError(error);
|
|
@@ -105,6 +108,9 @@ export function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
105
108
|
cache: false,
|
|
106
109
|
});
|
|
107
110
|
this.clearCacheEntry('GET:/privacy/blocked');
|
|
111
|
+
// Symmetric to blockUser: the unblock changed the viewer's `blockedIds`,
|
|
112
|
+
// so bust the consolidated `GET /users/me/graph` cache.
|
|
113
|
+
this.clearCacheEntry('GET:/users/me/graph');
|
|
108
114
|
return result;
|
|
109
115
|
} catch (error) {
|
|
110
116
|
throw this.handleError(error);
|
|
@@ -57,7 +57,7 @@ export function OxyServicesSecurityMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
57
57
|
* @param limit - Number of recent events to fetch (default: 10)
|
|
58
58
|
* @returns Array of recent security activities
|
|
59
59
|
*/
|
|
60
|
-
async getRecentSecurityActivity(limit
|
|
60
|
+
async getRecentSecurityActivity(limit = 10): Promise<SecurityActivity[]> {
|
|
61
61
|
try {
|
|
62
62
|
const response = await this.getSecurityActivity(limit, 0);
|
|
63
63
|
return response.data || [];
|