@oxyhq/core 10.1.2 → 10.1.3
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/session/accountDialogController.js +25 -7
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/session/accountDialogController.js +25 -7
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/models/interfaces.d.ts +0 -1
- package/package.json +1 -1
- package/src/models/interfaces.ts +0 -1
- package/src/session/__tests__/accountDialogController.test.ts +36 -0
- package/src/session/accountDialogController.ts +23 -7
package/package.json
CHANGED
package/src/models/interfaces.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { User } from '../../models/interfaces';
|
|
|
4
4
|
import type { SessionLoginResponse, MinimalUserData } from '../../models/session';
|
|
5
5
|
import type { AccountNode } from '../../mixins/OxyServices.accounts';
|
|
6
6
|
import { SessionClient, type SessionClientHost } from '../SessionClient';
|
|
7
|
+
import { logger } from '../../utils/loggerUtils';
|
|
7
8
|
import {
|
|
8
9
|
AccountDialogController,
|
|
9
10
|
createAccountDialogController,
|
|
@@ -206,6 +207,7 @@ describe('AccountDialogController — account list', () => {
|
|
|
206
207
|
});
|
|
207
208
|
|
|
208
209
|
it('keeps device rows and surfaces the error when listAccounts fails', async () => {
|
|
210
|
+
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
|
|
209
211
|
const { controller, oxy, sc } = makeHarness();
|
|
210
212
|
sc.set(state([{ accountId: 'a1', sessionId: 's1' }], 'a1'));
|
|
211
213
|
oxy.getUsersByIds.mockResolvedValue([user('a1')]);
|
|
@@ -216,6 +218,40 @@ describe('AccountDialogController — account list', () => {
|
|
|
216
218
|
const snap = controller.getSnapshot();
|
|
217
219
|
expect(snap.error).toBe('graph boom');
|
|
218
220
|
expect(snap.accounts.map((r) => r.accountId)).toEqual(['a1']);
|
|
221
|
+
// A genuine (non-401) failure STILL warns.
|
|
222
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
223
|
+
'[AccountDialogController] listAccounts failed',
|
|
224
|
+
{ component: 'AccountDialogController' },
|
|
225
|
+
expect.any(Error),
|
|
226
|
+
);
|
|
227
|
+
warnSpy.mockRestore();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('treats a 401 from listAccounts as the signed-out edge — debug, no surfaced error, no warn', async () => {
|
|
231
|
+
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
|
|
232
|
+
const debugSpy = jest.spyOn(logger, 'debug').mockImplementation(() => undefined);
|
|
233
|
+
const { controller, oxy, sc } = makeHarness();
|
|
234
|
+
sc.set(state([{ accountId: 'a1', sessionId: 's1' }], 'a1'));
|
|
235
|
+
oxy.getUsersByIds.mockResolvedValue([user('a1')]);
|
|
236
|
+
// A stale/revoked bearer 401s: an EXPECTED signed-out outcome, not a failure.
|
|
237
|
+
oxy.listAccounts.mockRejectedValue(
|
|
238
|
+
Object.assign(new Error('Invalid or missing authorization header'), { status: 401 }),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
await controller.refresh();
|
|
242
|
+
|
|
243
|
+
const snap = controller.getSnapshot();
|
|
244
|
+
// Never surface an error for a normal signed-out state; device rows still render.
|
|
245
|
+
expect(snap.error).toBeNull();
|
|
246
|
+
expect(snap.accounts.map((r) => r.accountId)).toEqual(['a1']);
|
|
247
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
248
|
+
expect(debugSpy).toHaveBeenCalledWith(
|
|
249
|
+
'[AccountDialogController] listAccounts unauthorized (signed out)',
|
|
250
|
+
{ component: 'AccountDialogController' },
|
|
251
|
+
expect.objectContaining({ status: 401 }),
|
|
252
|
+
);
|
|
253
|
+
warnSpy.mockRestore();
|
|
254
|
+
debugSpy.mockRestore();
|
|
219
255
|
});
|
|
220
256
|
});
|
|
221
257
|
|
|
@@ -30,6 +30,7 @@ import type { OxyServices } from '../OxyServices';
|
|
|
30
30
|
import type { SessionLoginResponse, MinimalUserData } from '../models/session';
|
|
31
31
|
import type { User } from '../models/interfaces';
|
|
32
32
|
import { logger } from '../utils/loggerUtils';
|
|
33
|
+
import { extractErrorStatus } from '../utils/errorUtils';
|
|
33
34
|
import { CENTRAL_IDP_APEX } from '../utils/authWebUrl';
|
|
34
35
|
import {
|
|
35
36
|
generateOAuthState,
|
|
@@ -392,10 +393,19 @@ export class AccountDialogController {
|
|
|
392
393
|
try {
|
|
393
394
|
graph = await this.oxyServices.listAccounts();
|
|
394
395
|
} catch (error) {
|
|
395
|
-
// A
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
396
|
+
// A 401 here is the EXPECTED signed-out edge, not a failure: the bearer was
|
|
397
|
+
// stale/revoked, so `HttpService` already cleared it and emitted
|
|
398
|
+
// `onTokensChanged(null)`, which drops the graph via `reconcileAuth`. Log at
|
|
399
|
+
// debug and leave the dialog error-free — a signed-out device with zero
|
|
400
|
+
// accounts is a normal state, not a warning. Any other error (network, 5xx,
|
|
401
|
+
// malformed) IS unexpected: surface it and warn while keeping the prior graph
|
|
402
|
+
// so device rows still render.
|
|
403
|
+
if (extractErrorStatus(error) === 401) {
|
|
404
|
+
logger.debug('[AccountDialogController] listAccounts unauthorized (signed out)', { component: 'AccountDialogController' }, error);
|
|
405
|
+
} else {
|
|
406
|
+
this.error = errorMessage(error);
|
|
407
|
+
logger.warn('[AccountDialogController] listAccounts failed', { component: 'AccountDialogController' }, error);
|
|
408
|
+
}
|
|
399
409
|
}
|
|
400
410
|
if (seq !== this.refreshSeq) return; // superseded by a newer refresh
|
|
401
411
|
|
|
@@ -432,9 +442,15 @@ export class AccountDialogController {
|
|
|
432
442
|
try {
|
|
433
443
|
profiles = await this.oxyServices.getUsersByIds(ids);
|
|
434
444
|
} catch (error) {
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
|
|
445
|
+
// A 401 is the EXPECTED signed-out edge (stale/cleared bearer) — log at debug.
|
|
446
|
+
// `getUsersByIds` already swallows per-chunk failures and returns `[]`, so any
|
|
447
|
+
// OTHER error here is an unexpected total failure worth a warn. Either way keep
|
|
448
|
+
// the prior profile map.
|
|
449
|
+
if (extractErrorStatus(error) === 401) {
|
|
450
|
+
logger.debug('[AccountDialogController] getUsersByIds unauthorized (signed out)', { component: 'AccountDialogController' }, error);
|
|
451
|
+
} else {
|
|
452
|
+
logger.warn('[AccountDialogController] getUsersByIds failed', { component: 'AccountDialogController' }, error);
|
|
453
|
+
}
|
|
438
454
|
return;
|
|
439
455
|
}
|
|
440
456
|
if (seq !== this.refreshSeq) return; // superseded
|