@pylonsync/sync 0.5.0 → 0.5.2
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/index.d.ts +13 -0
- package/package.json +1 -1
- package/src/index.ts +43 -1
package/dist/index.d.ts
CHANGED
|
@@ -265,6 +265,8 @@ export declare class SyncEngine {
|
|
|
265
265
|
* joiner whose orchestrator never fires onPromote (because it was
|
|
266
266
|
* never leader) silently run as a leader. */
|
|
267
267
|
private isMultiTabLeader;
|
|
268
|
+
/** One-shot guard for the cross-app keyspace-collision warning. */
|
|
269
|
+
private warnedForeignToken;
|
|
268
270
|
/**
|
|
269
271
|
* Serialized apply queue. Every change-event apply — from WS onmessage,
|
|
270
272
|
* pull(), or session-changed catchup — chains onto this promise so
|
|
@@ -725,6 +727,17 @@ export declare class SyncEngine {
|
|
|
725
727
|
* will retry. Errors must not abort bootstrap.
|
|
726
728
|
*/
|
|
727
729
|
private fetchSessionBootstrap;
|
|
730
|
+
/**
|
|
731
|
+
* A stored token that /api/auth/me resolves to no user is either expired
|
|
732
|
+
* or — the common footgun — a FOREIGN token: two Pylon apps sharing an
|
|
733
|
+
* origin (every app on localhost:4321 in dev) both read the default,
|
|
734
|
+
* unprefixed `pylon_token`, so one app sends the other's token and the
|
|
735
|
+
* server rejects it. The failure surfaces far away as "anonymous session"
|
|
736
|
+
* from createOrg, which names neither the token nor the keyspace. Point at
|
|
737
|
+
* the real cause, once. A genuinely signed-out user has no stored token,
|
|
738
|
+
* so this stays quiet for them.
|
|
739
|
+
*/
|
|
740
|
+
private warnForeignToken;
|
|
728
741
|
/**
|
|
729
742
|
* Apply a freshly-observed session through the resolver and act on
|
|
730
743
|
* the verdict. Serialized via `sessionChain` so concurrent triggers
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -389,6 +389,8 @@ export class SyncEngine {
|
|
|
389
389
|
* joiner whose orchestrator never fires onPromote (because it was
|
|
390
390
|
* never leader) silently run as a leader. */
|
|
391
391
|
private isMultiTabLeader = false;
|
|
392
|
+
/** One-shot guard for the cross-app keyspace-collision warning. */
|
|
393
|
+
private warnedForeignToken = false;
|
|
392
394
|
|
|
393
395
|
/**
|
|
394
396
|
* Serialized apply queue. Every change-event apply — from WS onmessage,
|
|
@@ -1634,7 +1636,22 @@ export class SyncEngine {
|
|
|
1634
1636
|
/** Current auth token from config or the storage adapter. Null when neither has one. */
|
|
1635
1637
|
private currentToken(): string | null {
|
|
1636
1638
|
if (this.config.token) return this.config.token;
|
|
1637
|
-
|
|
1639
|
+
const key = this.tokenStorageKey();
|
|
1640
|
+
const token = this.storage.get(key);
|
|
1641
|
+
if (token) return token;
|
|
1642
|
+
// Non-destructive legacy adoption, mirroring React's configureClient:
|
|
1643
|
+
// an app that shipped on the default `pylon_token` keeps its session
|
|
1644
|
+
// when it adopts a namespace. On web this is a no-op (React already
|
|
1645
|
+
// copied it in shared localStorage); it matters for async/native
|
|
1646
|
+
// adapters the React layer doesn't touch. COPY, never delete.
|
|
1647
|
+
if (key !== "pylon_token") {
|
|
1648
|
+
const legacy = this.storage.get("pylon_token");
|
|
1649
|
+
if (legacy) {
|
|
1650
|
+
this.storage.set(key, legacy);
|
|
1651
|
+
return legacy;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return null;
|
|
1638
1655
|
}
|
|
1639
1656
|
|
|
1640
1657
|
/**
|
|
@@ -2358,6 +2375,7 @@ export class SyncEngine {
|
|
|
2358
2375
|
roles?: string[];
|
|
2359
2376
|
avatar_url?: string | null;
|
|
2360
2377
|
};
|
|
2378
|
+
if ((raw.user_id ?? null) === null) this.warnForeignToken();
|
|
2361
2379
|
return {
|
|
2362
2380
|
userId: raw.user_id ?? null,
|
|
2363
2381
|
tenantId: raw.tenant_id ?? null,
|
|
@@ -2372,6 +2390,30 @@ export class SyncEngine {
|
|
|
2372
2390
|
}
|
|
2373
2391
|
}
|
|
2374
2392
|
|
|
2393
|
+
/**
|
|
2394
|
+
* A stored token that /api/auth/me resolves to no user is either expired
|
|
2395
|
+
* or — the common footgun — a FOREIGN token: two Pylon apps sharing an
|
|
2396
|
+
* origin (every app on localhost:4321 in dev) both read the default,
|
|
2397
|
+
* unprefixed `pylon_token`, so one app sends the other's token and the
|
|
2398
|
+
* server rejects it. The failure surfaces far away as "anonymous session"
|
|
2399
|
+
* from createOrg, which names neither the token nor the keyspace. Point at
|
|
2400
|
+
* the real cause, once. A genuinely signed-out user has no stored token,
|
|
2401
|
+
* so this stays quiet for them.
|
|
2402
|
+
*/
|
|
2403
|
+
private warnForeignToken(): void {
|
|
2404
|
+
if (this.warnedForeignToken) return;
|
|
2405
|
+
if (this.config.token) return; // explicit token, not a keyspace artifact
|
|
2406
|
+
if (!this.storage.get(this.tokenStorageKey())) return; // no token → normal signed-out
|
|
2407
|
+
this.warnedForeignToken = true;
|
|
2408
|
+
console.warn(
|
|
2409
|
+
'[pylon] a stored auth token was rejected (/api/auth/me resolved to an ' +
|
|
2410
|
+
'anonymous identity). If more than one Pylon app runs on this origin, ' +
|
|
2411
|
+
'they share the default "pylon_token" keyspace and clobber each ' +
|
|
2412
|
+
"other's sessions. Give each app a name: init({ appName }) / " +
|
|
2413
|
+
"configureClient({ appName }).",
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2375
2417
|
/**
|
|
2376
2418
|
* Apply a freshly-observed session through the resolver and act on
|
|
2377
2419
|
* the verdict. Serialized via `sessionChain` so concurrent triggers
|