@oxyhq/core 12.4.0 → 12.5.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/mixins/OxyServices.user.js +11 -0
- package/dist/cjs/server/safeFetch.js +4 -4
- package/dist/cjs/session/refresh.js +14 -3
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/mixins/OxyServices.user.js +11 -0
- package/dist/esm/server/safeFetch.js +4 -4
- package/dist/esm/session/refresh.js +14 -3
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/mixins/OxyServices.user.d.ts +10 -1
- package/dist/types/models/interfaces.d.ts +15 -1
- package/dist/types/server/safeFetch.d.ts +2 -0
- package/package.json +2 -2
- package/src/mixins/OxyServices.user.ts +15 -0
- package/src/models/interfaces.ts +20 -1
- package/src/server/safeFetch.ts +6 -1
- package/src/session/__tests__/refresh.test.ts +20 -0
- package/src/session/refresh.ts +12 -3
|
@@ -440,6 +440,17 @@ function OxyServicesUserMixin(Base) {
|
|
|
440
440
|
async updateUserPreferences(preferences) {
|
|
441
441
|
return this.updateProfile({ userPreferences: preferences });
|
|
442
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Update the authenticated user's portable theme preference (light/dark/
|
|
445
|
+
* system + Bloom color-preset key). Persisted on the User document via the
|
|
446
|
+
* SAME `PUT /users/me` settings path as the other preferences — same cache
|
|
447
|
+
* invalidation — so the next cold boot serves it on the self/session payload
|
|
448
|
+
* with no extra network call. The full object is written (both `mode` and
|
|
449
|
+
* `colorPreset` are required by the API).
|
|
450
|
+
*/
|
|
451
|
+
async updateThemePreference(themePreference) {
|
|
452
|
+
return this.updateProfile({ themePreference });
|
|
453
|
+
}
|
|
443
454
|
/**
|
|
444
455
|
* Request account verification
|
|
445
456
|
*/
|
|
@@ -383,7 +383,7 @@ function buildRequestOptions(target, pinnedIp, pinnedFamily, method, headers, si
|
|
|
383
383
|
};
|
|
384
384
|
}
|
|
385
385
|
/** Perform a single upstream request (no auto-redirect). */
|
|
386
|
-
function fetchOnce(options, isHttps, headersTimeoutMs) {
|
|
386
|
+
function fetchOnce(options, isHttps, headersTimeoutMs, body) {
|
|
387
387
|
return new Promise((resolve, reject) => {
|
|
388
388
|
const transport = isHttps ? node_https_1.default : node_http_1.default;
|
|
389
389
|
const req = transport.request(options, (res) => resolve(res));
|
|
@@ -391,7 +391,7 @@ function fetchOnce(options, isHttps, headersTimeoutMs) {
|
|
|
391
391
|
req.destroy(new UpstreamError('upstream headers timeout'));
|
|
392
392
|
});
|
|
393
393
|
req.on('error', (err) => reject(err));
|
|
394
|
-
req.end();
|
|
394
|
+
req.end(body);
|
|
395
395
|
});
|
|
396
396
|
}
|
|
397
397
|
/**
|
|
@@ -406,7 +406,7 @@ function fetchOnce(options, isHttps, headersTimeoutMs) {
|
|
|
406
406
|
* @throws {UpstreamError} on redirect-loop / malformed-redirect / timeout.
|
|
407
407
|
*/
|
|
408
408
|
async function safeFetch(rawUrl, options = {}) {
|
|
409
|
-
const { method = 'GET', headers: callerHeaders, maxRedirects = exports.MAX_REDIRECTS, headersTimeoutMs = exports.UPSTREAM_HEADERS_TIMEOUT_MS, signal, } = options;
|
|
409
|
+
const { method = 'GET', headers: callerHeaders, body, maxRedirects = exports.MAX_REDIRECTS, headersTimeoutMs = exports.UPSTREAM_HEADERS_TIMEOUT_MS, signal, } = options;
|
|
410
410
|
// Normalize a case-insensitive header map and ensure a User-Agent default.
|
|
411
411
|
const baseHeaders = {};
|
|
412
412
|
if (callerHeaders) {
|
|
@@ -429,7 +429,7 @@ async function safeFetch(rawUrl, options = {}) {
|
|
|
429
429
|
}
|
|
430
430
|
const target = new node_url_1.URL(currentUrl);
|
|
431
431
|
const requestOptions = buildRequestOptions(target, guard.ip, guard.family, method, baseHeaders, signal);
|
|
432
|
-
const response = await fetchOnce(requestOptions, target.protocol === 'https:', headersTimeoutMs);
|
|
432
|
+
const response = await fetchOnce(requestOptions, target.protocol === 'https:', headersTimeoutMs, body);
|
|
433
433
|
const status = response.statusCode ?? 0;
|
|
434
434
|
if (REDIRECT_STATUS_CODES.has(status)) {
|
|
435
435
|
const location = response.headers.location;
|
|
@@ -75,9 +75,20 @@ async function refreshDeviceSecretArm(deps) {
|
|
|
75
75
|
// Structural read (not `instanceof Error`): the thrown value can be a
|
|
76
76
|
// plain ApiError-shaped object or come from another realm.
|
|
77
77
|
const message = error?.message;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
const body = typeof message === 'string' ? message : '';
|
|
79
|
+
// ONLY the server's explicit `invalid_device_secret` proves the presented
|
|
80
|
+
// secret is bad and may clear the durable device credential. `no_active_session`
|
|
81
|
+
// is an authoritative signed-out. ANY OTHER 401 — a middleware/CSRF/proxy 401,
|
|
82
|
+
// an ALB/starting-instance 401, a CORS error page, etc., all common during a
|
|
83
|
+
// deploy/restart window — is NOT proof the secret diverged: treat it as
|
|
84
|
+
// transient and KEEP the credential so a later attempt self-heals. Wiping the
|
|
85
|
+
// credential on an ambiguous 401 is what logged users out on every deploy,
|
|
86
|
+
// ecosystem-wide.
|
|
87
|
+
if (body.includes('invalid_device_secret'))
|
|
88
|
+
return { status: 'invalid-secret' };
|
|
89
|
+
if (body.includes('no_active_session'))
|
|
90
|
+
return { status: 'no-session' };
|
|
91
|
+
return { status: 'transient' };
|
|
81
92
|
}
|
|
82
93
|
return { status: 'transient' };
|
|
83
94
|
}
|