@isomorph.ai/app-sdk 1.5.2 → 1.5.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/README.md CHANGED
@@ -88,3 +88,10 @@ the host mints a fresh identity cookie from its sign-in session and redirects
88
88
  to the identity GET. When that refresh does not end in a 200 with the user —
89
89
  the session is gone, the network failed — the original 401 is surfaced
90
90
  without a resend, and the page's sign-in handling takes over.
91
+
92
+ On a separate API host the identity GET itself (`identity.current()`, and the
93
+ background refresh every 60 s that drives `identity.onChange`) is renewed the
94
+ same way: its refresh ends at the identity GET, so that answer is the identity
95
+ and no second GET is sent. When the refresh fails, `identity.current()`
96
+ answers `null` and listeners hear `null`, as before. On the page's own origin
97
+ the identity GET is never renewed (it is the renewal). (1.5.3)
package/dist/index.d.ts CHANGED
@@ -250,7 +250,7 @@ interface Query {
250
250
  }
251
251
  interface IdentityRenewal {
252
252
  renewals(): number;
253
- renew(sentAt: number): Promise<void | boolean>;
253
+ renew(sentAt: number): Promise<void | false | IsomorphUser>;
254
254
  }
255
255
  declare class Transport {
256
256
  private readonly onUnauthorized?;
package/dist/index.js CHANGED
@@ -133,9 +133,11 @@ const CONTEXT_REFRESH_PATH = `/__harbour/context/refresh?return_to=${encodeURICo
133
133
  // database: the handler never ran, so the retry is the first and only write.
134
134
  // The body is a JSON string, FormData or Blob — all replayable as-is.
135
135
  //
136
- // The identity GET itself is never renewed (it IS the renewal), and under Node
137
- // the runner's identity is a static token no GET can renew, so a refusal there
138
- // surfaces at once.
136
+ // The identity GET itself is never renewed on the page-relative path (it IS
137
+ // the renewal), and under Node the runner's identity is a static token no GET
138
+ // can renew, so a refusal there surfaces at once. On a separate API host the
139
+ // renewal is the context refresh instead, so the identity GET is renewed too
140
+ // (Transport.json, ADR 0027).
139
141
  function renewableOnUnauthorized(path) {
140
142
  if (typeof location === 'undefined')
141
143
  return false;
@@ -163,7 +165,7 @@ class Transport {
163
165
  this.identity = options.identity;
164
166
  }
165
167
  json(path, init = {}) {
166
- return this.request(path, init, renewableOnUnauthorized(path));
168
+ return this.request(path, init, renewableOnUnauthorized(path) || (this.separateApiHost && path === IDENTITY_PATH));
167
169
  }
168
170
  async request(path, init, renewOnUnauthorized) {
169
171
  const sentAt = this.renewal?.renewals() ?? 0;
@@ -184,6 +186,10 @@ class Transport {
184
186
  // exception is an API-host refresh that failed: the original 401
185
187
  // surfaces, and the page's shim takes over with its login hop.
186
188
  const renewed = await this.renewal.renew(sentAt).catch(() => undefined);
189
+ // The API host's refresh is followed to the identity GET, so its
190
+ // answer IS the identity: the GET is not sent a second time.
191
+ if (renewed && path === IDENTITY_PATH)
192
+ return renewed;
187
193
  if (renewed !== false)
188
194
  return this.request(path, init, false);
189
195
  }
@@ -356,7 +362,10 @@ export function createClient(options = {}) {
356
362
  refreshesStarted += 1;
357
363
  refreshInFlight = transport.json(IDENTITY_PATH)
358
364
  .then((user) => {
359
- notifyIdentity(user, true);
365
+ // A user answered by the API host's context refresh was announced by
366
+ // that refresh already (it is the same object): once is enough.
367
+ if (user === null || user !== currentUser)
368
+ notifyIdentity(user, true);
360
369
  return user;
361
370
  })
362
371
  .catch((error) => {
@@ -384,9 +393,11 @@ export function createClient(options = {}) {
384
393
  };
385
394
  // On a separate API host the same sharing rule holds, but the renewal is
386
395
  // the host's context refresh, credentialed and followed to the identity
387
- // GET. It succeeds only on a 200 carrying the signed-in user; a network or
388
- // CORS failure (no gateway session: governance redirects to the IdP), a
389
- // non-200 or an HTML page answers false.
396
+ // GET. It succeeds only on a 200 carrying the signed-in user, and answers
397
+ // that user; a network or CORS failure (no gateway session: governance
398
+ // redirects to the IdP), a non-200 or an HTML page answers false. The
399
+ // identity GET itself is renewed this way too (ADR 0027), so the periodic
400
+ // refresh behind identity.onChange survives a lapsed context.
390
401
  if (transport.separateApiHost) {
391
402
  let contextRefreshes = 0;
392
403
  let contextRefreshInFlight;
@@ -402,7 +413,7 @@ export function createClient(options = {}) {
402
413
  if (typeof user?.id !== 'string')
403
414
  return false;
404
415
  notifyIdentity(user, true);
405
- return true;
416
+ return user;
406
417
  })
407
418
  .catch(() => false)
408
419
  .finally(() => { contextRefreshInFlight = undefined; });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/app-sdk",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",