@pylonsync/sync 0.9.0 → 0.9.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/dist/index.d.ts +7 -0
- package/package.json +1 -1
- package/src/index.ts +14 -1
- package/src/session-resolved.test.ts +41 -0
- package/src/session-resolver.ts +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -401,6 +401,13 @@ export declare class SyncEngine {
|
|
|
401
401
|
onBinaryFrame(handler: (bytes: Uint8Array) => void): () => void;
|
|
402
402
|
/** Read the cached resolved session. Null user = anonymous. */
|
|
403
403
|
resolvedSession(): ResolvedSession;
|
|
404
|
+
/**
|
|
405
|
+
* Whether `/api/auth/me` has answered during this run. Before that,
|
|
406
|
+
* `resolvedSession()` returns the empty placeholder, which readers must
|
|
407
|
+
* not mistake for a signed-out user. Notified through the same store
|
|
408
|
+
* channel as the session itself.
|
|
409
|
+
*/
|
|
410
|
+
sessionResolved(): boolean;
|
|
404
411
|
/**
|
|
405
412
|
* Coarse connection state (see `SyncConnectionStatus`). Updated as
|
|
406
413
|
* the WS opens/closes and reconnect attempts run; subscribers re-
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -547,6 +547,16 @@ export class SyncEngine {
|
|
|
547
547
|
return this.session.resolved();
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Whether `/api/auth/me` has answered during this run. Before that,
|
|
552
|
+
* `resolvedSession()` returns the empty placeholder, which readers must
|
|
553
|
+
* not mistake for a signed-out user. Notified through the same store
|
|
554
|
+
* channel as the session itself.
|
|
555
|
+
*/
|
|
556
|
+
sessionResolved(): boolean {
|
|
557
|
+
return this.session.hasObserved();
|
|
558
|
+
}
|
|
559
|
+
|
|
550
560
|
/**
|
|
551
561
|
* Coarse connection state (see `SyncConnectionStatus`). Updated as
|
|
552
562
|
* the WS opens/closes and reconnect attempts run; subscribers re-
|
|
@@ -2498,8 +2508,11 @@ export class SyncEngine {
|
|
|
2498
2508
|
}
|
|
2499
2509
|
}
|
|
2500
2510
|
}
|
|
2511
|
+
const firstResolution = !this.session.hasObserved();
|
|
2501
2512
|
this.session.commitObservation(next);
|
|
2502
|
-
if (verdict.identityChanged) {
|
|
2513
|
+
if (verdict.identityChanged || firstResolution) {
|
|
2514
|
+
// The first answer flips `sessionResolved()` even when the
|
|
2515
|
+
// session itself matches the placeholder (anonymous caller).
|
|
2503
2516
|
this.store.notify();
|
|
2504
2517
|
}
|
|
2505
2518
|
if (broadcast && this.isMultiTabLeader) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// `sessionResolved()` tells readers whether `/api/auth/me` has answered.
|
|
2
|
+
// Before it does, `resolvedSession()` is the empty placeholder — a route
|
|
3
|
+
// guard that reads `userId == null` as "signed out" sends a returning
|
|
4
|
+
// user to the sign-in screen on every cold launch. The flag flips on the
|
|
5
|
+
// first answer, anonymous or not, and notifies store subscribers.
|
|
6
|
+
|
|
7
|
+
import { afterEach, expect, test } from "bun:test";
|
|
8
|
+
import { createTestEnv, type TestEnv } from "./test-harness";
|
|
9
|
+
|
|
10
|
+
let env: TestEnv | null = null;
|
|
11
|
+
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
if (env) {
|
|
14
|
+
await env.dispose();
|
|
15
|
+
env = null;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("unresolved before start; resolved after /api/auth/me answers anonymous", async () => {
|
|
20
|
+
env = await createTestEnv({ transport: "poll" });
|
|
21
|
+
const { engine } = env;
|
|
22
|
+
expect(engine.sessionResolved()).toBe(false);
|
|
23
|
+
let notified = 0;
|
|
24
|
+
engine.store.subscribe(() => {
|
|
25
|
+
notified++;
|
|
26
|
+
});
|
|
27
|
+
await engine.start();
|
|
28
|
+
expect(engine.sessionResolved()).toBe(true);
|
|
29
|
+
expect(engine.resolvedSession().userId).toBeNull();
|
|
30
|
+
// The placeholder and the anonymous answer are identical, so only the
|
|
31
|
+
// resolved flag changed — subscribers must still hear about it.
|
|
32
|
+
expect(notified).toBeGreaterThan(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("resolved with the signed-in identity", async () => {
|
|
36
|
+
env = await createTestEnv({ transport: "poll" });
|
|
37
|
+
env.signIn({ userId: "u1" });
|
|
38
|
+
await env.engine.start();
|
|
39
|
+
expect(env.engine.sessionResolved()).toBe(true);
|
|
40
|
+
expect(env.engine.resolvedSession().userId).toBe("u1");
|
|
41
|
+
});
|
package/src/session-resolver.ts
CHANGED
|
@@ -104,6 +104,7 @@ export class SessionResolver {
|
|
|
104
104
|
commitObservation(next: ResolvedSession): void {
|
|
105
105
|
this.lastSeenTenant = next.tenantId;
|
|
106
106
|
this._resolved = next;
|
|
107
|
+
this._observed = true;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
/** Convenience for tests / migration: inspect + commit in one call.
|
|
@@ -112,7 +113,6 @@ export class SessionResolver {
|
|
|
112
113
|
observeSession(next: ResolvedSession): SessionTransition {
|
|
113
114
|
const verdict = this.inspectSession(next);
|
|
114
115
|
this.commitObservation(next);
|
|
115
|
-
this._observed = true;
|
|
116
116
|
return verdict;
|
|
117
117
|
}
|
|
118
118
|
|