@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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @oxyhq/core/logger — the ecosystem-wide logging chokepoint.
|
|
4
|
+
*
|
|
5
|
+
* A tiny, dependency-free, universal logger that works unchanged in React
|
|
6
|
+
* Native, browsers, Node, and Bun. Every Oxy app and package should log
|
|
7
|
+
* through here instead of calling `console.*` directly, so that level,
|
|
8
|
+
* formatting, and transport are controlled in ONE place.
|
|
9
|
+
*
|
|
10
|
+
* Design goals:
|
|
11
|
+
* - Zero dependencies. No Node-only imports, no `process.stdout` assumptions.
|
|
12
|
+
* - Four levels (`debug` | `info` | `warn` | `error`) plus `silent`.
|
|
13
|
+
* - Namespaced child loggers via `createLogger('mention:feed')` / `.child()`.
|
|
14
|
+
* - Structured context objects carried through to the sink.
|
|
15
|
+
* - A single pluggable sink so backends can pipe to pino / CloudWatch / etc.
|
|
16
|
+
* without touching any call site.
|
|
17
|
+
* - Debug is off in production by default (`__DEV__` on RN, `NODE_ENV`
|
|
18
|
+
* elsewhere); override globally with `configureLogger({ level })`.
|
|
19
|
+
*
|
|
20
|
+
* @module logger
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.logger = exports.consoleSink = void 0;
|
|
24
|
+
exports.isDev = isDev;
|
|
25
|
+
exports.configureLogger = configureLogger;
|
|
26
|
+
exports.getLoggerConfig = getLoggerConfig;
|
|
27
|
+
exports.resetLoggerConfig = resetLoggerConfig;
|
|
28
|
+
exports.createLogger = createLogger;
|
|
29
|
+
const LEVEL_WEIGHT = {
|
|
30
|
+
silent: 0,
|
|
31
|
+
error: 1,
|
|
32
|
+
warn: 2,
|
|
33
|
+
info: 3,
|
|
34
|
+
debug: 4,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* True in development. Uses React Native's `__DEV__` when present, otherwise
|
|
38
|
+
* falls back to `process.env.NODE_ENV === 'development'`. Never throws.
|
|
39
|
+
*/
|
|
40
|
+
function isDev() {
|
|
41
|
+
if (typeof __DEV__ !== 'undefined')
|
|
42
|
+
return __DEV__ === true;
|
|
43
|
+
try {
|
|
44
|
+
return typeof process !== 'undefined' && process.env?.NODE_ENV === 'development';
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function formatPrefix(entry) {
|
|
51
|
+
const parts = [entry.timestamp, entry.level.toUpperCase()];
|
|
52
|
+
if (entry.namespace)
|
|
53
|
+
parts.push(`[${entry.namespace}]`);
|
|
54
|
+
return parts.join(' ');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Default sink: routes each entry to the matching `console` method. `info` and
|
|
58
|
+
* `debug` go to `console.log` (RN/browser-safe — `console.debug`/`console.info`
|
|
59
|
+
* are inconsistently surfaced across runtimes).
|
|
60
|
+
*/
|
|
61
|
+
const consoleSink = (entry) => {
|
|
62
|
+
const extras = [];
|
|
63
|
+
if (entry.context && Object.keys(entry.context).length > 0)
|
|
64
|
+
extras.push(entry.context);
|
|
65
|
+
if (entry.error !== undefined)
|
|
66
|
+
extras.push(entry.error);
|
|
67
|
+
if (entry.args.length > 0)
|
|
68
|
+
extras.push(...entry.args);
|
|
69
|
+
const line = `${formatPrefix(entry)} ${entry.message}`;
|
|
70
|
+
if (entry.level === 'error')
|
|
71
|
+
console.error(line, ...extras);
|
|
72
|
+
else if (entry.level === 'warn')
|
|
73
|
+
console.warn(line, ...extras);
|
|
74
|
+
else
|
|
75
|
+
console.log(line, ...extras);
|
|
76
|
+
};
|
|
77
|
+
exports.consoleSink = consoleSink;
|
|
78
|
+
function defaultLevel() {
|
|
79
|
+
return isDev() ? 'debug' : 'info';
|
|
80
|
+
}
|
|
81
|
+
const config = {
|
|
82
|
+
level: defaultLevel(),
|
|
83
|
+
sink: exports.consoleSink,
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Update the global logger configuration. Affects every logger instance
|
|
87
|
+
* (root and children) immediately. Pass `{ level }` to gate output and/or
|
|
88
|
+
* `{ sink }` to redirect transport (pino, CloudWatch, a test capture, …).
|
|
89
|
+
*/
|
|
90
|
+
function configureLogger(partial) {
|
|
91
|
+
if (partial.level !== undefined)
|
|
92
|
+
config.level = partial.level;
|
|
93
|
+
if (partial.sink !== undefined)
|
|
94
|
+
config.sink = partial.sink;
|
|
95
|
+
}
|
|
96
|
+
/** Read a snapshot of the current global configuration. */
|
|
97
|
+
function getLoggerConfig() {
|
|
98
|
+
return { level: config.level, sink: config.sink };
|
|
99
|
+
}
|
|
100
|
+
/** Restore the default level (env-derived) and the console sink. */
|
|
101
|
+
function resetLoggerConfig() {
|
|
102
|
+
config.level = defaultLevel();
|
|
103
|
+
config.sink = exports.consoleSink;
|
|
104
|
+
}
|
|
105
|
+
function mergeContext(base, extra) {
|
|
106
|
+
if (!base)
|
|
107
|
+
return extra;
|
|
108
|
+
if (!extra)
|
|
109
|
+
return base;
|
|
110
|
+
return { ...base, ...extra };
|
|
111
|
+
}
|
|
112
|
+
function emit(level, namespace, baseContext, message, error, context, args) {
|
|
113
|
+
if (LEVEL_WEIGHT[level] > LEVEL_WEIGHT[config.level])
|
|
114
|
+
return;
|
|
115
|
+
config.sink({
|
|
116
|
+
level,
|
|
117
|
+
message,
|
|
118
|
+
namespace,
|
|
119
|
+
context: mergeContext(baseContext, context),
|
|
120
|
+
error,
|
|
121
|
+
args,
|
|
122
|
+
timestamp: new Date().toISOString(),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function makeLogger(namespace, baseContext) {
|
|
126
|
+
return {
|
|
127
|
+
namespace,
|
|
128
|
+
debug: (message, context, ...args) => emit('debug', namespace, baseContext, message, undefined, context, args),
|
|
129
|
+
info: (message, context, ...args) => emit('info', namespace, baseContext, message, undefined, context, args),
|
|
130
|
+
warn: (message, context, ...args) => emit('warn', namespace, baseContext, message, undefined, context, args),
|
|
131
|
+
error: (message, error, context, ...args) => emit('error', namespace, baseContext, message, error, context, args),
|
|
132
|
+
child: (childNamespace, childContext) => makeLogger(namespace ? `${namespace}:${childNamespace}` : childNamespace, mergeContext(baseContext, childContext)),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** The shared root logger. Prefer a namespaced `createLogger(...)` in modules. */
|
|
136
|
+
exports.logger = makeLogger(undefined, undefined);
|
|
137
|
+
/**
|
|
138
|
+
* Create a namespaced logger.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* const log = createLogger('mention:feed');
|
|
143
|
+
* log.info('loaded', { count: 20 });
|
|
144
|
+
* const sub = log.child('prefetch'); // namespace → 'mention:feed:prefetch'
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
function createLogger(namespace, context) {
|
|
148
|
+
return makeLogger(namespace, context);
|
|
149
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OxyServicesAssetsMixin = OxyServicesAssetsMixin;
|
|
4
4
|
const protocol_1 = require("@oxyhq/protocol");
|
|
5
|
-
const
|
|
5
|
+
const logger_1 = require("../logger");
|
|
6
6
|
const errorUtils_1 = require("../utils/errorUtils");
|
|
7
7
|
/**
|
|
8
8
|
* Maximum number of ids sent per `POST /assets/service/by-ids` request. Matches
|
|
@@ -215,7 +215,7 @@ function OxyServicesAssetsMixin(Base) {
|
|
|
215
215
|
return Array.isArray(entries) ? entries : [];
|
|
216
216
|
}
|
|
217
217
|
catch (error) {
|
|
218
|
-
|
|
218
|
+
logger_1.logger.warn('getServiceAssetMetadataByIds: chunk failed, continuing with remaining chunks', {
|
|
219
219
|
method: 'getServiceAssetMetadataByIds',
|
|
220
220
|
chunkSize: chunk.length,
|
|
221
221
|
status: (0, errorUtils_1.extractErrorStatus)(error),
|
|
@@ -278,7 +278,7 @@ function OxyServicesAssetsMixin(Base) {
|
|
|
278
278
|
return Array.isArray(entries) ? entries : [];
|
|
279
279
|
}
|
|
280
280
|
catch (error) {
|
|
281
|
-
|
|
281
|
+
logger_1.logger.warn('getServiceAssetMetadataBySha256: chunk failed, continuing with remaining chunks', {
|
|
282
282
|
method: 'getServiceAssetMetadataBySha256',
|
|
283
283
|
chunkSize: chunk.length,
|
|
284
284
|
status: (0, errorUtils_1.extractErrorStatus)(error),
|
|
@@ -362,7 +362,7 @@ function OxyServicesAssetsMixin(Base) {
|
|
|
362
362
|
return response;
|
|
363
363
|
}
|
|
364
364
|
catch (error) {
|
|
365
|
-
|
|
365
|
+
logger_1.logger.error('File upload error', error, { component: 'OxyServices.assets' });
|
|
366
366
|
let errorMessage = 'File upload failed';
|
|
367
367
|
if (error instanceof Error) {
|
|
368
368
|
errorMessage = error.message || errorMessage;
|
|
@@ -7,7 +7,7 @@ const OxyServices_errors_1 = require("../OxyServices.errors");
|
|
|
7
7
|
const keyManager_1 = require("../crypto/keyManager");
|
|
8
8
|
const signatureService_1 = require("../crypto/signatureService");
|
|
9
9
|
const protocol_1 = require("@oxyhq/protocol");
|
|
10
|
-
const
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
11
|
const userIdentity_1 = require("../utils/userIdentity");
|
|
12
12
|
/**
|
|
13
13
|
* Default lifetime of a "Sign in with Oxy" device-flow session / authorize code.
|
|
@@ -125,7 +125,7 @@ function OxyServicesAuthMixin(Base) {
|
|
|
125
125
|
: Buffer.alloc(storedSecretBuf.length);
|
|
126
126
|
const compareResult = nodeCrypto.timingSafeEqual(storedSecretBuf, compareBuf);
|
|
127
127
|
if (!lengthMatch || !compareResult) {
|
|
128
|
-
|
|
128
|
+
logger_1.logger.warn('[oxy.auth] Service token cache hit with mismatched secret', {
|
|
129
129
|
component: 'auth',
|
|
130
130
|
method: 'getServiceToken',
|
|
131
131
|
});
|
|
@@ -852,6 +852,108 @@ function OxyServicesAuthMixin(Base) {
|
|
|
852
852
|
throw this.handleError(error);
|
|
853
853
|
}
|
|
854
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Begin a WebAuthn / passkey REGISTRATION ceremony. Requests the
|
|
857
|
+
* `PublicKeyCredentialCreationOptions` the browser's `navigator.credentials
|
|
858
|
+
* .create()` (or `@simplewebauthn/browser`'s `startRegistration`) needs.
|
|
859
|
+
*
|
|
860
|
+
* With a bearer token planted this links a passkey to the signed-in account
|
|
861
|
+
* (`username` ignored); without one it is a prospective signup and `username`
|
|
862
|
+
* is the desired handle. The returned options are OPAQUE — Oxy does not own
|
|
863
|
+
* their shape (the browser / `@simplewebauthn` does), so they pass through
|
|
864
|
+
* as `unknown` for the caller to hand straight to the ceremony.
|
|
865
|
+
*/
|
|
866
|
+
async webauthnRegisterOptions(username) {
|
|
867
|
+
try {
|
|
868
|
+
return await this.makeRequest('POST', '/auth/webauthn/register/options', { ...(username !== undefined ? { username } : {}) }, { cache: false });
|
|
869
|
+
}
|
|
870
|
+
catch (error) {
|
|
871
|
+
throw this.handleError(error);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Finish a WebAuthn / passkey REGISTRATION ceremony. Forwards the opaque
|
|
876
|
+
* browser `RegistrationResponseJSON` (`response`) alongside the Oxy envelope
|
|
877
|
+
* (desired `username` for signup + the device-session naming fields).
|
|
878
|
+
*
|
|
879
|
+
* Two server branches, disambiguated by the response shape:
|
|
880
|
+
* - **Signup** (no bearer): the account is created and a session minted —
|
|
881
|
+
* the response carries `sessionId`, is the SAME {@link LoginResult}
|
|
882
|
+
* contract as `POST /auth/verify`, and its access token is planted here.
|
|
883
|
+
* - **Link** (bearer present): the passkey is attached to the signed-in
|
|
884
|
+
* account and the server returns `{ success, message }` with no session,
|
|
885
|
+
* which is returned verbatim (no token planting).
|
|
886
|
+
*/
|
|
887
|
+
async webauthnRegisterVerify(response, envelope = {}) {
|
|
888
|
+
try {
|
|
889
|
+
const res = await this.makeRequest('POST', '/auth/webauthn/register/verify', { response, ...envelope }, { cache: false });
|
|
890
|
+
if (res && typeof res === 'object') {
|
|
891
|
+
const record = res;
|
|
892
|
+
// Signup branch: mints a session (LoginSessionResult, carries
|
|
893
|
+
// `sessionId`). Parse against the login contract and plant the token.
|
|
894
|
+
if ('sessionId' in record) {
|
|
895
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.loginResultSchema, record);
|
|
896
|
+
if (!parsed) {
|
|
897
|
+
throw new Error('auth/webauthn/register/verify returned an unexpected response shape');
|
|
898
|
+
}
|
|
899
|
+
if (!('twoFactorRequired' in parsed) && parsed.accessToken) {
|
|
900
|
+
this.setTokens(parsed.accessToken);
|
|
901
|
+
}
|
|
902
|
+
return parsed;
|
|
903
|
+
}
|
|
904
|
+
// Link branch: passkey attached to the signed-in account, no session.
|
|
905
|
+
if (record.success === true && typeof record.message === 'string') {
|
|
906
|
+
return { success: true, message: record.message };
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
throw new Error('auth/webauthn/register/verify returned an unexpected response shape');
|
|
910
|
+
}
|
|
911
|
+
catch (error) {
|
|
912
|
+
throw this.handleError(error);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Begin a WebAuthn / passkey AUTHENTICATION ceremony. Requests the
|
|
917
|
+
* `PublicKeyCredentialRequestOptions` the browser's `navigator.credentials
|
|
918
|
+
* .get()` (or `@simplewebauthn/browser`'s `startAuthentication`) needs.
|
|
919
|
+
*
|
|
920
|
+
* When `username` is present the server scopes `allowCredentials` to that
|
|
921
|
+
* user's passkeys (username-first); when omitted it returns an empty
|
|
922
|
+
* allow-list for the usernameless / discoverable-credential flow. The
|
|
923
|
+
* returned options are OPAQUE and pass through as `unknown`.
|
|
924
|
+
*/
|
|
925
|
+
async webauthnLoginOptions(username) {
|
|
926
|
+
try {
|
|
927
|
+
return await this.makeRequest('POST', '/auth/webauthn/login/options', { ...(username !== undefined ? { username } : {}) }, { cache: false });
|
|
928
|
+
}
|
|
929
|
+
catch (error) {
|
|
930
|
+
throw this.handleError(error);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Finish a WebAuthn / passkey AUTHENTICATION ceremony. Forwards the opaque
|
|
935
|
+
* browser `AuthenticationResponseJSON` (`response`) alongside the
|
|
936
|
+
* device-session envelope. Resolves to the SAME {@link LoginResult} contract
|
|
937
|
+
* as `POST /auth/verify`; on the session arm the access token is planted
|
|
938
|
+
* immediately (mirroring {@link passwordSignIn}), and the response's
|
|
939
|
+
* `deviceId` + `deviceSecret` are the zero-cookie restore credential.
|
|
940
|
+
*/
|
|
941
|
+
async webauthnLoginVerify(response, envelope = {}) {
|
|
942
|
+
try {
|
|
943
|
+
const res = await this.makeRequest('POST', '/auth/webauthn/login/verify', { response, ...envelope }, { cache: false });
|
|
944
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.loginResultSchema, res);
|
|
945
|
+
if (!parsed) {
|
|
946
|
+
throw new Error('auth/webauthn/login/verify returned an unexpected response shape');
|
|
947
|
+
}
|
|
948
|
+
if (!('twoFactorRequired' in parsed) && parsed.accessToken) {
|
|
949
|
+
this.setTokens(parsed.accessToken);
|
|
950
|
+
}
|
|
951
|
+
return parsed;
|
|
952
|
+
}
|
|
953
|
+
catch (error) {
|
|
954
|
+
throw this.handleError(error);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
855
957
|
/**
|
|
856
958
|
* Exchange an OAuth authorization code (returned to the RP redirect URI
|
|
857
959
|
* after password sign-in at auth.oxy.so) for a device-first session.
|
|
@@ -6,7 +6,7 @@ exports.OxyServicesLanguageMixin = OxyServicesLanguageMixin;
|
|
|
6
6
|
*/
|
|
7
7
|
const languageUtils_1 = require("../utils/languageUtils");
|
|
8
8
|
const protocol_1 = require("@oxyhq/protocol");
|
|
9
|
-
const
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
10
|
function OxyServicesLanguageMixin(Base) {
|
|
11
11
|
return class extends Base {
|
|
12
12
|
constructor(...args) {
|
|
@@ -31,7 +31,7 @@ function OxyServicesLanguageMixin(Base) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
|
-
|
|
34
|
+
logger_1.logger.error('AsyncStorage not available in React Native', error, { component: 'OxyServices.language' });
|
|
35
35
|
throw new Error('AsyncStorage is required in React Native environment');
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -86,9 +86,7 @@ function OxyServicesLanguageMixin(Base) {
|
|
|
86
86
|
return null;
|
|
87
87
|
}
|
|
88
88
|
catch (error) {
|
|
89
|
-
|
|
90
|
-
console.warn('Failed to get current language:', error);
|
|
91
|
-
}
|
|
89
|
+
logger_1.logger.warn('Failed to get current language', { component: 'OxyServices.language' }, error);
|
|
92
90
|
return null;
|
|
93
91
|
}
|
|
94
92
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OxyServicesPrivacyMixin = OxyServicesPrivacyMixin;
|
|
4
|
-
const
|
|
4
|
+
const logger_1 = require("../logger");
|
|
5
5
|
function OxyServicesPrivacyMixin(Base) {
|
|
6
6
|
return class extends Base {
|
|
7
7
|
constructor(...args) {
|
|
@@ -29,9 +29,7 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
29
29
|
}
|
|
30
30
|
catch (error) {
|
|
31
31
|
// If there's an error, assume not in list to avoid breaking functionality
|
|
32
|
-
|
|
33
|
-
console.warn('Error checking user list:', error);
|
|
34
|
-
}
|
|
32
|
+
logger_1.logger.warn('Error checking user list', { component: 'OxyServices.privacy' }, error);
|
|
35
33
|
return false;
|
|
36
34
|
}
|
|
37
35
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OxyServicesSecurityMixin = OxyServicesSecurityMixin;
|
|
4
|
-
const
|
|
4
|
+
const logger_1 = require("../logger");
|
|
5
5
|
function OxyServicesSecurityMixin(Base) {
|
|
6
6
|
return class extends Base {
|
|
7
7
|
constructor(...args) {
|
|
@@ -65,11 +65,9 @@ function OxyServicesSecurityMixin(Base) {
|
|
|
65
65
|
await this.makeRequest('POST', '/security/activity/private-key-exported', { deviceId }, { cache: false });
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
|
-
// Don't throw - logging failures shouldn't break user flow
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
console.warn('[OxyServices] Failed to log private key exported event:', error);
|
|
72
|
-
}
|
|
68
|
+
// Don't throw - logging failures shouldn't break user flow, but surface
|
|
69
|
+
// for monitoring via the shared logger sink.
|
|
70
|
+
logger_1.logger.warn('[OxyServices] Failed to log private key exported event', { component: 'OxyServices.security' }, error);
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
73
|
/**
|
|
@@ -82,11 +80,9 @@ function OxyServicesSecurityMixin(Base) {
|
|
|
82
80
|
await this.makeRequest('POST', '/security/activity/backup-created', { deviceId }, { cache: false });
|
|
83
81
|
}
|
|
84
82
|
catch (error) {
|
|
85
|
-
// Don't throw - logging failures shouldn't break user flow
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
console.warn('[OxyServices] Failed to log backup created event:', error);
|
|
89
|
-
}
|
|
83
|
+
// Don't throw - logging failures shouldn't break user flow, but surface
|
|
84
|
+
// for monitoring via the shared logger sink.
|
|
85
|
+
logger_1.logger.warn('[OxyServices] Failed to log backup created event', { component: 'OxyServices.security' }, error);
|
|
90
86
|
}
|
|
91
87
|
}
|
|
92
88
|
};
|
|
@@ -17,10 +17,10 @@ function OxyServicesTopicsMixin(Base) {
|
|
|
17
17
|
const params = {};
|
|
18
18
|
if (locale)
|
|
19
19
|
params.locale = locale;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// `GET /topics/categories` returns `{ categories: TopicData[] }` — the
|
|
21
|
+
// SDK's `unwrapResponse` only unwraps `{ data }`, so unwrap here.
|
|
22
|
+
const response = await this.makeRequest('GET', '/topics/categories', params, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.EXTRA_LONG });
|
|
23
|
+
return response.categories ?? [];
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
26
|
throw this.handleError(error);
|
|
@@ -37,9 +37,9 @@ function OxyServicesTopicsMixin(Base) {
|
|
|
37
37
|
const params = { q: query };
|
|
38
38
|
if (limit)
|
|
39
39
|
params.limit = limit;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// `GET /topics/search` returns `{ topics: TopicData[] }` — unwrap it.
|
|
41
|
+
const response = await this.makeRequest('GET', '/topics/search', params, { cache: false });
|
|
42
|
+
return response.topics ?? [];
|
|
43
43
|
}
|
|
44
44
|
catch (error) {
|
|
45
45
|
throw this.handleError(error);
|
|
@@ -48,7 +48,7 @@ function OxyServicesTopicsMixin(Base) {
|
|
|
48
48
|
/**
|
|
49
49
|
* List topics with optional filters
|
|
50
50
|
* @param options - Filter and pagination options
|
|
51
|
-
* @returns
|
|
51
|
+
* @returns Paginated topics envelope (`topics` plus `total`/`limit`/`offset`)
|
|
52
52
|
*/
|
|
53
53
|
async listTopics(options) {
|
|
54
54
|
try {
|
|
@@ -63,10 +63,18 @@ function OxyServicesTopicsMixin(Base) {
|
|
|
63
63
|
params.offset = options.offset;
|
|
64
64
|
if (options?.locale)
|
|
65
65
|
params.locale = options.locale;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
66
|
+
// `GET /topics` returns `{ topics, total, limit, offset }`. The pagination
|
|
67
|
+
// fields matter to callers, so return the whole envelope (typed) rather
|
|
68
|
+
// than throwing them away.
|
|
69
|
+
const response = await this.makeRequest('GET', '/topics', params, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
70
|
+
const requestedLimit = typeof params.limit === 'number' ? params.limit : 0;
|
|
71
|
+
const requestedOffset = typeof params.offset === 'number' ? params.offset : 0;
|
|
72
|
+
return {
|
|
73
|
+
topics: response.topics ?? [],
|
|
74
|
+
total: response.total ?? response.topics?.length ?? 0,
|
|
75
|
+
limit: response.limit ?? requestedLimit,
|
|
76
|
+
offset: response.offset ?? requestedOffset,
|
|
77
|
+
};
|
|
70
78
|
}
|
|
71
79
|
catch (error) {
|
|
72
80
|
throw this.handleError(error);
|
|
@@ -95,9 +103,11 @@ function OxyServicesTopicsMixin(Base) {
|
|
|
95
103
|
*/
|
|
96
104
|
async resolveTopicNames(names) {
|
|
97
105
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
// `POST /topics/resolve` returns `{ topics: Record<name, TopicData> }`
|
|
107
|
+
// (a name-keyed map, not an array). Unwrap and flatten to the resolved
|
|
108
|
+
// topics; each TopicData carries its own `name` for re-keying.
|
|
109
|
+
const response = await this.makeRequest('POST', '/topics/resolve', { names }, { cache: false });
|
|
110
|
+
return Object.values(response.topics ?? {});
|
|
101
111
|
}
|
|
102
112
|
catch (error) {
|
|
103
113
|
throw this.handleError(error);
|
|
@@ -6,7 +6,7 @@ const apiUtils_1 = require("../utils/apiUtils");
|
|
|
6
6
|
const keyManager_1 = require("../crypto/keyManager");
|
|
7
7
|
const signatureService_1 = require("../crypto/signatureService");
|
|
8
8
|
const userIdentity_1 = require("../utils/userIdentity");
|
|
9
|
-
const
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
10
|
const errorUtils_1 = require("../utils/errorUtils");
|
|
11
11
|
/**
|
|
12
12
|
* Maximum number of ids sent per `POST /users/by-ids` request. Matches the
|
|
@@ -113,7 +113,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
113
113
|
// noise. Return contract is unchanged.
|
|
114
114
|
const status = (0, errorUtils_1.extractErrorStatus)(error);
|
|
115
115
|
const isNotFound = status === 404;
|
|
116
|
-
|
|
116
|
+
logger_1.logger.debug(isNotFound ? 'resolveProfile: handle not found' : 'resolveProfile: discovery failed', {
|
|
117
117
|
method: 'resolveProfile',
|
|
118
118
|
handle,
|
|
119
119
|
status,
|
|
@@ -194,7 +194,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
194
194
|
// caller (contract unchanged: rethrow via `handleError`). Add debug
|
|
195
195
|
// observability first so a recurring upstream failure is diagnosable
|
|
196
196
|
// — distinguishing an auth/transport problem from a server 5xx.
|
|
197
|
-
|
|
197
|
+
logger_1.logger.debug('getProfileRecommendations: discovery read failed', {
|
|
198
198
|
method: 'getProfileRecommendations',
|
|
199
199
|
path: usesScoredPath ? 'POST' : 'GET',
|
|
200
200
|
excludeTypes: options?.excludeTypes,
|
|
@@ -308,7 +308,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
308
308
|
return Array.isArray(users) ? users.map((user) => (0, userIdentity_1.normalizeUserIdentity)(user)) : [];
|
|
309
309
|
}
|
|
310
310
|
catch (error) {
|
|
311
|
-
|
|
311
|
+
logger_1.logger.warn('getUsersByIds: chunk failed, continuing with remaining chunks', {
|
|
312
312
|
method: 'getUsersByIds',
|
|
313
313
|
mode: useServiceAuth ? 'service' : 'user',
|
|
314
314
|
chunkSize: chunk.length,
|