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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pryv",
3
- "version": "3.12.0",
3
+ "version": "3.12.1",
4
4
  "description": "Pryv JavaScript library",
5
5
  "keywords": [
6
6
  "Pryv",
@@ -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
- // the LoginButton's logout confirm re-initializing to INITIALIZED) would
258
- // otherwise overwrite `this._state`, so later listeners in this loop would
259
- // receive the wrong state (a logout would deliver INITIALIZED instead of
260
- // SIGNOUT to the app's onStateChange).
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
- this.deleteAuthorizationData();
78
- this.auth.init();
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 state (which
577
- * persists until expiry — see `expireAfter` on the access request).
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
- // Mimic LoginButton (registered after the external app listener): on
116
- // SIGNOUT it confirms + re-initializes, synchronously driving the state
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
  });
@@ -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
- let dom;
13
- let cleanupDom = false;
12
+ const GLOBALS = ['document', 'window', 'location', 'navigator'];
13
+ let saved;
14
14
  let originalFetchGet;
15
15
 
16
16
  before(() => {
17
- if (typeof document === 'undefined') {
18
- cleanupDom = true;
19
- dom = new JSDOM('<!DOCTYPE html><body></body>', { url: PAGE_URL });
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 (!cleanupDom) return;
32
- delete global.document;
33
- delete global.window;
34
- delete global.location;
35
- delete global.navigator;
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 () => {