pryv 3.12.0 → 3.12.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/package.json
CHANGED
|
@@ -253,11 +253,12 @@ class AuthController {
|
|
|
253
253
|
this._state = newState;
|
|
254
254
|
|
|
255
255
|
// Dispatch the state that was just set (`newState`), NOT the live `this.state`
|
|
256
|
-
// getter: a listener that synchronously changes the state mid-dispatch (e.g.
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
// SIGNOUT to the app's
|
|
256
|
+
// getter: a listener that synchronously changes the state mid-dispatch (e.g. a
|
|
257
|
+
// custom listener re-initializing to INITIALIZED on SIGNOUT; the LoginButton's
|
|
258
|
+
// own re-init now starts a microtask later) would otherwise overwrite
|
|
259
|
+
// `this._state`, so later listeners in this loop would receive the wrong state
|
|
260
|
+
// (a logout would deliver INITIALIZED instead of SIGNOUT to the app's
|
|
261
|
+
// onStateChange).
|
|
261
262
|
this.stateChangeListeners.forEach((listener) => {
|
|
262
263
|
try {
|
|
263
264
|
listener(newState);
|
|
@@ -74,8 +74,11 @@ class LoginButton {
|
|
|
74
74
|
case AuthStates.SIGNOUT: {
|
|
75
75
|
const message = this.messages.SIGNOUT_CONFIRM ? this.messages.SIGNOUT_CONFIRM : 'Logout ?';
|
|
76
76
|
if (confirm(message)) {
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
// Awaited: a re-init left running in the background resumes later
|
|
78
|
+
// (after loading assets) and consumes whatever poll URL the page shows
|
|
79
|
+
// by then, racing any other sign-in in progress.
|
|
80
|
+
await this.deleteAuthorizationData();
|
|
81
|
+
await this.auth.init();
|
|
79
82
|
}
|
|
80
83
|
break;
|
|
81
84
|
}
|
package/src/Service.js
CHANGED
|
@@ -573,8 +573,12 @@ class Service {
|
|
|
573
573
|
* apiEndpoint), and uses this method to build a working `Connection`.
|
|
574
574
|
*
|
|
575
575
|
* The implementation polls `<access>/<key>` once; the call MUST be
|
|
576
|
-
* made while the access is still in the ACCEPTED
|
|
577
|
-
*
|
|
576
|
+
* made while the access request is still readable in the ACCEPTED
|
|
577
|
+
* state. Servers keep a decided request only for a short retention
|
|
578
|
+
* window after it is first polled (default 2 minutes, operator setting
|
|
579
|
+
* `access.terminalRetentionMs`), so call this promptly after the flow
|
|
580
|
+
* completes; afterwards the key is unknown. (`expireAfter` on the
|
|
581
|
+
* access request is the lifetime of the access created, not of the key.)
|
|
578
582
|
*
|
|
579
583
|
* @param {string} key - polling key from `startAccessRequest`
|
|
580
584
|
* @returns {Promise<Connection>}
|
|
@@ -112,9 +112,10 @@ describe('[AUTX] Browser.LoginButton', function () {
|
|
|
112
112
|
const seen = [];
|
|
113
113
|
const a = makeAuth((s) => seen.push(s.status));
|
|
114
114
|
await a.init();
|
|
115
|
-
//
|
|
116
|
-
// SIGNOUT it
|
|
117
|
-
// to INITIALIZED during the same dispatch.
|
|
115
|
+
// Worst case of a LoginButton-like listener (registered after the external
|
|
116
|
+
// app listener): on SIGNOUT it re-initializes synchronously, driving the
|
|
117
|
+
// state to INITIALIZED during the same dispatch. (The real LoginButton
|
|
118
|
+
// awaits its re-init, which starts a microtask later.)
|
|
118
119
|
a.stateChangeListeners.push((s) => {
|
|
119
120
|
if (s.status === AuthStates.SIGNOUT) a.state = { status: AuthStates.INITIALIZED, serviceInfo: {} };
|
|
120
121
|
});
|
package/test/LoginButton.test.js
CHANGED
|
@@ -39,6 +39,7 @@ describe('[LBTX] LoginButton', function () {
|
|
|
39
39
|
delete global.document;
|
|
40
40
|
delete global.window;
|
|
41
41
|
delete global.location;
|
|
42
|
+
delete global.navigator;
|
|
42
43
|
delete global.confirm;
|
|
43
44
|
});
|
|
44
45
|
|
|
@@ -174,6 +175,23 @@ describe('[LBTX] LoginButton', function () {
|
|
|
174
175
|
// Confirm is mocked to return true
|
|
175
176
|
});
|
|
176
177
|
|
|
178
|
+
it('[LBSG] SIGNOUT waits for the re-init to finish (none left running in the background)', async function () {
|
|
179
|
+
// A re-init that outlives onStateChange resumes later and consumes
|
|
180
|
+
// whatever poll URL the page shows by then (it raced [LBRU] on CI).
|
|
181
|
+
const originalInit = loginBtn.auth.init;
|
|
182
|
+
let reinitDone = false;
|
|
183
|
+
loginBtn.auth.init = async function () {
|
|
184
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
185
|
+
reinitDone = true;
|
|
186
|
+
};
|
|
187
|
+
try {
|
|
188
|
+
await loginBtn.onStateChange({ status: AuthStates.SIGNOUT });
|
|
189
|
+
expect(reinitDone).to.equal(true);
|
|
190
|
+
} finally {
|
|
191
|
+
loginBtn.auth.init = originalInit;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
177
195
|
it('[LBSF] handles unknown state gracefully', async function () {
|
|
178
196
|
// Should log warning but not throw
|
|
179
197
|
await loginBtn.onStateChange({ status: 'UNKNOWN_STATE' });
|
|
@@ -9,14 +9,19 @@ const utils = require('../src/utils');
|
|
|
9
9
|
|
|
10
10
|
describe('[LBRU] LoginButton URL cleanup after redirect', function () {
|
|
11
11
|
const PAGE_URL = 'http://localhost/app?x=1&prYvpoll=http%3A%2F%2Flocalhost%2Fpoll%2Fkey123&prYvstatus=ACCEPTED';
|
|
12
|
-
|
|
13
|
-
let
|
|
12
|
+
const GLOBALS = ['document', 'window', 'location', 'navigator'];
|
|
13
|
+
let saved;
|
|
14
14
|
let originalFetchGet;
|
|
15
15
|
|
|
16
16
|
before(() => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// Always our own page: reusing a DOM left by another file would test that
|
|
18
|
+
// page's URL, not PAGE_URL. Node only (a real browser run has no JSDOM).
|
|
19
|
+
if (typeof JSDOM !== 'undefined') {
|
|
20
|
+
saved = {};
|
|
21
|
+
for (const name of GLOBALS) {
|
|
22
|
+
saved[name] = Object.getOwnPropertyDescriptor(global, name);
|
|
23
|
+
}
|
|
24
|
+
const dom = new JSDOM('<!DOCTYPE html><body></body>', { url: PAGE_URL });
|
|
20
25
|
global.document = dom.window.document;
|
|
21
26
|
global.window = dom.window;
|
|
22
27
|
global.location = dom.window.location;
|
|
@@ -28,11 +33,11 @@ describe('[LBRU] LoginButton URL cleanup after redirect', function () {
|
|
|
28
33
|
|
|
29
34
|
after(() => {
|
|
30
35
|
utils.fetchGet = originalFetchGet;
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
if (saved == null) return;
|
|
37
|
+
for (const name of GLOBALS) {
|
|
38
|
+
if (saved[name]) Object.defineProperty(global, name, saved[name]);
|
|
39
|
+
else delete global[name];
|
|
40
|
+
}
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
it('[LBR1] strips consumed prYv* params from the visible URL', async () => {
|