@oxyhq/core 10.2.0 → 11.0.1
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/boot/sessionColdBoot.js +3 -3
- package/dist/cjs/crypto/keyManager.js +68 -69
- package/dist/cjs/crypto/signatureService.js +2 -2
- package/dist/cjs/index.js +17 -20
- package/dist/cjs/logger/index.js +149 -0
- package/dist/cjs/mixins/OxyServices.assets.js +4 -4
- package/dist/cjs/mixins/OxyServices.auth.js +104 -2
- package/dist/cjs/mixins/OxyServices.language.js +3 -5
- package/dist/cjs/mixins/OxyServices.privacy.js +2 -4
- package/dist/cjs/mixins/OxyServices.security.js +7 -11
- package/dist/cjs/mixins/OxyServices.topics.js +25 -15
- package/dist/cjs/mixins/OxyServices.user.js +4 -4
- package/dist/cjs/mixins/OxyServices.utility.js +18 -18
- package/dist/cjs/session/SessionClient.js +17 -17
- package/dist/cjs/session/accountDialogController.js +12 -12
- package/dist/cjs/session/authStateStore.js +5 -5
- package/dist/cjs/session/refresh.js +4 -4
- package/dist/cjs/session/socketLoader.js +2 -2
- package/dist/cjs/utils/asyncUtils.js +2 -2
- package/dist/cjs/utils/avatarUtils.js +2 -1
- package/dist/cjs/utils/deviceManager.js +7 -5
- package/dist/cjs/utils/errorUtils.js +3 -3
- package/dist/cjs/utils/oauthPkce.js +3 -3
- package/dist/cjs/utils/requestUtils.js +1 -1
- package/dist/cjs/utils/webauthnOrigin.js +51 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/boot/sessionColdBoot.js +1 -1
- package/dist/esm/crypto/keyManager.js +1 -2
- package/dist/esm/crypto/signatureService.js +1 -1
- package/dist/esm/index.js +6 -3
- package/dist/esm/logger/index.js +140 -0
- package/dist/esm/mixins/OxyServices.assets.js +2 -2
- package/dist/esm/mixins/OxyServices.auth.js +103 -1
- package/dist/esm/mixins/OxyServices.language.js +3 -5
- package/dist/esm/mixins/OxyServices.privacy.js +2 -4
- package/dist/esm/mixins/OxyServices.security.js +7 -11
- package/dist/esm/mixins/OxyServices.topics.js +25 -15
- package/dist/esm/mixins/OxyServices.user.js +1 -1
- package/dist/esm/mixins/OxyServices.utility.js +1 -1
- package/dist/esm/session/SessionClient.js +1 -1
- package/dist/esm/session/accountDialogController.js +1 -1
- package/dist/esm/session/authStateStore.js +1 -1
- package/dist/esm/session/refresh.js +1 -1
- package/dist/esm/session/socketLoader.js +1 -1
- package/dist/esm/utils/asyncUtils.js +1 -1
- package/dist/esm/utils/avatarUtils.js +2 -1
- package/dist/esm/utils/deviceManager.js +7 -5
- package/dist/esm/utils/errorUtils.js +1 -1
- package/dist/esm/utils/oauthPkce.js +1 -1
- package/dist/esm/utils/requestUtils.js +1 -1
- package/dist/esm/utils/webauthnOrigin.js +48 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/logger/index.d.ts +104 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +58 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +3 -3
- package/dist/types/models/Topic.d.ts +10 -0
- package/dist/types/utils/requestUtils.d.ts +1 -1
- package/dist/types/utils/webauthnOrigin.d.ts +32 -0
- package/package.json +17 -2
- package/src/boot/sessionColdBoot.ts +1 -1
- package/src/crypto/keyManager.ts +1 -2
- package/src/crypto/signatureService.ts +1 -1
- package/src/index.ts +22 -19
- package/src/logger/__tests__/logger.test.ts +207 -0
- package/src/logger/index.ts +217 -0
- package/src/mixins/OxyServices.assets.ts +3 -3
- package/src/mixins/OxyServices.auth.ts +134 -1
- package/src/mixins/OxyServices.language.ts +3 -5
- package/src/mixins/OxyServices.privacy.ts +2 -4
- package/src/mixins/OxyServices.security.ts +7 -11
- package/src/mixins/OxyServices.topics.ts +47 -17
- package/src/mixins/OxyServices.user.ts +1 -1
- package/src/mixins/OxyServices.utility.ts +1 -1
- package/src/mixins/__tests__/discoveryErrorHandling.test.ts +1 -1
- package/src/mixins/__tests__/topics.test.ts +156 -0
- package/src/mixins/__tests__/webauthnAuth.test.ts +206 -0
- package/src/models/Topic.ts +11 -0
- package/src/session/SessionClient.ts +1 -1
- package/src/session/__tests__/SessionClient.diagnostics.test.ts +1 -1
- package/src/session/__tests__/accountDialogController.test.ts +1 -1
- package/src/session/accountDialogController.ts +1 -1
- package/src/session/authStateStore.ts +1 -1
- package/src/session/refresh.ts +1 -1
- package/src/session/socketLoader.ts +1 -1
- package/src/utils/__tests__/webauthnOrigin.test.ts +83 -0
- package/src/utils/asyncUtils.ts +1 -1
- package/src/utils/avatarUtils.ts +3 -1
- package/src/utils/deviceManager.ts +8 -5
- package/src/utils/errorUtils.ts +1 -1
- package/src/utils/oauthPkce.ts +1 -1
- package/src/utils/requestUtils.ts +1 -1
- package/src/utils/webauthnOrigin.ts +52 -0
- package/dist/cjs/shared/utils/debugUtils.js +0 -80
- package/dist/cjs/utils/loggerUtils.js +0 -126
- package/dist/esm/shared/utils/debugUtils.js +0 -72
- package/dist/esm/utils/loggerUtils.js +0 -115
- package/dist/types/shared/utils/debugUtils.d.ts +0 -48
- package/dist/types/utils/loggerUtils.d.ts +0 -48
- package/src/shared/utils/__tests__/debugUtils.test.ts +0 -55
- package/src/shared/utils/debugUtils.ts +0 -78
- package/src/utils/loggerUtils.ts +0 -153
package/dist/types/index.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export type { DeviceFingerprint, StoredDeviceInfo } from './utils/deviceManager'
|
|
|
57
57
|
export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress, DeviceLinkedSession, DeviceLinkedSessionsResponse, DeviceLinkedSessionLogoutResponse, UpdateDeviceNameResponse, } from './models/interfaces';
|
|
58
58
|
export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
|
|
59
59
|
export { TopicType, TopicSource } from './models/Topic';
|
|
60
|
-
export type { TopicData, TopicTranslation } from './models/Topic';
|
|
60
|
+
export type { TopicData, TopicTranslation, TopicListResult } from './models/Topic';
|
|
61
61
|
export { SUPPORTED_LANGUAGES, FALLBACK_LOCALE, getBaseLanguage, normalizeLocale, isSupportedLocale, getLanguageMetadata, getLanguageName, getNativeLanguageName, isRTLLocale, getUserLanguages, getPrimaryLanguage, } from './utils/languageUtils';
|
|
62
62
|
export type { SupportedLanguage } from './utils/languageUtils';
|
|
63
63
|
export { getPlatformOS, setPlatformOS, isWeb, isNative, isIOS, isAndroid, isWebBrowser, } from './utils/platform';
|
|
@@ -68,7 +68,6 @@ export type { ThemeValue } from './shared/utils/themeUtils';
|
|
|
68
68
|
export { HttpStatus, getErrorStatus, getErrorMessage, isAlreadyRegisteredError, isUnauthorizedError, isForbiddenError, isNotFoundError, isRateLimitError, isServerError, isNetworkError, isRetryableError, } from './shared/utils/errorUtils';
|
|
69
69
|
export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBackoffInterval, recordFailure, recordSuccess, shouldAllowRequest, delay, withRetry, } from './shared/utils/networkUtils';
|
|
70
70
|
export type { CircuitBreakerState, CircuitBreakerConfig } from './shared/utils/networkUtils';
|
|
71
|
-
export { isDev, debugLog, debugWarn, debugError, createDebugLogger, } from './shared/utils/debugUtils';
|
|
72
71
|
export { translate } from './i18n';
|
|
73
72
|
export { buildSearchParams, buildUrl, buildPaginationParams, safeJsonParse, } from './utils/apiUtils';
|
|
74
73
|
export type { PaginationParams, ApiResponse, ErrorResponse, } from './utils/apiUtils';
|
|
@@ -76,13 +75,14 @@ export { ErrorCodes, createApiError, handleHttpError, validateRequiredFields, }
|
|
|
76
75
|
export { retryAsync } from './utils/asyncUtils';
|
|
77
76
|
export { EMAIL_REGEX, USERNAME_REGEX, PASSWORD_REGEX, isValidEmail, isValidUsername, isValidPassword, isValidDisplayName, isRequiredString, isRequiredNumber, isRequiredBoolean, isValidArray, isValidObject, isValidUUID, isValidURL, isValidDate, isValidFileSize, isValidFileType, sanitizeString, sanitizeHTML, isValidObjectId, validateAndSanitizeUserInput, } from './utils/validationUtils';
|
|
78
77
|
export { normalizeInlineText, normalizeMultilineText, } from './utils/textNormalization';
|
|
79
|
-
export { logger,
|
|
80
|
-
export type { LogContext } from './
|
|
78
|
+
export { logger, createLogger, configureLogger, getLoggerConfig, resetLoggerConfig, consoleSink, isDev, } from './logger';
|
|
79
|
+
export type { Logger, LogLevel, EmittableLogLevel, LogContext, LogEntry, LogSink, LoggerConfig, } from './logger';
|
|
81
80
|
export { updateAvatarVisibility } from './utils/avatarUtils';
|
|
82
81
|
export { buildAccountsArray, createQuickAccount, getAccountDisplayName, getAccountFallbackHandle, formatPublicKeyHandle, getAccountColor, } from './utils/accountUtils';
|
|
83
82
|
export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
|
|
84
83
|
export { registrableApex } from './utils/registrableApex';
|
|
85
84
|
export { CENTRAL_IDP_APEX } from './utils/authWebUrl';
|
|
85
|
+
export { isOxyRpOrigin } from './utils/webauthnOrigin';
|
|
86
86
|
export { runColdBoot } from './utils/coldBoot';
|
|
87
87
|
export type { ColdBootStep, ColdBootStepResult, ColdBootSession, ColdBootSkip, ColdBootOutcome, RunColdBootOptions, } from './utils/coldBoot';
|
|
88
88
|
export { buildOAuthAuthorizeUrl, computeCodeChallenge, generateOAuthState, generatePkcePair, DEFAULT_OAUTH_SCOPE, OXY_AUTHORIZE_URL, OXY_OAUTH_STATE_STORAGE_KEY, OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY, OXY_SILENT_OAUTH_ATTEMPTED_KEY, OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY, normalizeOAuthRedirectUri, persistOAuthHandshake, readOAuthHandshake, clearOAuthHandshake, } from './utils/oauthPkce';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxyhq/core/logger — the ecosystem-wide logging chokepoint.
|
|
3
|
+
*
|
|
4
|
+
* A tiny, dependency-free, universal logger that works unchanged in React
|
|
5
|
+
* Native, browsers, Node, and Bun. Every Oxy app and package should log
|
|
6
|
+
* through here instead of calling `console.*` directly, so that level,
|
|
7
|
+
* formatting, and transport are controlled in ONE place.
|
|
8
|
+
*
|
|
9
|
+
* Design goals:
|
|
10
|
+
* - Zero dependencies. No Node-only imports, no `process.stdout` assumptions.
|
|
11
|
+
* - Four levels (`debug` | `info` | `warn` | `error`) plus `silent`.
|
|
12
|
+
* - Namespaced child loggers via `createLogger('mention:feed')` / `.child()`.
|
|
13
|
+
* - Structured context objects carried through to the sink.
|
|
14
|
+
* - A single pluggable sink so backends can pipe to pino / CloudWatch / etc.
|
|
15
|
+
* without touching any call site.
|
|
16
|
+
* - Debug is off in production by default (`__DEV__` on RN, `NODE_ENV`
|
|
17
|
+
* elsewhere); override globally with `configureLogger({ level })`.
|
|
18
|
+
*
|
|
19
|
+
* @module logger
|
|
20
|
+
*/
|
|
21
|
+
/** Ordered severity levels. `silent` disables all output. */
|
|
22
|
+
export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
|
|
23
|
+
/** The levels that actually emit an entry (everything but `silent`). */
|
|
24
|
+
export type EmittableLogLevel = Exclude<LogLevel, 'silent'>;
|
|
25
|
+
/**
|
|
26
|
+
* Structured, JSON-friendly context attached to a log line. The well-known
|
|
27
|
+
* keys are optional hints; arbitrary keys are allowed so callers can attach
|
|
28
|
+
* whatever structured fields their sink cares about.
|
|
29
|
+
*/
|
|
30
|
+
export interface LogContext {
|
|
31
|
+
component?: string;
|
|
32
|
+
method?: string;
|
|
33
|
+
userId?: string;
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
requestId?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/** A single normalized log record handed to the active sink. */
|
|
39
|
+
export interface LogEntry {
|
|
40
|
+
level: EmittableLogLevel;
|
|
41
|
+
message: string;
|
|
42
|
+
/** Colon-joined namespace, e.g. `mention:feed`. Absent for the root logger. */
|
|
43
|
+
namespace?: string;
|
|
44
|
+
/** Merged structured context (logger base context + per-call context). */
|
|
45
|
+
context?: LogContext;
|
|
46
|
+
/** The error value passed to `.error(message, error, …)`, if any. */
|
|
47
|
+
error?: unknown;
|
|
48
|
+
/** Extra variadic args passed after the context. */
|
|
49
|
+
args: unknown[];
|
|
50
|
+
/** ISO-8601 timestamp of when the entry was created. */
|
|
51
|
+
timestamp: string;
|
|
52
|
+
}
|
|
53
|
+
/** A transport that receives every emitted (level-passing) entry. */
|
|
54
|
+
export type LogSink = (entry: LogEntry) => void;
|
|
55
|
+
/** Global logger configuration. */
|
|
56
|
+
export interface LoggerConfig {
|
|
57
|
+
level: LogLevel;
|
|
58
|
+
sink: LogSink;
|
|
59
|
+
}
|
|
60
|
+
/** A namespaced logger. Instances are cheap; derive children with `.child()`. */
|
|
61
|
+
export interface Logger {
|
|
62
|
+
/** The logger's colon-joined namespace, if any. */
|
|
63
|
+
readonly namespace?: string;
|
|
64
|
+
debug(message: string, context?: LogContext, ...args: unknown[]): void;
|
|
65
|
+
info(message: string, context?: LogContext, ...args: unknown[]): void;
|
|
66
|
+
warn(message: string, context?: LogContext, ...args: unknown[]): void;
|
|
67
|
+
error(message: string, error?: unknown, context?: LogContext, ...args: unknown[]): void;
|
|
68
|
+
/** Derive a child logger with an extended namespace and merged base context. */
|
|
69
|
+
child(namespace: string, context?: LogContext): Logger;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* True in development. Uses React Native's `__DEV__` when present, otherwise
|
|
73
|
+
* falls back to `process.env.NODE_ENV === 'development'`. Never throws.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isDev(): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Default sink: routes each entry to the matching `console` method. `info` and
|
|
78
|
+
* `debug` go to `console.log` (RN/browser-safe — `console.debug`/`console.info`
|
|
79
|
+
* are inconsistently surfaced across runtimes).
|
|
80
|
+
*/
|
|
81
|
+
export declare const consoleSink: LogSink;
|
|
82
|
+
/**
|
|
83
|
+
* Update the global logger configuration. Affects every logger instance
|
|
84
|
+
* (root and children) immediately. Pass `{ level }` to gate output and/or
|
|
85
|
+
* `{ sink }` to redirect transport (pino, CloudWatch, a test capture, …).
|
|
86
|
+
*/
|
|
87
|
+
export declare function configureLogger(partial: Partial<LoggerConfig>): void;
|
|
88
|
+
/** Read a snapshot of the current global configuration. */
|
|
89
|
+
export declare function getLoggerConfig(): Readonly<LoggerConfig>;
|
|
90
|
+
/** Restore the default level (env-derived) and the console sink. */
|
|
91
|
+
export declare function resetLoggerConfig(): void;
|
|
92
|
+
/** The shared root logger. Prefer a namespaced `createLogger(...)` in modules. */
|
|
93
|
+
export declare const logger: Logger;
|
|
94
|
+
/**
|
|
95
|
+
* Create a namespaced logger.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const log = createLogger('mention:feed');
|
|
100
|
+
* log.info('loaded', { count: 20 });
|
|
101
|
+
* const sub = log.child('prefetch'); // namespace → 'mention:feed:prefetch'
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export declare function createLogger(namespace?: string, context?: LogContext): Logger;
|
|
@@ -494,6 +494,64 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
494
494
|
deviceName?: string;
|
|
495
495
|
deviceId?: string;
|
|
496
496
|
}): Promise<LoginSessionResult>;
|
|
497
|
+
/**
|
|
498
|
+
* Begin a WebAuthn / passkey REGISTRATION ceremony. Requests the
|
|
499
|
+
* `PublicKeyCredentialCreationOptions` the browser's `navigator.credentials
|
|
500
|
+
* .create()` (or `@simplewebauthn/browser`'s `startRegistration`) needs.
|
|
501
|
+
*
|
|
502
|
+
* With a bearer token planted this links a passkey to the signed-in account
|
|
503
|
+
* (`username` ignored); without one it is a prospective signup and `username`
|
|
504
|
+
* is the desired handle. The returned options are OPAQUE — Oxy does not own
|
|
505
|
+
* their shape (the browser / `@simplewebauthn` does), so they pass through
|
|
506
|
+
* as `unknown` for the caller to hand straight to the ceremony.
|
|
507
|
+
*/
|
|
508
|
+
webauthnRegisterOptions(username?: string): Promise<unknown>;
|
|
509
|
+
/**
|
|
510
|
+
* Finish a WebAuthn / passkey REGISTRATION ceremony. Forwards the opaque
|
|
511
|
+
* browser `RegistrationResponseJSON` (`response`) alongside the Oxy envelope
|
|
512
|
+
* (desired `username` for signup + the device-session naming fields).
|
|
513
|
+
*
|
|
514
|
+
* Two server branches, disambiguated by the response shape:
|
|
515
|
+
* - **Signup** (no bearer): the account is created and a session minted —
|
|
516
|
+
* the response carries `sessionId`, is the SAME {@link LoginResult}
|
|
517
|
+
* contract as `POST /auth/verify`, and its access token is planted here.
|
|
518
|
+
* - **Link** (bearer present): the passkey is attached to the signed-in
|
|
519
|
+
* account and the server returns `{ success, message }` with no session,
|
|
520
|
+
* which is returned verbatim (no token planting).
|
|
521
|
+
*/
|
|
522
|
+
webauthnRegisterVerify(response: unknown, envelope?: {
|
|
523
|
+
username?: string;
|
|
524
|
+
deviceName?: string;
|
|
525
|
+
deviceFingerprint?: string;
|
|
526
|
+
deviceId?: string;
|
|
527
|
+
}): Promise<{
|
|
528
|
+
success: true;
|
|
529
|
+
message: string;
|
|
530
|
+
} | LoginResult>;
|
|
531
|
+
/**
|
|
532
|
+
* Begin a WebAuthn / passkey AUTHENTICATION ceremony. Requests the
|
|
533
|
+
* `PublicKeyCredentialRequestOptions` the browser's `navigator.credentials
|
|
534
|
+
* .get()` (or `@simplewebauthn/browser`'s `startAuthentication`) needs.
|
|
535
|
+
*
|
|
536
|
+
* When `username` is present the server scopes `allowCredentials` to that
|
|
537
|
+
* user's passkeys (username-first); when omitted it returns an empty
|
|
538
|
+
* allow-list for the usernameless / discoverable-credential flow. The
|
|
539
|
+
* returned options are OPAQUE and pass through as `unknown`.
|
|
540
|
+
*/
|
|
541
|
+
webauthnLoginOptions(username?: string): Promise<unknown>;
|
|
542
|
+
/**
|
|
543
|
+
* Finish a WebAuthn / passkey AUTHENTICATION ceremony. Forwards the opaque
|
|
544
|
+
* browser `AuthenticationResponseJSON` (`response`) alongside the
|
|
545
|
+
* device-session envelope. Resolves to the SAME {@link LoginResult} contract
|
|
546
|
+
* as `POST /auth/verify`; on the session arm the access token is planted
|
|
547
|
+
* immediately (mirroring {@link passwordSignIn}), and the response's
|
|
548
|
+
* `deviceId` + `deviceSecret` are the zero-cookie restore credential.
|
|
549
|
+
*/
|
|
550
|
+
webauthnLoginVerify(response: unknown, envelope?: {
|
|
551
|
+
deviceName?: string;
|
|
552
|
+
deviceFingerprint?: string;
|
|
553
|
+
deviceId?: string;
|
|
554
|
+
}): Promise<LoginResult>;
|
|
497
555
|
/**
|
|
498
556
|
* Exchange an OAuth authorization code (returned to the RP redirect URI
|
|
499
557
|
* after password sign-in at auth.oxy.so) for a device-first session.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides methods for topic discovery and management
|
|
5
5
|
*/
|
|
6
6
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
7
|
-
import type { TopicData, TopicTranslation } from '../models/Topic';
|
|
7
|
+
import type { TopicData, TopicListResult, TopicTranslation } from '../models/Topic';
|
|
8
8
|
export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
9
9
|
new (...args: any[]): {
|
|
10
10
|
/**
|
|
@@ -23,7 +23,7 @@ export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>
|
|
|
23
23
|
/**
|
|
24
24
|
* List topics with optional filters
|
|
25
25
|
* @param options - Filter and pagination options
|
|
26
|
-
* @returns
|
|
26
|
+
* @returns Paginated topics envelope (`topics` plus `total`/`limit`/`offset`)
|
|
27
27
|
*/
|
|
28
28
|
listTopics(options?: {
|
|
29
29
|
type?: string;
|
|
@@ -31,7 +31,7 @@ export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>
|
|
|
31
31
|
limit?: number;
|
|
32
32
|
offset?: number;
|
|
33
33
|
locale?: string;
|
|
34
|
-
}): Promise<
|
|
34
|
+
}): Promise<TopicListResult>;
|
|
35
35
|
/**
|
|
36
36
|
* Get a single topic by slug
|
|
37
37
|
* @param slug - Topic slug
|
|
@@ -30,3 +30,13 @@ export interface TopicData {
|
|
|
30
30
|
createdAt: string;
|
|
31
31
|
updatedAt: string;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Paginated result of {@link OxyServices.listTopics}. Mirrors the `GET /topics`
|
|
35
|
+
* envelope so callers keep access to `total`/`offset` for pagination.
|
|
36
|
+
*/
|
|
37
|
+
export interface TopicListResult {
|
|
38
|
+
topics: TopicData[];
|
|
39
|
+
total: number;
|
|
40
|
+
limit: number;
|
|
41
|
+
offset: number;
|
|
42
|
+
}
|
|
@@ -93,7 +93,7 @@ export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
|
93
93
|
* Simple logger with level support
|
|
94
94
|
*
|
|
95
95
|
* Lightweight logger for HTTP clients and utilities.
|
|
96
|
-
* For
|
|
96
|
+
* For structured, namespaced, level-gated logging, use `@oxyhq/core/logger`.
|
|
97
97
|
*
|
|
98
98
|
* @example
|
|
99
99
|
* ```typescript
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuthn relying-party origin guard (client side).
|
|
3
|
+
*
|
|
4
|
+
* The passkey ceremonies (`OxyServices.webauthn*`) are only meaningful when the
|
|
5
|
+
* page is served from a first-party Oxy web origin: a credential minted with
|
|
6
|
+
* `WEBAUTHN_RP_ID=oxy.so` can only be created/asserted from `oxy.so`, one of its
|
|
7
|
+
* subdomains, or a loopback dev server. This is the browser-side mirror of the
|
|
8
|
+
* server's `isOxyApexOrigin` (`packages/api/src/utils/origin.ts`), which forms
|
|
9
|
+
* the server's `expectedOrigin` allow-set — consumers use it to decide whether to
|
|
10
|
+
* even offer the passkey UI on the current page.
|
|
11
|
+
*
|
|
12
|
+
* It reads `globalThis.location` directly (no argument) because that is the only
|
|
13
|
+
* origin the browser will let a WebAuthn ceremony run against. On native / SSR /
|
|
14
|
+
* any environment without a DOM `location`, it returns `false` (there is no
|
|
15
|
+
* relying-party origin, so passkeys are not applicable).
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* True iff the current page's host is a first-party Oxy relying-party origin:
|
|
19
|
+
* `oxy.so`, any `*.oxy.so` subdomain, or a loopback dev host
|
|
20
|
+
* (`localhost` / `127.0.0.1` / `[::1]`).
|
|
21
|
+
*
|
|
22
|
+
* Fails closed: no `location` (native/SSR), a non-string/empty hostname, or a
|
|
23
|
+
* host that merely ends in the literal `oxy.so` without the dot boundary
|
|
24
|
+
* (`evil-oxy.so`, `oxy.so.evil.com`) all return `false`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* isOxyRpOrigin() // true on https://accounts.oxy.so
|
|
28
|
+
* isOxyRpOrigin() // true on http://localhost:8081
|
|
29
|
+
* isOxyRpOrigin() // false on https://evil.com
|
|
30
|
+
* isOxyRpOrigin() // false in a React Native / SSR context (no location)
|
|
31
|
+
*/
|
|
32
|
+
export declare function isOxyRpOrigin(): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.1",
|
|
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",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"*": {
|
|
10
10
|
"server": [
|
|
11
11
|
"dist/types/server/index.d.ts"
|
|
12
|
+
],
|
|
13
|
+
"logger": [
|
|
14
|
+
"dist/types/logger/index.d.ts"
|
|
12
15
|
]
|
|
13
16
|
}
|
|
14
17
|
},
|
|
@@ -41,6 +44,18 @@
|
|
|
41
44
|
},
|
|
42
45
|
"default": "./dist/esm/server/index.js"
|
|
43
46
|
},
|
|
47
|
+
"./logger": {
|
|
48
|
+
"react-native": "./dist/esm/logger/index.js",
|
|
49
|
+
"import": {
|
|
50
|
+
"types": "./dist/types/logger/index.d.ts",
|
|
51
|
+
"default": "./dist/esm/logger/index.js"
|
|
52
|
+
},
|
|
53
|
+
"require": {
|
|
54
|
+
"types": "./dist/types/logger/index.d.ts",
|
|
55
|
+
"default": "./dist/cjs/logger/index.js"
|
|
56
|
+
},
|
|
57
|
+
"default": "./dist/esm/logger/index.js"
|
|
58
|
+
},
|
|
44
59
|
"./package.json": "./package.json"
|
|
45
60
|
},
|
|
46
61
|
"files": [
|
|
@@ -94,7 +109,7 @@
|
|
|
94
109
|
}
|
|
95
110
|
},
|
|
96
111
|
"dependencies": {
|
|
97
|
-
"@oxyhq/contracts": "^0.14.
|
|
112
|
+
"@oxyhq/contracts": "^0.14.2",
|
|
98
113
|
"@oxyhq/protocol": "^0.1.5",
|
|
99
114
|
"bip39": "^3.1.0",
|
|
100
115
|
"buffer": "^6.0.3",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { runColdBoot, type ColdBootOutcome, type ColdBootStep } from '../utils/coldBoot';
|
|
24
24
|
import { isNative as detectNative } from '../utils/platform';
|
|
25
|
-
import { logger } from '../
|
|
25
|
+
import { logger } from '../logger';
|
|
26
26
|
import { TOKEN_REFRESH_LEAD_MS, refreshDeviceSecretArm } from '../session/refresh';
|
|
27
27
|
import type { OxyServices } from '../OxyServices';
|
|
28
28
|
import type { AuthStateStore } from '../session/authStateStore';
|
package/src/crypto/keyManager.ts
CHANGED
|
@@ -9,8 +9,7 @@ import { ec as EC } from 'elliptic';
|
|
|
9
9
|
import type { ECKeyPair } from 'elliptic';
|
|
10
10
|
import { isWeb, isIOS, isAndroid } from '../utils/platform';
|
|
11
11
|
import { type ExpoCryptoLike, type ExpoSecureStoreLike, isReactNative, isNodeJS, loadExpoCrypto, loadNodeCrypto, loadSecureStore, loadSharedIdentityBridge } from '@oxyhq/protocol';
|
|
12
|
-
import { logger } from '../
|
|
13
|
-
import { isDev } from '../shared/utils/debugUtils';
|
|
12
|
+
import { isDev, logger } from '../logger';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Options for expo-secure-store calls made by KeyManager.
|
package/src/index.ts
CHANGED
|
@@ -317,7 +317,7 @@ export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
|
|
|
317
317
|
|
|
318
318
|
// Topic enums + type
|
|
319
319
|
export { TopicType, TopicSource } from './models/Topic';
|
|
320
|
-
export type { TopicData, TopicTranslation } from './models/Topic';
|
|
320
|
+
export type { TopicData, TopicTranslation, TopicListResult } from './models/Topic';
|
|
321
321
|
|
|
322
322
|
// ---------------------------------------------------------------------------
|
|
323
323
|
// Languages
|
|
@@ -402,14 +402,6 @@ export {
|
|
|
402
402
|
} from './shared/utils/networkUtils';
|
|
403
403
|
export type { CircuitBreakerState, CircuitBreakerConfig } from './shared/utils/networkUtils';
|
|
404
404
|
|
|
405
|
-
export {
|
|
406
|
-
isDev,
|
|
407
|
-
debugLog,
|
|
408
|
-
debugWarn,
|
|
409
|
-
debugError,
|
|
410
|
-
createDebugLogger,
|
|
411
|
-
} from './shared/utils/debugUtils';
|
|
412
|
-
|
|
413
405
|
// ---------------------------------------------------------------------------
|
|
414
406
|
// i18n
|
|
415
407
|
// ---------------------------------------------------------------------------
|
|
@@ -475,20 +467,26 @@ export {
|
|
|
475
467
|
} from './utils/textNormalization';
|
|
476
468
|
|
|
477
469
|
// ---------------------------------------------------------------------------
|
|
478
|
-
// Logging
|
|
470
|
+
// Logging — the ecosystem-wide chokepoint (also at subpath `@oxyhq/core/logger`)
|
|
479
471
|
// ---------------------------------------------------------------------------
|
|
480
472
|
export {
|
|
481
473
|
logger,
|
|
474
|
+
createLogger,
|
|
475
|
+
configureLogger,
|
|
476
|
+
getLoggerConfig,
|
|
477
|
+
resetLoggerConfig,
|
|
478
|
+
consoleSink,
|
|
479
|
+
isDev,
|
|
480
|
+
} from './logger';
|
|
481
|
+
export type {
|
|
482
|
+
Logger,
|
|
482
483
|
LogLevel,
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
logPerformance,
|
|
490
|
-
} from './utils/loggerUtils';
|
|
491
|
-
export type { LogContext } from './utils/loggerUtils';
|
|
484
|
+
EmittableLogLevel,
|
|
485
|
+
LogContext,
|
|
486
|
+
LogEntry,
|
|
487
|
+
LogSink,
|
|
488
|
+
LoggerConfig,
|
|
489
|
+
} from './logger';
|
|
492
490
|
|
|
493
491
|
// ---------------------------------------------------------------------------
|
|
494
492
|
// Avatars
|
|
@@ -519,6 +517,11 @@ export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
|
|
|
519
517
|
export { registrableApex } from './utils/registrableApex';
|
|
520
518
|
export { CENTRAL_IDP_APEX } from './utils/authWebUrl';
|
|
521
519
|
|
|
520
|
+
// WebAuthn relying-party origin guard (client side). Mirrors the server's
|
|
521
|
+
// `isOxyApexOrigin` so consumers can decide whether to offer passkey UI on the
|
|
522
|
+
// current page (first-party Oxy origin / loopback only).
|
|
523
|
+
export { isOxyRpOrigin } from './utils/webauthnOrigin';
|
|
524
|
+
|
|
522
525
|
export { runColdBoot } from './utils/coldBoot';
|
|
523
526
|
export type {
|
|
524
527
|
ColdBootStep,
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configureLogger,
|
|
3
|
+
consoleSink,
|
|
4
|
+
createLogger,
|
|
5
|
+
getLoggerConfig,
|
|
6
|
+
isDev,
|
|
7
|
+
logger,
|
|
8
|
+
resetLoggerConfig,
|
|
9
|
+
type LogEntry,
|
|
10
|
+
} from '../index';
|
|
11
|
+
|
|
12
|
+
/** Install a capturing sink and return the collected entries. */
|
|
13
|
+
function captureEntries(): LogEntry[] {
|
|
14
|
+
const entries: LogEntry[] = [];
|
|
15
|
+
configureLogger({ sink: (entry) => entries.push(entry) });
|
|
16
|
+
return entries;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('logger', () => {
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
resetLoggerConfig();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('level gating', () => {
|
|
25
|
+
it('emits only levels at or below the configured level', () => {
|
|
26
|
+
const entries = captureEntries();
|
|
27
|
+
configureLogger({ level: 'warn' });
|
|
28
|
+
|
|
29
|
+
logger.debug('d');
|
|
30
|
+
logger.info('i');
|
|
31
|
+
logger.warn('w');
|
|
32
|
+
logger.error('e');
|
|
33
|
+
|
|
34
|
+
expect(entries.map((entry) => entry.level)).toEqual(['warn', 'error']);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('emits everything at debug level', () => {
|
|
38
|
+
const entries = captureEntries();
|
|
39
|
+
configureLogger({ level: 'debug' });
|
|
40
|
+
|
|
41
|
+
logger.debug('d');
|
|
42
|
+
logger.info('i');
|
|
43
|
+
logger.warn('w');
|
|
44
|
+
logger.error('e');
|
|
45
|
+
|
|
46
|
+
expect(entries.map((entry) => entry.level)).toEqual(['debug', 'info', 'warn', 'error']);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('emits nothing at silent level', () => {
|
|
50
|
+
const entries = captureEntries();
|
|
51
|
+
configureLogger({ level: 'silent' });
|
|
52
|
+
|
|
53
|
+
logger.debug('d');
|
|
54
|
+
logger.info('i');
|
|
55
|
+
logger.warn('w');
|
|
56
|
+
logger.error('e');
|
|
57
|
+
|
|
58
|
+
expect(entries).toHaveLength(0);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('namespacing', () => {
|
|
63
|
+
it('root logger has no namespace', () => {
|
|
64
|
+
const entries = captureEntries();
|
|
65
|
+
configureLogger({ level: 'debug' });
|
|
66
|
+
|
|
67
|
+
logger.info('hello');
|
|
68
|
+
|
|
69
|
+
expect(entries[0].namespace).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('createLogger sets the namespace', () => {
|
|
73
|
+
const entries = captureEntries();
|
|
74
|
+
configureLogger({ level: 'debug' });
|
|
75
|
+
|
|
76
|
+
createLogger('mention:feed').info('loaded');
|
|
77
|
+
|
|
78
|
+
expect(entries[0].namespace).toBe('mention:feed');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('child() colon-joins nested namespaces', () => {
|
|
82
|
+
const entries = captureEntries();
|
|
83
|
+
configureLogger({ level: 'debug' });
|
|
84
|
+
|
|
85
|
+
createLogger('mention:feed').child('prefetch').info('done');
|
|
86
|
+
|
|
87
|
+
expect(entries[0].namespace).toBe('mention:feed:prefetch');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('structured context', () => {
|
|
92
|
+
it('passes per-call context through to the sink', () => {
|
|
93
|
+
const entries = captureEntries();
|
|
94
|
+
configureLogger({ level: 'debug' });
|
|
95
|
+
|
|
96
|
+
logger.info('hi', { userId: 'u1', requestId: 'r1' });
|
|
97
|
+
|
|
98
|
+
expect(entries[0].context).toEqual({ userId: 'u1', requestId: 'r1' });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('merges base context with per-call context (per-call wins)', () => {
|
|
102
|
+
const entries = captureEntries();
|
|
103
|
+
configureLogger({ level: 'debug' });
|
|
104
|
+
|
|
105
|
+
createLogger('svc', { component: 'Svc', userId: 'base' }).warn('x', { userId: 'call', method: 'm' });
|
|
106
|
+
|
|
107
|
+
expect(entries[0].context).toEqual({ component: 'Svc', userId: 'call', method: 'm' });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('captures the error value on error()', () => {
|
|
111
|
+
const entries = captureEntries();
|
|
112
|
+
configureLogger({ level: 'debug' });
|
|
113
|
+
const boom = new Error('boom');
|
|
114
|
+
|
|
115
|
+
logger.error('failed', boom, { component: 'Svc' });
|
|
116
|
+
|
|
117
|
+
expect(entries[0].error).toBe(boom);
|
|
118
|
+
expect(entries[0].context).toEqual({ component: 'Svc' });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('forwards trailing variadic args', () => {
|
|
122
|
+
const entries = captureEntries();
|
|
123
|
+
configureLogger({ level: 'debug' });
|
|
124
|
+
|
|
125
|
+
logger.warn('x', { component: 'Svc' }, 1, 'two');
|
|
126
|
+
|
|
127
|
+
expect(entries[0].args).toEqual([1, 'two']);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('sink override', () => {
|
|
132
|
+
it('routes all output through the configured sink', () => {
|
|
133
|
+
const sink = jest.fn();
|
|
134
|
+
configureLogger({ level: 'debug', sink });
|
|
135
|
+
|
|
136
|
+
logger.info('routed');
|
|
137
|
+
|
|
138
|
+
expect(sink).toHaveBeenCalledTimes(1);
|
|
139
|
+
expect(sink.mock.calls[0][0]).toMatchObject({ level: 'info', message: 'routed' });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('resetLoggerConfig restores the console sink', () => {
|
|
143
|
+
configureLogger({ sink: jest.fn() });
|
|
144
|
+
resetLoggerConfig();
|
|
145
|
+
expect(getLoggerConfig().sink).toBe(consoleSink);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('consoleSink', () => {
|
|
150
|
+
it('routes error to console.error and warn to console.warn', () => {
|
|
151
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
152
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
153
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
154
|
+
const now = new Date().toISOString();
|
|
155
|
+
|
|
156
|
+
consoleSink({ level: 'error', message: 'e', args: [], timestamp: now });
|
|
157
|
+
consoleSink({ level: 'warn', message: 'w', args: [], timestamp: now });
|
|
158
|
+
consoleSink({ level: 'info', message: 'i', args: [], timestamp: now });
|
|
159
|
+
|
|
160
|
+
expect(errorSpy).toHaveBeenCalledTimes(1);
|
|
161
|
+
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
162
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
163
|
+
|
|
164
|
+
errorSpy.mockRestore();
|
|
165
|
+
warnSpy.mockRestore();
|
|
166
|
+
logSpy.mockRestore();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('isDev', () => {
|
|
171
|
+
// `isDev` reads the RN-injected global `__DEV__` first; model it as an
|
|
172
|
+
// optional global so we can exercise the NODE_ENV fallback without `as any`.
|
|
173
|
+
const globalWithDev = globalThis as typeof globalThis & { __DEV__?: boolean };
|
|
174
|
+
const originalDev = globalWithDev.__DEV__;
|
|
175
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
176
|
+
|
|
177
|
+
afterEach(() => {
|
|
178
|
+
// Setting to `undefined` makes `typeof __DEV__` read as 'undefined', the
|
|
179
|
+
// same signal `isDev` keys off — no need for the `delete` operator.
|
|
180
|
+
globalWithDev.__DEV__ = originalDev;
|
|
181
|
+
process.env.NODE_ENV = originalNodeEnv;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('returns true when __DEV__ is true (React Native)', () => {
|
|
185
|
+
globalWithDev.__DEV__ = true;
|
|
186
|
+
expect(isDev()).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('returns false when __DEV__ is false regardless of NODE_ENV', () => {
|
|
190
|
+
globalWithDev.__DEV__ = false;
|
|
191
|
+
process.env.NODE_ENV = 'development';
|
|
192
|
+
expect(isDev()).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('falls back to NODE_ENV=development when __DEV__ is undefined', () => {
|
|
196
|
+
globalWithDev.__DEV__ = undefined;
|
|
197
|
+
process.env.NODE_ENV = 'development';
|
|
198
|
+
expect(isDev()).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('returns false when NODE_ENV is production and __DEV__ is undefined', () => {
|
|
202
|
+
globalWithDev.__DEV__ = undefined;
|
|
203
|
+
process.env.NODE_ENV = 'production';
|
|
204
|
+
expect(isDev()).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|