@c9up/aurora 0.1.19 → 0.1.21
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/AuroraManager.d.ts +24 -8
- package/dist/AuroraManager.js +15 -8
- package/dist/AuroraProvider.d.ts +2 -2
- package/dist/AuroraProvider.js +3 -3
- package/dist/Pages.d.ts +1 -1
- package/dist/Pages.js +1 -1
- package/dist/live.d.ts +5 -0
- package/dist/live.js +12 -0
- package/dist/liveClient.d.ts +1 -1
- package/dist/liveClient.js +2 -2
- package/dist/liveServer.d.ts +1 -1
- package/dist/liveServer.js +1 -1
- package/dist/middleware.d.ts +32 -0
- package/dist/middleware.js +43 -0
- package/dist/rpc.d.ts +15 -1
- package/dist/rpc.js +31 -2
- package/dist/server/renderPage.d.ts +3 -3
- package/dist/server/renderPage.js +3 -3
- package/dist/server/serveAssets.d.ts +2 -2
- package/dist/server/serveAssets.js +2 -2
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -0
- package/package.json +1 -1
- package/src/AuroraManager.ts +29 -9
- package/src/AuroraProvider.ts +3 -3
- package/src/Pages.ts +2 -2
- package/src/live.ts +39 -0
- package/src/liveClient.ts +2 -2
- package/src/liveServer.ts +1 -1
- package/src/middleware.ts +74 -0
- package/src/rpc.ts +42 -3
- package/src/server/renderPage.ts +4 -4
- package/src/server/serveAssets.ts +2 -2
- package/src/server.ts +4 -0
package/dist/AuroraManager.d.ts
CHANGED
|
@@ -33,16 +33,29 @@ export interface AuroraManagerConfig {
|
|
|
33
33
|
* URL prefix the asset routes mount under. The aurora runtime is served
|
|
34
34
|
* from `<assetsPrefix>/aurora/*` and the app's pages from
|
|
35
35
|
* `<assetsPrefix>/pages/*`, and the SSR importmap + page URLs derive from
|
|
36
|
-
* it. Default `/
|
|
36
|
+
* it. Default `/__assets` (the leading underscore namespaces framework
|
|
37
37
|
* assets away from app routes, Next.js `/_next` style). Set e.g. `/assets`
|
|
38
38
|
* for an underscore-free scheme. An explicit `pages.urlPrefix` still wins.
|
|
39
39
|
*/
|
|
40
40
|
assetsPrefix?: string;
|
|
41
|
+
/**
|
|
42
|
+
* App-level importmap overrides, configured ONCE here (the AdonisJS model:
|
|
43
|
+
* config lives in `config/aurora.ts`, controllers stay thin and never hand-write
|
|
44
|
+
* importmaps). Merged over aurora's auto defaults (`@c9up/aurora`,
|
|
45
|
+
* `@c9up/aurora/rpc`, `@c9up/comet`) on every `render()`. Use to point a bare
|
|
46
|
+
* specifier at a curated browser entry, e.g.
|
|
47
|
+
* `{ '@c9up/aurora': '/__assets/pages/browser/aurora.js' }`. A per-call
|
|
48
|
+
* `render(..., { importmap })` still wins over this.
|
|
49
|
+
*
|
|
50
|
+
* (Importmaps are aurora's no-bundler particularity — AdonisJS bundles via Vite
|
|
51
|
+
* and has no importmap; we keep the config-driven shape to stay Adonis-idiomatic.)
|
|
52
|
+
*/
|
|
53
|
+
importmap?: Record<string, string>;
|
|
41
54
|
}
|
|
42
55
|
export declare class AuroraManager {
|
|
43
56
|
readonly pages: Pages;
|
|
44
57
|
readonly auroraDistRoot: string;
|
|
45
|
-
/** Resolved asset prefix (default `/
|
|
58
|
+
/** Resolved asset prefix (default `/__assets`). */
|
|
46
59
|
readonly assetsPrefix: string;
|
|
47
60
|
/** Mount path for the aurora runtime — `<assetsPrefix>/aurora`. */
|
|
48
61
|
readonly auroraAssetPath: string;
|
|
@@ -52,22 +65,25 @@ export declare class AuroraManager {
|
|
|
52
65
|
readonly cometAssetPath: string;
|
|
53
66
|
/** Resolved `@c9up/comet` dist dir, or `null` when comet isn't installed. */
|
|
54
67
|
readonly cometDistRoot: string | null;
|
|
68
|
+
/** App-level importmap overrides from `config/aurora.ts`, merged on render. */
|
|
69
|
+
readonly importmap: Record<string, string>;
|
|
55
70
|
constructor(config: AuroraManagerConfig);
|
|
56
71
|
/**
|
|
57
|
-
* SSR + hydrate + ship the document. The importmap
|
|
58
|
-
* `@c9up/aurora
|
|
59
|
-
* `
|
|
60
|
-
*
|
|
72
|
+
* SSR + hydrate + ship the document. The importmap layers, last wins:
|
|
73
|
+
* aurora's auto defaults (`@c9up/aurora`, `@c9up/aurora/rpc`, `@c9up/comet`)
|
|
74
|
+
* < the config-level `importmap` (from `config/aurora.ts`) < a per-call
|
|
75
|
+
* `options.importmap`. So a thin controller calls `render(ctx, name, props)`
|
|
76
|
+
* with no importmap, and any curation lives once in config.
|
|
61
77
|
*/
|
|
62
78
|
render(ctx: RenderHttpContext, name: string, props: unknown, options?: RenderPageOptions): Promise<void>;
|
|
63
79
|
/**
|
|
64
80
|
* Handler for aurora's pre-built ESM runtime. Mount on
|
|
65
|
-
* `GET /
|
|
81
|
+
* `GET /__assets/aurora/*`.
|
|
66
82
|
*/
|
|
67
83
|
auroraAssetsHandler(): (ctx: AssetsHttpContext) => Promise<void>;
|
|
68
84
|
/**
|
|
69
85
|
* Handler for the app's pages directory. Mount on
|
|
70
|
-
* `GET /
|
|
86
|
+
* `GET /__assets/pages/*`.
|
|
71
87
|
*/
|
|
72
88
|
pageAssetsHandler(): (ctx: AssetsHttpContext) => Promise<void>;
|
|
73
89
|
/**
|
package/dist/AuroraManager.js
CHANGED
|
@@ -38,7 +38,7 @@ function normalizePrefix(prefix) {
|
|
|
38
38
|
export class AuroraManager {
|
|
39
39
|
pages;
|
|
40
40
|
auroraDistRoot;
|
|
41
|
-
/** Resolved asset prefix (default `/
|
|
41
|
+
/** Resolved asset prefix (default `/__assets`). */
|
|
42
42
|
assetsPrefix;
|
|
43
43
|
/** Mount path for the aurora runtime — `<assetsPrefix>/aurora`. */
|
|
44
44
|
auroraAssetPath;
|
|
@@ -48,12 +48,15 @@ export class AuroraManager {
|
|
|
48
48
|
cometAssetPath;
|
|
49
49
|
/** Resolved `@c9up/comet` dist dir, or `null` when comet isn't installed. */
|
|
50
50
|
cometDistRoot;
|
|
51
|
+
/** App-level importmap overrides from `config/aurora.ts`, merged on render. */
|
|
52
|
+
importmap;
|
|
51
53
|
constructor(config) {
|
|
52
|
-
this.assetsPrefix = normalizePrefix(config.assetsPrefix ?? "/
|
|
54
|
+
this.assetsPrefix = normalizePrefix(config.assetsPrefix ?? "/__assets");
|
|
53
55
|
this.auroraAssetPath = `${this.assetsPrefix}/aurora`;
|
|
54
56
|
this.pageAssetPath = `${this.assetsPrefix}/pages`;
|
|
55
57
|
this.cometAssetPath = `${this.assetsPrefix}/comet`;
|
|
56
58
|
this.cometDistRoot = config.cometDistRoot ?? resolveCometDist();
|
|
59
|
+
this.importmap = config.importmap ?? {};
|
|
57
60
|
// Pages serve their compiled JS from the same prefix unless the app
|
|
58
61
|
// pins an explicit urlPrefix.
|
|
59
62
|
this.pages = new Pages({
|
|
@@ -63,10 +66,11 @@ export class AuroraManager {
|
|
|
63
66
|
this.auroraDistRoot = config.auroraDistRoot ?? DEFAULT_AURORA_DIST;
|
|
64
67
|
}
|
|
65
68
|
/**
|
|
66
|
-
* SSR + hydrate + ship the document. The importmap
|
|
67
|
-
* `@c9up/aurora
|
|
68
|
-
* `
|
|
69
|
-
*
|
|
69
|
+
* SSR + hydrate + ship the document. The importmap layers, last wins:
|
|
70
|
+
* aurora's auto defaults (`@c9up/aurora`, `@c9up/aurora/rpc`, `@c9up/comet`)
|
|
71
|
+
* < the config-level `importmap` (from `config/aurora.ts`) < a per-call
|
|
72
|
+
* `options.importmap`. So a thin controller calls `render(ctx, name, props)`
|
|
73
|
+
* with no importmap, and any curation lives once in config.
|
|
70
74
|
*/
|
|
71
75
|
render(ctx, name, props, options) {
|
|
72
76
|
return renderPage(ctx, this.pages, name, props, {
|
|
@@ -84,20 +88,23 @@ export class AuroraManager {
|
|
|
84
88
|
...(this.cometDistRoot
|
|
85
89
|
? { "@c9up/comet": `${this.cometAssetPath}/index.js` }
|
|
86
90
|
: {}),
|
|
91
|
+
// Config-level overrides (config/aurora.ts) — Adonis-style config-driven,
|
|
92
|
+
// so controllers never hand-write an importmap. A per-call override wins.
|
|
93
|
+
...this.importmap,
|
|
87
94
|
...options?.importmap,
|
|
88
95
|
},
|
|
89
96
|
});
|
|
90
97
|
}
|
|
91
98
|
/**
|
|
92
99
|
* Handler for aurora's pre-built ESM runtime. Mount on
|
|
93
|
-
* `GET /
|
|
100
|
+
* `GET /__assets/aurora/*`.
|
|
94
101
|
*/
|
|
95
102
|
auroraAssetsHandler() {
|
|
96
103
|
return serveAssets({ root: this.auroraDistRoot });
|
|
97
104
|
}
|
|
98
105
|
/**
|
|
99
106
|
* Handler for the app's pages directory. Mount on
|
|
100
|
-
* `GET /
|
|
107
|
+
* `GET /__assets/pages/*`.
|
|
101
108
|
*/
|
|
102
109
|
pageAssetsHandler() {
|
|
103
110
|
return serveAssets({ root: this.pages.root });
|
package/dist/AuroraProvider.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* AuroraProvider — registers the AuroraManager singleton and auto-mounts
|
|
3
3
|
* the two asset routes the browser needs:
|
|
4
4
|
*
|
|
5
|
-
* GET /
|
|
6
|
-
* GET /
|
|
5
|
+
* GET /__assets/aurora/* → packages/@c9up/aurora/dist/*
|
|
6
|
+
* GET /__assets/pages/* → resources/pages/*
|
|
7
7
|
*
|
|
8
8
|
* Config (in `config/aurora.ts`):
|
|
9
9
|
*
|
package/dist/AuroraProvider.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* AuroraProvider — registers the AuroraManager singleton and auto-mounts
|
|
3
3
|
* the two asset routes the browser needs:
|
|
4
4
|
*
|
|
5
|
-
* GET /
|
|
6
|
-
* GET /
|
|
5
|
+
* GET /__assets/aurora/* → packages/@c9up/aurora/dist/*
|
|
6
|
+
* GET /__assets/pages/* → resources/pages/*
|
|
7
7
|
*
|
|
8
8
|
* Config (in `config/aurora.ts`):
|
|
9
9
|
*
|
|
@@ -64,7 +64,7 @@ export default class AuroraProvider {
|
|
|
64
64
|
const router = this.app.container.resolve("router");
|
|
65
65
|
const manager = this.app.container.resolve(AuroraManager);
|
|
66
66
|
// Mount paths derive from the configured `assetsPrefix` (default
|
|
67
|
-
// `/
|
|
67
|
+
// `/__assets`) — set `config.aurora.assetsPrefix` to change the scheme.
|
|
68
68
|
router.get(`${manager.auroraAssetPath}/*`, adaptHandler(manager.auroraAssetsHandler()));
|
|
69
69
|
router.get(`${manager.pageAssetPath}/*`, adaptHandler(manager.pageAssetsHandler()));
|
|
70
70
|
// Serve @c9up/comet's runtime so the RPC client's bare `import
|
package/dist/Pages.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface PagesConfig {
|
|
|
25
25
|
root: string;
|
|
26
26
|
/**
|
|
27
27
|
* URL prefix the browser uses to fetch a page's compiled JS.
|
|
28
|
-
* Defaults to `/
|
|
28
|
+
* Defaults to `/__assets/pages`. A name `"Foo"` maps to
|
|
29
29
|
* `${urlPrefix}/Foo.js`.
|
|
30
30
|
*/
|
|
31
31
|
urlPrefix?: string;
|
package/dist/Pages.js
CHANGED
|
@@ -29,7 +29,7 @@ export class Pages {
|
|
|
29
29
|
registry = new Map();
|
|
30
30
|
constructor(config) {
|
|
31
31
|
this.root = config.root;
|
|
32
|
-
this.urlPrefix = (config.urlPrefix ?? "/
|
|
32
|
+
this.urlPrefix = (config.urlPrefix ?? "/__assets/pages").replace(/\/$/, "");
|
|
33
33
|
this.extension = config.extension ?? ".js";
|
|
34
34
|
}
|
|
35
35
|
/**
|
package/dist/live.d.ts
CHANGED
|
@@ -53,3 +53,8 @@ export interface LiveSession {
|
|
|
53
53
|
* every session then reads the same signal and patches on its change.
|
|
54
54
|
*/
|
|
55
55
|
export declare function mountLiveSession(factory: () => LiveComponentDefinition): LiveSession;
|
|
56
|
+
export { connectPatches, type LiveStore, liveStore, type RelayBroadcaster, } from "./liveBroadcast.js";
|
|
57
|
+
export { buildLiveTransport, type LiveClientOptions, type LiveClientTransport, type LiveHttpPoster, liveClient, type RelaySubscribeClient, } from "./liveClient.js";
|
|
58
|
+
export { createLiveRegistry, type LiveRegistry, type LiveSessionHandle, } from "./liveRegistry.js";
|
|
59
|
+
export { createLiveRouter, type LiveMount, type LiveRouter, } from "./liveRouter.js";
|
|
60
|
+
export { DEFAULT_LIVE_EVENT_PATH, type LiveHttpContext, type LiveHttpRouter, type WireLiveEventsOptions, wireLiveEvents, } from "./liveServer.js";
|
package/dist/live.js
CHANGED
|
@@ -94,3 +94,15 @@ export function mountLiveSession(factory) {
|
|
|
94
94
|
},
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
+
// ─── `@c9up/aurora/live` barrel ──────────────────────────────────────
|
|
98
|
+
//
|
|
99
|
+
// The Live stack (broadcast / client / registry / router / server) is
|
|
100
|
+
// re-exported here so the whole niche surface lives behind ONE subpath,
|
|
101
|
+
// `@c9up/aurora/live` — keeping the main `.` barrel lean (it no longer
|
|
102
|
+
// eager-pulls these into every browser graph), consistent with how `./ssr`,
|
|
103
|
+
// `./relay`, `./rpc` and `./hydrate` are already subpath-gated.
|
|
104
|
+
export { connectPatches, liveStore, } from "./liveBroadcast.js";
|
|
105
|
+
export { buildLiveTransport, liveClient, } from "./liveClient.js";
|
|
106
|
+
export { createLiveRegistry, } from "./liveRegistry.js";
|
|
107
|
+
export { createLiveRouter, } from "./liveRouter.js";
|
|
108
|
+
export { DEFAULT_LIVE_EVENT_PATH, wireLiveEvents, } from "./liveServer.js";
|
package/dist/liveClient.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface LiveHttpPoster {
|
|
|
61
61
|
/**
|
|
62
62
|
* Build a {@link LiveClientTransport} from a relay client (SSE down) + an HTTP
|
|
63
63
|
* client (events up). `path` must match the server's `wireLiveEvents` route
|
|
64
|
-
* (default `/
|
|
64
|
+
* (default `/__live/event`). Keeps `liveClient` itself transport-agnostic.
|
|
65
65
|
*
|
|
66
66
|
* @example
|
|
67
67
|
* import { relay } from '@c9up/aurora/relay'
|
package/dist/liveClient.js
CHANGED
|
@@ -58,7 +58,7 @@ export function liveClient(opts) {
|
|
|
58
58
|
/**
|
|
59
59
|
* Build a {@link LiveClientTransport} from a relay client (SSE down) + an HTTP
|
|
60
60
|
* client (events up). `path` must match the server's `wireLiveEvents` route
|
|
61
|
-
* (default `/
|
|
61
|
+
* (default `/__live/event`). Keeps `liveClient` itself transport-agnostic.
|
|
62
62
|
*
|
|
63
63
|
* @example
|
|
64
64
|
* import { relay } from '@c9up/aurora/relay'
|
|
@@ -67,7 +67,7 @@ export function liveClient(opts) {
|
|
|
67
67
|
* liveClient({ container, factory, mount, transport })
|
|
68
68
|
*/
|
|
69
69
|
export function buildLiveTransport(relayClient, http, options = {}) {
|
|
70
|
-
const path = options.path ?? "/
|
|
70
|
+
const path = options.path ?? "/__live/event";
|
|
71
71
|
return {
|
|
72
72
|
subscribe: (channel, handler) => relayClient.subscribe(channel, handler),
|
|
73
73
|
post: (id, event, payload) => {
|
package/dist/liveServer.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface WireLiveEventsOptions {
|
|
|
29
29
|
path?: string;
|
|
30
30
|
}
|
|
31
31
|
/** Default inbound-event route — keep the client transport's `path` in sync. */
|
|
32
|
-
export declare const DEFAULT_LIVE_EVENT_PATH = "/
|
|
32
|
+
export declare const DEFAULT_LIVE_EVENT_PATH = "/__live/event";
|
|
33
33
|
/**
|
|
34
34
|
* Register the inbound live-event route on the host router. Call once at boot
|
|
35
35
|
* (e.g. from a provider that resolved the router + relay from the container).
|
package/dist/liveServer.js
CHANGED
|
@@ -18,7 +18,7 @@ function isLiveEventBody(value) {
|
|
|
18
18
|
return typeof value.id === "string" && typeof value.event === "string";
|
|
19
19
|
}
|
|
20
20
|
/** Default inbound-event route — keep the client transport's `path` in sync. */
|
|
21
|
-
export const DEFAULT_LIVE_EVENT_PATH = "/
|
|
21
|
+
export const DEFAULT_LIVE_EVENT_PATH = "/__live/event";
|
|
22
22
|
/**
|
|
23
23
|
* Register the inbound live-event route on the host router. Call once at boot
|
|
24
24
|
* (e.g. from a provider that resolved the router + relay from the container).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `auroraContext` — binds `ctx.aurora.render(name, props)` onto the request
|
|
3
|
+
* context, the AdonisJS ctx-service idiom (the `ctx.view` / `ctx.inertia`
|
|
4
|
+
* analog). Register it globally in your kernel; a thin controller then does:
|
|
5
|
+
*
|
|
6
|
+
* async show({ aurora }: HttpContext) {
|
|
7
|
+
* return aurora.render('Dashboard', { user, stats })
|
|
8
|
+
* }
|
|
9
|
+
*
|
|
10
|
+
* Agnostic: it resolves the AuroraManager from the request container
|
|
11
|
+
* (`ctx.containerResolver.make('aurora')`) — never imports `@c9up/ream` — and is
|
|
12
|
+
* a no-op when no manager is registered. The module-level `aurora.render(ctx, …)`
|
|
13
|
+
* service still works; this is the ctx-bound sugar.
|
|
14
|
+
*/
|
|
15
|
+
import type { RenderHttpContext, RenderPageOptions } from "./server/renderPage.js";
|
|
16
|
+
/** The `ctx.aurora` surface — ctx-bound render (no explicit ctx argument). */
|
|
17
|
+
export interface AuroraRequestRenderer {
|
|
18
|
+
render(name: string, props?: unknown, options?: RenderPageOptions): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** Request context the middleware needs: render target + optional resolver/slot. */
|
|
21
|
+
interface AuroraMiddlewareContext extends RenderHttpContext {
|
|
22
|
+
containerResolver?: {
|
|
23
|
+
make(token: unknown): unknown;
|
|
24
|
+
};
|
|
25
|
+
aurora?: AuroraRequestRenderer;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Middleware: attach `ctx.aurora` for the request. No-op (passes through) when
|
|
29
|
+
* the AuroraManager isn't registered, so it's safe to mount unconditionally.
|
|
30
|
+
*/
|
|
31
|
+
export declare function auroraContext(ctx: AuroraMiddlewareContext, next: () => Promise<void>): Promise<void>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `auroraContext` — binds `ctx.aurora.render(name, props)` onto the request
|
|
3
|
+
* context, the AdonisJS ctx-service idiom (the `ctx.view` / `ctx.inertia`
|
|
4
|
+
* analog). Register it globally in your kernel; a thin controller then does:
|
|
5
|
+
*
|
|
6
|
+
* async show({ aurora }: HttpContext) {
|
|
7
|
+
* return aurora.render('Dashboard', { user, stats })
|
|
8
|
+
* }
|
|
9
|
+
*
|
|
10
|
+
* Agnostic: it resolves the AuroraManager from the request container
|
|
11
|
+
* (`ctx.containerResolver.make('aurora')`) — never imports `@c9up/ream` — and is
|
|
12
|
+
* a no-op when no manager is registered. The module-level `aurora.render(ctx, …)`
|
|
13
|
+
* service still works; this is the ctx-bound sugar.
|
|
14
|
+
*/
|
|
15
|
+
/** Structural check that a resolved value is render-capable (an AuroraManager). */
|
|
16
|
+
function isManager(value) {
|
|
17
|
+
return (typeof value === "object" &&
|
|
18
|
+
value !== null &&
|
|
19
|
+
"render" in value &&
|
|
20
|
+
typeof value.render === "function");
|
|
21
|
+
}
|
|
22
|
+
function resolveManager(resolver) {
|
|
23
|
+
try {
|
|
24
|
+
const resolved = resolver?.make("aurora");
|
|
25
|
+
return isManager(resolved) ? resolved : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Middleware: attach `ctx.aurora` for the request. No-op (passes through) when
|
|
33
|
+
* the AuroraManager isn't registered, so it's safe to mount unconditionally.
|
|
34
|
+
*/
|
|
35
|
+
export function auroraContext(ctx, next) {
|
|
36
|
+
const manager = resolveManager(ctx.containerResolver);
|
|
37
|
+
if (manager) {
|
|
38
|
+
ctx.aurora = {
|
|
39
|
+
render: (name, props, options) => manager.render(ctx, name, props, options),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return next();
|
|
43
|
+
}
|
package/dist/rpc.d.ts
CHANGED
|
@@ -8,9 +8,23 @@ export interface RpcClientOptions {
|
|
|
8
8
|
http?: HttpClient;
|
|
9
9
|
/** Default headers — only used when no `http` client is supplied. */
|
|
10
10
|
headers?: Record<string, string>;
|
|
11
|
+
/**
|
|
12
|
+
* Auto CSRF: read the `XSRF-TOKEN` cookie and echo it as `X-XSRF-TOKEN` on
|
|
13
|
+
* every call (Axios/Angular convention), so RPC POSTs pass blackhole's
|
|
14
|
+
* signed double-submit check when the route is cookie/session-authed. No-op
|
|
15
|
+
* outside the browser and when the cookie is absent. Default `true`.
|
|
16
|
+
* `/rpc` under a bearer (JWT) guard is CSRF-exempt, so the missing-cookie
|
|
17
|
+
* no-op is exactly right there too.
|
|
18
|
+
*/
|
|
19
|
+
xsrf?: boolean;
|
|
20
|
+
/** Cookie to read the CSRF token from. Default `XSRF-TOKEN`. */
|
|
21
|
+
xsrfCookieName?: string;
|
|
22
|
+
/** Header to echo the CSRF token in. Default `X-XSRF-TOKEN`. */
|
|
23
|
+
xsrfHeaderName?: string;
|
|
11
24
|
}
|
|
12
25
|
/**
|
|
13
26
|
* Create a JSON-RPC client bound to aurora's HttpClient transport. Inherits the
|
|
14
|
-
* supplied (or a fresh) HttpClient's base URL, auth headers, and timeouts
|
|
27
|
+
* supplied (or a fresh) HttpClient's base URL, auth headers, and timeouts, and
|
|
28
|
+
* (by default) auto-attaches the `X-XSRF-TOKEN` CSRF header from the cookie.
|
|
15
29
|
*/
|
|
16
30
|
export declare function createRpcClient(options?: RpcClientOptions): RpcClient;
|
package/dist/rpc.js
CHANGED
|
@@ -16,14 +16,43 @@
|
|
|
16
16
|
import { createRpcClient as createCometRpcClient } from "@c9up/comet";
|
|
17
17
|
import { HttpClient } from "./http.js";
|
|
18
18
|
export { isRpcError, RpcError, } from "@c9up/comet";
|
|
19
|
+
/**
|
|
20
|
+
* Read a cookie's raw value from `document.cookie`. Returns `undefined`
|
|
21
|
+
* server-side (no `document`) or when the cookie is absent. The value is sent
|
|
22
|
+
* verbatim — double-submit compares it byte-for-byte against the cookie, so it
|
|
23
|
+
* must not be decoded.
|
|
24
|
+
*/
|
|
25
|
+
function readCookie(name) {
|
|
26
|
+
if (typeof document === "undefined")
|
|
27
|
+
return undefined;
|
|
28
|
+
const prefix = `${name}=`;
|
|
29
|
+
for (const part of document.cookie.split(";")) {
|
|
30
|
+
const trimmed = part.trimStart();
|
|
31
|
+
if (trimmed.startsWith(prefix))
|
|
32
|
+
return trimmed.slice(prefix.length);
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
19
36
|
/**
|
|
20
37
|
* Create a JSON-RPC client bound to aurora's HttpClient transport. Inherits the
|
|
21
|
-
* supplied (or a fresh) HttpClient's base URL, auth headers, and timeouts
|
|
38
|
+
* supplied (or a fresh) HttpClient's base URL, auth headers, and timeouts, and
|
|
39
|
+
* (by default) auto-attaches the `X-XSRF-TOKEN` CSRF header from the cookie.
|
|
22
40
|
*/
|
|
23
41
|
export function createRpcClient(options = {}) {
|
|
24
42
|
const http = options.http ?? new HttpClient({ headers: options.headers });
|
|
43
|
+
const xsrfEnabled = options.xsrf ?? true;
|
|
44
|
+
const cookieName = options.xsrfCookieName ?? "XSRF-TOKEN";
|
|
45
|
+
const headerName = options.xsrfHeaderName ?? "X-XSRF-TOKEN";
|
|
25
46
|
return createCometRpcClient({
|
|
26
47
|
url: options.url,
|
|
27
|
-
transport: (url, body, { signal }) =>
|
|
48
|
+
transport: (url, body, { signal }) => {
|
|
49
|
+
let headers;
|
|
50
|
+
if (xsrfEnabled) {
|
|
51
|
+
const token = readCookie(cookieName);
|
|
52
|
+
if (token !== undefined)
|
|
53
|
+
headers = { [headerName]: token };
|
|
54
|
+
}
|
|
55
|
+
return http.post(url, body, { signal, headers });
|
|
56
|
+
},
|
|
28
57
|
});
|
|
29
58
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* <head>
|
|
9
9
|
* ...
|
|
10
10
|
* <script type="importmap">
|
|
11
|
-
* { "imports": { "@c9up/aurora": "/
|
|
11
|
+
* { "imports": { "@c9up/aurora": "/__assets/aurora/index.js" } }
|
|
12
12
|
* </script>
|
|
13
13
|
* </head>
|
|
14
14
|
* <body>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* <script id="aurora-page-data" type="application/json">{…}</script>
|
|
17
17
|
* <script type="module">
|
|
18
18
|
* import { hydrate } from '@c9up/aurora'
|
|
19
|
-
* import Page from '/
|
|
19
|
+
* import Page from '/__assets/pages/ProjectPage.js'
|
|
20
20
|
* const data = JSON.parse(document.getElementById('aurora-page-data').textContent)
|
|
21
21
|
* hydrate(document.getElementById('aurora-root'), () => Page(data.props))
|
|
22
22
|
* </script>
|
|
@@ -39,7 +39,7 @@ export interface RenderHttpContext {
|
|
|
39
39
|
export interface RenderPageOptions {
|
|
40
40
|
/**
|
|
41
41
|
* Importmap entries injected into `<head>`. Defaults to mapping
|
|
42
|
-
* `@c9up/aurora` to `/
|
|
42
|
+
* `@c9up/aurora` to `/__assets/aurora/index.js`. Override to point
|
|
43
43
|
* at a different mount or to add app-side aliases.
|
|
44
44
|
*/
|
|
45
45
|
importmap?: Record<string, string>;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* <head>
|
|
9
9
|
* ...
|
|
10
10
|
* <script type="importmap">
|
|
11
|
-
* { "imports": { "@c9up/aurora": "/
|
|
11
|
+
* { "imports": { "@c9up/aurora": "/__assets/aurora/index.js" } }
|
|
12
12
|
* </script>
|
|
13
13
|
* </head>
|
|
14
14
|
* <body>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* <script id="aurora-page-data" type="application/json">{…}</script>
|
|
17
17
|
* <script type="module">
|
|
18
18
|
* import { hydrate } from '@c9up/aurora'
|
|
19
|
-
* import Page from '/
|
|
19
|
+
* import Page from '/__assets/pages/ProjectPage.js'
|
|
20
20
|
* const data = JSON.parse(document.getElementById('aurora-page-data').textContent)
|
|
21
21
|
* hydrate(document.getElementById('aurora-root'), () => Page(data.props))
|
|
22
22
|
* </script>
|
|
@@ -58,7 +58,7 @@ export async function renderPage(ctx, pages, name, props, options = {}) {
|
|
|
58
58
|
const tree = await factory(props);
|
|
59
59
|
const body = renderToString(tree);
|
|
60
60
|
const importmap = {
|
|
61
|
-
"@c9up/aurora": "/
|
|
61
|
+
"@c9up/aurora": "/__assets/aurora/index.js",
|
|
62
62
|
...options.importmap,
|
|
63
63
|
};
|
|
64
64
|
const rootId = options.rootId ?? "aurora-root";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* `serveAssets` — generic static-file handler exposed by aurora so an
|
|
3
3
|
* app can mount the runtime + the pages dist with a couple of routes:
|
|
4
4
|
*
|
|
5
|
-
* router.get('/
|
|
6
|
-
* router.get('/
|
|
5
|
+
* router.get('/__assets/aurora/*', serveAssets({ root: auroraDistPath }))
|
|
6
|
+
* router.get('/__assets/pages/*', serveAssets({ root: pagesPath }))
|
|
7
7
|
*
|
|
8
8
|
* The handler is framework-agnostic: it reads `ctx.request.param('*')`
|
|
9
9
|
* and writes to `ctx.response`. Any context that satisfies
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* `serveAssets` — generic static-file handler exposed by aurora so an
|
|
3
3
|
* app can mount the runtime + the pages dist with a couple of routes:
|
|
4
4
|
*
|
|
5
|
-
* router.get('/
|
|
6
|
-
* router.get('/
|
|
5
|
+
* router.get('/__assets/aurora/*', serveAssets({ root: auroraDistPath }))
|
|
6
|
+
* router.get('/__assets/pages/*', serveAssets({ root: pagesPath }))
|
|
7
7
|
*
|
|
8
8
|
* The handler is framework-agnostic: it reads `ctx.request.param('*')`
|
|
9
9
|
* and writes to `ctx.response`. Any context that satisfies
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AuroraManager, type AuroraManagerConfig } from "./AuroraManager.js";
|
|
2
|
+
export { type AuroraRequestRenderer, auroraContext, } from "./middleware.js";
|
|
2
3
|
export { type PageFactory, Pages, type PagesConfig } from "./Pages.js";
|
|
3
4
|
export { type RenderHttpContext, type RenderPageOptions, type RenderResponse, renderPage, } from "./server/renderPage.js";
|
|
4
5
|
export { type AssetsHttpContext, type AssetsRequest, type AssetsResponse, packageAssetDir, type ServeAssetsOptions, serveAssets, } from "./server/serveAssets.js";
|
package/dist/server.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// Node built-ins through the import graph. Server code imports from
|
|
6
6
|
// `@c9up/aurora/server`; the client `.` entry stays node-free.
|
|
7
7
|
export { AuroraManager } from "./AuroraManager.js";
|
|
8
|
+
export { auroraContext, } from "./middleware.js";
|
|
8
9
|
export { Pages } from "./Pages.js";
|
|
9
10
|
export { renderPage, } from "./server/renderPage.js";
|
|
10
11
|
export { packageAssetDir, serveAssets, } from "./server/serveAssets.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/aurora",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Aurora — reactive UI runtime for the Ream framework. Tagged-template DOM, signal-based state, isomorphic SSR + hydration, zero build step.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/AuroraManager.ts
CHANGED
|
@@ -45,11 +45,24 @@ export interface AuroraManagerConfig {
|
|
|
45
45
|
* URL prefix the asset routes mount under. The aurora runtime is served
|
|
46
46
|
* from `<assetsPrefix>/aurora/*` and the app's pages from
|
|
47
47
|
* `<assetsPrefix>/pages/*`, and the SSR importmap + page URLs derive from
|
|
48
|
-
* it. Default `/
|
|
48
|
+
* it. Default `/__assets` (the leading underscore namespaces framework
|
|
49
49
|
* assets away from app routes, Next.js `/_next` style). Set e.g. `/assets`
|
|
50
50
|
* for an underscore-free scheme. An explicit `pages.urlPrefix` still wins.
|
|
51
51
|
*/
|
|
52
52
|
assetsPrefix?: string;
|
|
53
|
+
/**
|
|
54
|
+
* App-level importmap overrides, configured ONCE here (the AdonisJS model:
|
|
55
|
+
* config lives in `config/aurora.ts`, controllers stay thin and never hand-write
|
|
56
|
+
* importmaps). Merged over aurora's auto defaults (`@c9up/aurora`,
|
|
57
|
+
* `@c9up/aurora/rpc`, `@c9up/comet`) on every `render()`. Use to point a bare
|
|
58
|
+
* specifier at a curated browser entry, e.g.
|
|
59
|
+
* `{ '@c9up/aurora': '/__assets/pages/browser/aurora.js' }`. A per-call
|
|
60
|
+
* `render(..., { importmap })` still wins over this.
|
|
61
|
+
*
|
|
62
|
+
* (Importmaps are aurora's no-bundler particularity — AdonisJS bundles via Vite
|
|
63
|
+
* and has no importmap; we keep the config-driven shape to stay Adonis-idiomatic.)
|
|
64
|
+
*/
|
|
65
|
+
importmap?: Record<string, string>;
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
const DEFAULT_AURORA_DIST = resolvePath(
|
|
@@ -79,7 +92,7 @@ function normalizePrefix(prefix: string): string {
|
|
|
79
92
|
export class AuroraManager {
|
|
80
93
|
readonly pages: Pages;
|
|
81
94
|
readonly auroraDistRoot: string;
|
|
82
|
-
/** Resolved asset prefix (default `/
|
|
95
|
+
/** Resolved asset prefix (default `/__assets`). */
|
|
83
96
|
readonly assetsPrefix: string;
|
|
84
97
|
/** Mount path for the aurora runtime — `<assetsPrefix>/aurora`. */
|
|
85
98
|
readonly auroraAssetPath: string;
|
|
@@ -89,13 +102,16 @@ export class AuroraManager {
|
|
|
89
102
|
readonly cometAssetPath: string;
|
|
90
103
|
/** Resolved `@c9up/comet` dist dir, or `null` when comet isn't installed. */
|
|
91
104
|
readonly cometDistRoot: string | null;
|
|
105
|
+
/** App-level importmap overrides from `config/aurora.ts`, merged on render. */
|
|
106
|
+
readonly importmap: Record<string, string>;
|
|
92
107
|
|
|
93
108
|
constructor(config: AuroraManagerConfig) {
|
|
94
|
-
this.assetsPrefix = normalizePrefix(config.assetsPrefix ?? "/
|
|
109
|
+
this.assetsPrefix = normalizePrefix(config.assetsPrefix ?? "/__assets");
|
|
95
110
|
this.auroraAssetPath = `${this.assetsPrefix}/aurora`;
|
|
96
111
|
this.pageAssetPath = `${this.assetsPrefix}/pages`;
|
|
97
112
|
this.cometAssetPath = `${this.assetsPrefix}/comet`;
|
|
98
113
|
this.cometDistRoot = config.cometDistRoot ?? resolveCometDist();
|
|
114
|
+
this.importmap = config.importmap ?? {};
|
|
99
115
|
// Pages serve their compiled JS from the same prefix unless the app
|
|
100
116
|
// pins an explicit urlPrefix.
|
|
101
117
|
this.pages = new Pages({
|
|
@@ -106,10 +122,11 @@ export class AuroraManager {
|
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
/**
|
|
109
|
-
* SSR + hydrate + ship the document. The importmap
|
|
110
|
-
* `@c9up/aurora
|
|
111
|
-
* `
|
|
112
|
-
*
|
|
125
|
+
* SSR + hydrate + ship the document. The importmap layers, last wins:
|
|
126
|
+
* aurora's auto defaults (`@c9up/aurora`, `@c9up/aurora/rpc`, `@c9up/comet`)
|
|
127
|
+
* < the config-level `importmap` (from `config/aurora.ts`) < a per-call
|
|
128
|
+
* `options.importmap`. So a thin controller calls `render(ctx, name, props)`
|
|
129
|
+
* with no importmap, and any curation lives once in config.
|
|
113
130
|
*/
|
|
114
131
|
render(
|
|
115
132
|
ctx: RenderHttpContext,
|
|
@@ -132,6 +149,9 @@ export class AuroraManager {
|
|
|
132
149
|
...(this.cometDistRoot
|
|
133
150
|
? { "@c9up/comet": `${this.cometAssetPath}/index.js` }
|
|
134
151
|
: {}),
|
|
152
|
+
// Config-level overrides (config/aurora.ts) — Adonis-style config-driven,
|
|
153
|
+
// so controllers never hand-write an importmap. A per-call override wins.
|
|
154
|
+
...this.importmap,
|
|
135
155
|
...options?.importmap,
|
|
136
156
|
},
|
|
137
157
|
});
|
|
@@ -139,7 +159,7 @@ export class AuroraManager {
|
|
|
139
159
|
|
|
140
160
|
/**
|
|
141
161
|
* Handler for aurora's pre-built ESM runtime. Mount on
|
|
142
|
-
* `GET /
|
|
162
|
+
* `GET /__assets/aurora/*`.
|
|
143
163
|
*/
|
|
144
164
|
auroraAssetsHandler(): (ctx: AssetsHttpContext) => Promise<void> {
|
|
145
165
|
return serveAssets({ root: this.auroraDistRoot });
|
|
@@ -147,7 +167,7 @@ export class AuroraManager {
|
|
|
147
167
|
|
|
148
168
|
/**
|
|
149
169
|
* Handler for the app's pages directory. Mount on
|
|
150
|
-
* `GET /
|
|
170
|
+
* `GET /__assets/pages/*`.
|
|
151
171
|
*/
|
|
152
172
|
pageAssetsHandler(): (ctx: AssetsHttpContext) => Promise<void> {
|
|
153
173
|
return serveAssets({ root: this.pages.root });
|
package/src/AuroraProvider.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* AuroraProvider — registers the AuroraManager singleton and auto-mounts
|
|
3
3
|
* the two asset routes the browser needs:
|
|
4
4
|
*
|
|
5
|
-
* GET /
|
|
6
|
-
* GET /
|
|
5
|
+
* GET /__assets/aurora/* → packages/@c9up/aurora/dist/*
|
|
6
|
+
* GET /__assets/pages/* → resources/pages/*
|
|
7
7
|
*
|
|
8
8
|
* Config (in `config/aurora.ts`):
|
|
9
9
|
*
|
|
@@ -92,7 +92,7 @@ export default class AuroraProvider {
|
|
|
92
92
|
const router = this.app.container.resolve<ReamRouter>("router");
|
|
93
93
|
const manager = this.app.container.resolve<AuroraManager>(AuroraManager);
|
|
94
94
|
// Mount paths derive from the configured `assetsPrefix` (default
|
|
95
|
-
// `/
|
|
95
|
+
// `/__assets`) — set `config.aurora.assetsPrefix` to change the scheme.
|
|
96
96
|
router.get(
|
|
97
97
|
`${manager.auroraAssetPath}/*`,
|
|
98
98
|
adaptHandler(manager.auroraAssetsHandler()),
|
package/src/Pages.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface PagesConfig {
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* URL prefix the browser uses to fetch a page's compiled JS.
|
|
36
|
-
* Defaults to `/
|
|
36
|
+
* Defaults to `/__assets/pages`. A name `"Foo"` maps to
|
|
37
37
|
* `${urlPrefix}/Foo.js`.
|
|
38
38
|
*/
|
|
39
39
|
urlPrefix?: string;
|
|
@@ -63,7 +63,7 @@ export class Pages {
|
|
|
63
63
|
|
|
64
64
|
constructor(config: PagesConfig) {
|
|
65
65
|
this.root = config.root;
|
|
66
|
-
this.urlPrefix = (config.urlPrefix ?? "/
|
|
66
|
+
this.urlPrefix = (config.urlPrefix ?? "/__assets/pages").replace(/\/$/, "");
|
|
67
67
|
this.extension = config.extension ?? ".js";
|
|
68
68
|
}
|
|
69
69
|
|
package/src/live.ts
CHANGED
|
@@ -130,3 +130,42 @@ export function mountLiveSession(
|
|
|
130
130
|
},
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
// ─── `@c9up/aurora/live` barrel ──────────────────────────────────────
|
|
135
|
+
//
|
|
136
|
+
// The Live stack (broadcast / client / registry / router / server) is
|
|
137
|
+
// re-exported here so the whole niche surface lives behind ONE subpath,
|
|
138
|
+
// `@c9up/aurora/live` — keeping the main `.` barrel lean (it no longer
|
|
139
|
+
// eager-pulls these into every browser graph), consistent with how `./ssr`,
|
|
140
|
+
// `./relay`, `./rpc` and `./hydrate` are already subpath-gated.
|
|
141
|
+
export {
|
|
142
|
+
connectPatches,
|
|
143
|
+
type LiveStore,
|
|
144
|
+
liveStore,
|
|
145
|
+
type RelayBroadcaster,
|
|
146
|
+
} from "./liveBroadcast.js";
|
|
147
|
+
export {
|
|
148
|
+
buildLiveTransport,
|
|
149
|
+
type LiveClientOptions,
|
|
150
|
+
type LiveClientTransport,
|
|
151
|
+
type LiveHttpPoster,
|
|
152
|
+
liveClient,
|
|
153
|
+
type RelaySubscribeClient,
|
|
154
|
+
} from "./liveClient.js";
|
|
155
|
+
export {
|
|
156
|
+
createLiveRegistry,
|
|
157
|
+
type LiveRegistry,
|
|
158
|
+
type LiveSessionHandle,
|
|
159
|
+
} from "./liveRegistry.js";
|
|
160
|
+
export {
|
|
161
|
+
createLiveRouter,
|
|
162
|
+
type LiveMount,
|
|
163
|
+
type LiveRouter,
|
|
164
|
+
} from "./liveRouter.js";
|
|
165
|
+
export {
|
|
166
|
+
DEFAULT_LIVE_EVENT_PATH,
|
|
167
|
+
type LiveHttpContext,
|
|
168
|
+
type LiveHttpRouter,
|
|
169
|
+
type WireLiveEventsOptions,
|
|
170
|
+
wireLiveEvents,
|
|
171
|
+
} from "./liveServer.js";
|
package/src/liveClient.ts
CHANGED
|
@@ -91,7 +91,7 @@ export interface LiveHttpPoster {
|
|
|
91
91
|
/**
|
|
92
92
|
* Build a {@link LiveClientTransport} from a relay client (SSE down) + an HTTP
|
|
93
93
|
* client (events up). `path` must match the server's `wireLiveEvents` route
|
|
94
|
-
* (default `/
|
|
94
|
+
* (default `/__live/event`). Keeps `liveClient` itself transport-agnostic.
|
|
95
95
|
*
|
|
96
96
|
* @example
|
|
97
97
|
* import { relay } from '@c9up/aurora/relay'
|
|
@@ -104,7 +104,7 @@ export function buildLiveTransport(
|
|
|
104
104
|
http: LiveHttpPoster,
|
|
105
105
|
options: { path?: string } = {},
|
|
106
106
|
): LiveClientTransport {
|
|
107
|
-
const path = options.path ?? "/
|
|
107
|
+
const path = options.path ?? "/__live/event";
|
|
108
108
|
return {
|
|
109
109
|
subscribe: (channel, handler) =>
|
|
110
110
|
relayClient.subscribe<SlotPatch[]>(channel, handler),
|
package/src/liveServer.ts
CHANGED
|
@@ -45,7 +45,7 @@ function isLiveEventBody(value: unknown): value is LiveEventBody {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/** Default inbound-event route — keep the client transport's `path` in sync. */
|
|
48
|
-
export const DEFAULT_LIVE_EVENT_PATH = "/
|
|
48
|
+
export const DEFAULT_LIVE_EVENT_PATH = "/__live/event";
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Register the inbound live-event route on the host router. Call once at boot
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `auroraContext` — binds `ctx.aurora.render(name, props)` onto the request
|
|
3
|
+
* context, the AdonisJS ctx-service idiom (the `ctx.view` / `ctx.inertia`
|
|
4
|
+
* analog). Register it globally in your kernel; a thin controller then does:
|
|
5
|
+
*
|
|
6
|
+
* async show({ aurora }: HttpContext) {
|
|
7
|
+
* return aurora.render('Dashboard', { user, stats })
|
|
8
|
+
* }
|
|
9
|
+
*
|
|
10
|
+
* Agnostic: it resolves the AuroraManager from the request container
|
|
11
|
+
* (`ctx.containerResolver.make('aurora')`) — never imports `@c9up/ream` — and is
|
|
12
|
+
* a no-op when no manager is registered. The module-level `aurora.render(ctx, …)`
|
|
13
|
+
* service still works; this is the ctx-bound sugar.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { AuroraManager } from "./AuroraManager.js";
|
|
17
|
+
import type {
|
|
18
|
+
RenderHttpContext,
|
|
19
|
+
RenderPageOptions,
|
|
20
|
+
} from "./server/renderPage.js";
|
|
21
|
+
|
|
22
|
+
/** The `ctx.aurora` surface — ctx-bound render (no explicit ctx argument). */
|
|
23
|
+
export interface AuroraRequestRenderer {
|
|
24
|
+
render(
|
|
25
|
+
name: string,
|
|
26
|
+
props?: unknown,
|
|
27
|
+
options?: RenderPageOptions,
|
|
28
|
+
): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Request context the middleware needs: render target + optional resolver/slot. */
|
|
32
|
+
interface AuroraMiddlewareContext extends RenderHttpContext {
|
|
33
|
+
containerResolver?: { make(token: unknown): unknown };
|
|
34
|
+
aurora?: AuroraRequestRenderer;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Structural check that a resolved value is render-capable (an AuroraManager). */
|
|
38
|
+
function isManager(value: unknown): value is AuroraManager {
|
|
39
|
+
return (
|
|
40
|
+
typeof value === "object" &&
|
|
41
|
+
value !== null &&
|
|
42
|
+
"render" in value &&
|
|
43
|
+
typeof value.render === "function"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveManager(
|
|
48
|
+
resolver: { make(token: unknown): unknown } | undefined,
|
|
49
|
+
): AuroraManager | undefined {
|
|
50
|
+
try {
|
|
51
|
+
const resolved = resolver?.make("aurora");
|
|
52
|
+
return isManager(resolved) ? resolved : undefined;
|
|
53
|
+
} catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Middleware: attach `ctx.aurora` for the request. No-op (passes through) when
|
|
60
|
+
* the AuroraManager isn't registered, so it's safe to mount unconditionally.
|
|
61
|
+
*/
|
|
62
|
+
export function auroraContext(
|
|
63
|
+
ctx: AuroraMiddlewareContext,
|
|
64
|
+
next: () => Promise<void>,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const manager = resolveManager(ctx.containerResolver);
|
|
67
|
+
if (manager) {
|
|
68
|
+
ctx.aurora = {
|
|
69
|
+
render: (name, props, options) =>
|
|
70
|
+
manager.render(ctx, name, props, options),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return next();
|
|
74
|
+
}
|
package/src/rpc.ts
CHANGED
|
@@ -34,17 +34,56 @@ export interface RpcClientOptions {
|
|
|
34
34
|
http?: HttpClient;
|
|
35
35
|
/** Default headers — only used when no `http` client is supplied. */
|
|
36
36
|
headers?: Record<string, string>;
|
|
37
|
+
/**
|
|
38
|
+
* Auto CSRF: read the `XSRF-TOKEN` cookie and echo it as `X-XSRF-TOKEN` on
|
|
39
|
+
* every call (Axios/Angular convention), so RPC POSTs pass blackhole's
|
|
40
|
+
* signed double-submit check when the route is cookie/session-authed. No-op
|
|
41
|
+
* outside the browser and when the cookie is absent. Default `true`.
|
|
42
|
+
* `/rpc` under a bearer (JWT) guard is CSRF-exempt, so the missing-cookie
|
|
43
|
+
* no-op is exactly right there too.
|
|
44
|
+
*/
|
|
45
|
+
xsrf?: boolean;
|
|
46
|
+
/** Cookie to read the CSRF token from. Default `XSRF-TOKEN`. */
|
|
47
|
+
xsrfCookieName?: string;
|
|
48
|
+
/** Header to echo the CSRF token in. Default `X-XSRF-TOKEN`. */
|
|
49
|
+
xsrfHeaderName?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read a cookie's raw value from `document.cookie`. Returns `undefined`
|
|
54
|
+
* server-side (no `document`) or when the cookie is absent. The value is sent
|
|
55
|
+
* verbatim — double-submit compares it byte-for-byte against the cookie, so it
|
|
56
|
+
* must not be decoded.
|
|
57
|
+
*/
|
|
58
|
+
function readCookie(name: string): string | undefined {
|
|
59
|
+
if (typeof document === "undefined") return undefined;
|
|
60
|
+
const prefix = `${name}=`;
|
|
61
|
+
for (const part of document.cookie.split(";")) {
|
|
62
|
+
const trimmed = part.trimStart();
|
|
63
|
+
if (trimmed.startsWith(prefix)) return trimmed.slice(prefix.length);
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
37
66
|
}
|
|
38
67
|
|
|
39
68
|
/**
|
|
40
69
|
* Create a JSON-RPC client bound to aurora's HttpClient transport. Inherits the
|
|
41
|
-
* supplied (or a fresh) HttpClient's base URL, auth headers, and timeouts
|
|
70
|
+
* supplied (or a fresh) HttpClient's base URL, auth headers, and timeouts, and
|
|
71
|
+
* (by default) auto-attaches the `X-XSRF-TOKEN` CSRF header from the cookie.
|
|
42
72
|
*/
|
|
43
73
|
export function createRpcClient(options: RpcClientOptions = {}): RpcClient {
|
|
44
74
|
const http = options.http ?? new HttpClient({ headers: options.headers });
|
|
75
|
+
const xsrfEnabled = options.xsrf ?? true;
|
|
76
|
+
const cookieName = options.xsrfCookieName ?? "XSRF-TOKEN";
|
|
77
|
+
const headerName = options.xsrfHeaderName ?? "X-XSRF-TOKEN";
|
|
45
78
|
return createCometRpcClient({
|
|
46
79
|
url: options.url,
|
|
47
|
-
transport: (url, body, { signal }) =>
|
|
48
|
-
|
|
80
|
+
transport: (url, body, { signal }) => {
|
|
81
|
+
let headers: Record<string, string> | undefined;
|
|
82
|
+
if (xsrfEnabled) {
|
|
83
|
+
const token = readCookie(cookieName);
|
|
84
|
+
if (token !== undefined) headers = { [headerName]: token };
|
|
85
|
+
}
|
|
86
|
+
return http.post<unknown>(url, body, { signal, headers });
|
|
87
|
+
},
|
|
49
88
|
});
|
|
50
89
|
}
|
package/src/server/renderPage.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* <head>
|
|
9
9
|
* ...
|
|
10
10
|
* <script type="importmap">
|
|
11
|
-
* { "imports": { "@c9up/aurora": "/
|
|
11
|
+
* { "imports": { "@c9up/aurora": "/__assets/aurora/index.js" } }
|
|
12
12
|
* </script>
|
|
13
13
|
* </head>
|
|
14
14
|
* <body>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* <script id="aurora-page-data" type="application/json">{…}</script>
|
|
17
17
|
* <script type="module">
|
|
18
18
|
* import { hydrate } from '@c9up/aurora'
|
|
19
|
-
* import Page from '/
|
|
19
|
+
* import Page from '/__assets/pages/ProjectPage.js'
|
|
20
20
|
* const data = JSON.parse(document.getElementById('aurora-page-data').textContent)
|
|
21
21
|
* hydrate(document.getElementById('aurora-root'), () => Page(data.props))
|
|
22
22
|
* </script>
|
|
@@ -45,7 +45,7 @@ export interface RenderHttpContext {
|
|
|
45
45
|
export interface RenderPageOptions {
|
|
46
46
|
/**
|
|
47
47
|
* Importmap entries injected into `<head>`. Defaults to mapping
|
|
48
|
-
* `@c9up/aurora` to `/
|
|
48
|
+
* `@c9up/aurora` to `/__assets/aurora/index.js`. Override to point
|
|
49
49
|
* at a different mount or to add app-side aliases.
|
|
50
50
|
*/
|
|
51
51
|
importmap?: Record<string, string>;
|
|
@@ -139,7 +139,7 @@ export async function renderPage<P>(
|
|
|
139
139
|
const body = renderToString(tree);
|
|
140
140
|
|
|
141
141
|
const importmap = {
|
|
142
|
-
"@c9up/aurora": "/
|
|
142
|
+
"@c9up/aurora": "/__assets/aurora/index.js",
|
|
143
143
|
...options.importmap,
|
|
144
144
|
};
|
|
145
145
|
const rootId = options.rootId ?? "aurora-root";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* `serveAssets` — generic static-file handler exposed by aurora so an
|
|
3
3
|
* app can mount the runtime + the pages dist with a couple of routes:
|
|
4
4
|
*
|
|
5
|
-
* router.get('/
|
|
6
|
-
* router.get('/
|
|
5
|
+
* router.get('/__assets/aurora/*', serveAssets({ root: auroraDistPath }))
|
|
6
|
+
* router.get('/__assets/pages/*', serveAssets({ root: pagesPath }))
|
|
7
7
|
*
|
|
8
8
|
* The handler is framework-agnostic: it reads `ctx.request.param('*')`
|
|
9
9
|
* and writes to `ctx.response`. Any context that satisfies
|
package/src/server.ts
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
// `@c9up/aurora/server`; the client `.` entry stays node-free.
|
|
7
7
|
|
|
8
8
|
export { AuroraManager, type AuroraManagerConfig } from "./AuroraManager.js";
|
|
9
|
+
export {
|
|
10
|
+
type AuroraRequestRenderer,
|
|
11
|
+
auroraContext,
|
|
12
|
+
} from "./middleware.js";
|
|
9
13
|
export { type PageFactory, Pages, type PagesConfig } from "./Pages.js";
|
|
10
14
|
export {
|
|
11
15
|
type RenderHttpContext,
|