@pylonsync/functions 0.5.0 → 0.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/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Regression: the browser client namespaces its storage by the app's manifest
|
|
2
|
+
// name, but the name only lives server-side. The SSR runtime surfaces it as
|
|
3
|
+
// PYLON_APP_NAME and buildHydrationTail forwards it into __PYLON_DATA__ as `app`
|
|
4
|
+
// so the client can `configureClient({ appName })` at hydrate — before any token
|
|
5
|
+
// read. These pin that the field appears exactly when the env is set.
|
|
6
|
+
|
|
7
|
+
import { expect, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
import { buildHydrationTail } from "./ssr-runtime";
|
|
10
|
+
|
|
11
|
+
const base = {
|
|
12
|
+
component: "app/page.tsx",
|
|
13
|
+
layouts: [] as string[],
|
|
14
|
+
props: {},
|
|
15
|
+
ssrData: {},
|
|
16
|
+
manifestRoute: null,
|
|
17
|
+
publicPrefix: "/_pylon/build/",
|
|
18
|
+
manifestErr: null,
|
|
19
|
+
dataOnly: true, // return just the __PYLON_DATA__ script
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function withEnv(value: string | undefined, fn: () => void) {
|
|
23
|
+
const prev = process.env.PYLON_APP_NAME;
|
|
24
|
+
if (value === undefined) delete process.env.PYLON_APP_NAME;
|
|
25
|
+
else process.env.PYLON_APP_NAME = value;
|
|
26
|
+
try {
|
|
27
|
+
fn();
|
|
28
|
+
} finally {
|
|
29
|
+
if (prev === undefined) delete process.env.PYLON_APP_NAME;
|
|
30
|
+
else process.env.PYLON_APP_NAME = prev;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test("injects app from PYLON_APP_NAME", () => {
|
|
35
|
+
withEnv("revtrail", () => {
|
|
36
|
+
expect(buildHydrationTail(base)).toContain('"app":"revtrail"');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("omits app when PYLON_APP_NAME is unset", () => {
|
|
41
|
+
withEnv(undefined, () => {
|
|
42
|
+
expect(buildHydrationTail(base)).not.toContain('"app"');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -330,6 +330,7 @@ const CLIENT_RUNTIME_SOURCE = `// Generated by Pylon SSR (Phase 2 client runtime
|
|
|
330
330
|
|
|
331
331
|
import { createElement } from "react";
|
|
332
332
|
import { hydrateRoot } from "react-dom/client";
|
|
333
|
+
import { configureClient } from "@pylonsync/react";
|
|
333
334
|
import { createPylonBoundary, nearestBoundaryComponent } from "./client-boundary";
|
|
334
335
|
import { LOADING_MODULES } from "./loading-registry";
|
|
335
336
|
import { createNavPayloadCache } from "./nav-cache";
|
|
@@ -672,6 +673,13 @@ export function hydrate(component, Page, Layouts) {
|
|
|
672
673
|
// of all prefetching. In flight from here, it resolves during hydration.
|
|
673
674
|
void loadManifest();
|
|
674
675
|
const data = readPylonData();
|
|
676
|
+
// Namespace client storage by the app's manifest name (injected into
|
|
677
|
+
// __PYLON_DATA__ as \`app\`) BEFORE any db hook / auth helper reads a token.
|
|
678
|
+
// Without this every app on a shared origin (all localhost:4321 in dev)
|
|
679
|
+
// shares the default \`pylon_token\` and clobbers each other's sessions.
|
|
680
|
+
// configureClient runs once here; it also migrates a legacy default-keyspace
|
|
681
|
+
// token so an existing session survives the switch.
|
|
682
|
+
if (data && data.app) configureClient({ appName: data.app });
|
|
675
683
|
// First hydrate: the entry's component MATCHES the SSR'd page.
|
|
676
684
|
// Establish the root + install the click + popstate handlers
|
|
677
685
|
// exactly once.
|
package/src/ssr-runtime.ts
CHANGED
|
@@ -2030,6 +2030,14 @@ export function buildHydrationTail(args: {
|
|
|
2030
2030
|
props: serializableProps,
|
|
2031
2031
|
ssrData: args.ssrData,
|
|
2032
2032
|
};
|
|
2033
|
+
// App name (from the manifest, surfaced by the runtime as PYLON_APP_NAME) so
|
|
2034
|
+
// the client namespaces its localStorage/IndexedDB per-app at hydrate. Without
|
|
2035
|
+
// it every app on a shared origin (all localhost:4321 in dev) collides on the
|
|
2036
|
+
// default `pylon_token`. App-global + identity-free, so it stays byte-identical
|
|
2037
|
+
// across users — safe in the PPR-bucketed shared tail too.
|
|
2038
|
+
const appName =
|
|
2039
|
+
typeof process !== "undefined" ? process.env.PYLON_APP_NAME : undefined;
|
|
2040
|
+
if (appName) hydrationPayload.app = appName;
|
|
2033
2041
|
if (args.kind) hydrationPayload.kind = args.kind;
|
|
2034
2042
|
const json = escapeScriptJson(JSON.stringify(hydrationPayload));
|
|
2035
2043
|
let tail = `<script id="__PYLON_DATA__" type="application/json">${json}</script>`;
|