luchy 0.2.0 → 1.1.0
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 +253 -98
- package/dist/api/index.d.ts +44 -11
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +36 -8
- package/dist/api/schema.d.ts +862 -94
- package/dist/identity.d.ts +35 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/index.d.ts +23 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -8
- package/dist/react-router.d.ts +121 -53
- package/dist/react-router.d.ts.map +1 -1
- package/dist/react-router.js +185 -48
- package/dist/script/luchy.js +62 -9
- package/dist/script/luchy.js.br +0 -0
- package/dist/script/luchy.js.gz +0 -0
- package/dist/script/luchy.min.js +62 -9
- package/dist/script/luchy.min.js.br +0 -0
- package/dist/script/luchy.min.js.gz +0 -0
- package/dist/server.d.ts +71 -39
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +114 -26
- package/package.json +17 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed identity tokens.
|
|
3
|
+
*
|
|
4
|
+
* Ingest only trusts who a hit belongs to when the app vouches for it: the
|
|
5
|
+
* server signs `{ user, actor?, tenant? }` with the project's secret key,
|
|
6
|
+
* the token travels with the hit (browser or server), and ingest verifies it.
|
|
7
|
+
* An unsigned or tampered token is dropped and the hit is stored anonymous, so
|
|
8
|
+
* a visitor cannot attribute traffic to someone else by editing the page.
|
|
9
|
+
*
|
|
10
|
+
* WebCrypto only — runs in Workers, Node >= 18, Bun and Deno.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Who a hit belongs to. `user` is the account being viewed as, `actor` the
|
|
16
|
+
* person actually acting when they differ (impersonation), `tenant` the app's
|
|
17
|
+
* own workspace/client id.
|
|
18
|
+
*/
|
|
19
|
+
export type LuchyIdentity = {
|
|
20
|
+
user: string;
|
|
21
|
+
actor?: string;
|
|
22
|
+
tenant?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Signs an identity for ingest.
|
|
26
|
+
*
|
|
27
|
+
* Format: `base64url(JSON({ u, a?, t? })) + "." + base64url(HMAC-SHA256(secret, firstPart))`.
|
|
28
|
+
* The short keys keep the token small: it is rendered into every page.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* const token = await signIdentity({ user: 'u_1', tenant: '5' }, env.LUCHY_SECRET);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function signIdentity(identity: LuchyIdentity, secret: string): Promise<string>;
|
|
35
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAgB9E;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAgBjB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
+
export type { LuchyIdentity } from './identity.js';
|
|
2
|
+
/** A property bag. Nested objects are not supported by the ingest API. */
|
|
3
|
+
export type LuchyProps = Record<string, string | number | boolean>;
|
|
1
4
|
export type LuchyInitOptions = {
|
|
2
5
|
endpoint?: string;
|
|
3
6
|
apiKey: string;
|
|
4
7
|
trackLocalhost?: boolean;
|
|
8
|
+
/** A signed identity token (see `signIdentity` in `luchy/server`). */
|
|
9
|
+
identity?: string;
|
|
10
|
+
/** Merged into the payload of every event and pageview. */
|
|
11
|
+
props?: LuchyProps;
|
|
12
|
+
/**
|
|
13
|
+
* The script tag the tracker was loaded from. When set, `data-identity` and
|
|
14
|
+
* `data-props` are read from it on every send (so a server re-render after
|
|
15
|
+
* login is picked up), and `identify`/`reset` write to it.
|
|
16
|
+
*/
|
|
17
|
+
script?: Element;
|
|
5
18
|
};
|
|
6
19
|
export type LuchyTrackerOptions = {
|
|
7
20
|
autoPageviews?: boolean;
|
|
@@ -13,10 +26,19 @@ export type LuchyTracker = {
|
|
|
13
26
|
trackPageview(data?: {
|
|
14
27
|
pathname?: string;
|
|
15
28
|
referrer?: string;
|
|
29
|
+
payload?: LuchyProps;
|
|
16
30
|
}): Promise<void>;
|
|
17
|
-
trackEvent(name: string, payload?:
|
|
31
|
+
trackEvent(name: string, payload?: LuchyProps, options?: {
|
|
18
32
|
type?: string;
|
|
19
33
|
}): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Attributes everything sent from now on to a signed identity token, for
|
|
36
|
+
* SPAs that learn who the user is after load. `props` replace the current
|
|
37
|
+
* ones.
|
|
38
|
+
*/
|
|
39
|
+
identify(token: string, props?: LuchyProps): void;
|
|
40
|
+
/** Back to anonymous, e.g. on logout. Drops props too. */
|
|
41
|
+
reset(): void;
|
|
20
42
|
setOptions(options: LuchyTrackerOptions): void;
|
|
21
43
|
};
|
|
22
44
|
/**
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,aAAa,CAAC,IAAI,CAAC,EAAE;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,UAAU,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAClD,0DAA0D;IAC1D,KAAK,IAAI,IAAI,CAAC;IACd,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAChD,CAAC;AAgBF;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CA2Z7D"}
|
package/dist/index.js
CHANGED
|
@@ -21,9 +21,58 @@ function collectBrowserData() {
|
|
|
21
21
|
language: navigator.language || navigator.languages?.[0] || "en"
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
+
function parseProps(raw) {
|
|
25
|
+
if (!raw) return void 0;
|
|
26
|
+
try {
|
|
27
|
+
const value = JSON.parse(raw);
|
|
28
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
29
|
+
return void 0;
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
} catch {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
24
36
|
function luchy(options) {
|
|
25
37
|
const endpoint = options.endpoint || "https://api.luchy.app/ingest";
|
|
26
|
-
const { apiKey, trackLocalhost = false } = options;
|
|
38
|
+
const { apiKey, trackLocalhost = false, script } = options;
|
|
39
|
+
let identity = options.identity;
|
|
40
|
+
let props = options.props;
|
|
41
|
+
function currentIdentity() {
|
|
42
|
+
if (!script) return identity;
|
|
43
|
+
return script.getAttribute("data-identity") || void 0;
|
|
44
|
+
}
|
|
45
|
+
function currentProps() {
|
|
46
|
+
if (!script) return props;
|
|
47
|
+
return parseProps(script.getAttribute("data-props"));
|
|
48
|
+
}
|
|
49
|
+
function identify(token, nextProps) {
|
|
50
|
+
if (!script) {
|
|
51
|
+
identity = token;
|
|
52
|
+
props = nextProps;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
script.setAttribute("data-identity", token);
|
|
56
|
+
if (nextProps) {
|
|
57
|
+
script.setAttribute("data-props", JSON.stringify(nextProps));
|
|
58
|
+
} else {
|
|
59
|
+
script.removeAttribute("data-props");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function reset() {
|
|
63
|
+
if (!script) {
|
|
64
|
+
identity = void 0;
|
|
65
|
+
props = void 0;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
script.removeAttribute("data-identity");
|
|
69
|
+
script.removeAttribute("data-props");
|
|
70
|
+
}
|
|
71
|
+
function withProps(payload) {
|
|
72
|
+
const global = currentProps();
|
|
73
|
+
if (!global) return payload;
|
|
74
|
+
return { ...global, ...payload };
|
|
75
|
+
}
|
|
27
76
|
const shouldTrack = () => {
|
|
28
77
|
if (trackLocalhost) return true;
|
|
29
78
|
return location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
|
|
@@ -39,6 +88,8 @@ function luchy(options) {
|
|
|
39
88
|
data: {
|
|
40
89
|
pathname,
|
|
41
90
|
referrer,
|
|
91
|
+
payload: withProps(data?.payload),
|
|
92
|
+
identity: currentIdentity(),
|
|
42
93
|
...browserData
|
|
43
94
|
}
|
|
44
95
|
});
|
|
@@ -57,7 +108,8 @@ function luchy(options) {
|
|
|
57
108
|
// `payload`, NOT `props`. The ingest endpoint validates with a
|
|
58
109
|
// non-passthrough zod object, so an unknown key is stripped and the
|
|
59
110
|
// event is stored with no properties at all — 200, success, no data.
|
|
60
|
-
payload,
|
|
111
|
+
payload: withProps(payload),
|
|
112
|
+
identity: currentIdentity(),
|
|
61
113
|
...browserData
|
|
62
114
|
}
|
|
63
115
|
});
|
|
@@ -219,11 +271,9 @@ function luchy(options) {
|
|
|
219
271
|
const key = attr.name.slice(prefix.length).replace(/-/g, "_");
|
|
220
272
|
payload[key] = attr.value;
|
|
221
273
|
}
|
|
222
|
-
trackEvent(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
{ type: "click" }
|
|
226
|
-
);
|
|
274
|
+
trackEvent(name, Object.keys(payload).length > 0 ? payload : void 0, {
|
|
275
|
+
type: "click"
|
|
276
|
+
});
|
|
227
277
|
};
|
|
228
278
|
document.addEventListener("click", dataEventClickListener);
|
|
229
279
|
}
|
|
@@ -268,6 +318,8 @@ function luchy(options) {
|
|
|
268
318
|
return {
|
|
269
319
|
trackPageview,
|
|
270
320
|
trackEvent,
|
|
321
|
+
identify,
|
|
322
|
+
reset,
|
|
271
323
|
setOptions
|
|
272
324
|
};
|
|
273
325
|
}
|
|
@@ -280,7 +332,8 @@ if (typeof document !== "undefined" && document.currentScript) {
|
|
|
280
332
|
const tracker = luchy({
|
|
281
333
|
apiKey,
|
|
282
334
|
endpoint,
|
|
283
|
-
trackLocalhost
|
|
335
|
+
trackLocalhost,
|
|
336
|
+
script
|
|
284
337
|
});
|
|
285
338
|
const autoPageviewsAttr = script.getAttribute("data-auto-pageviews");
|
|
286
339
|
const autoOutboundAttr = script.getAttribute("data-auto-outbound");
|
package/dist/react-router.d.ts
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { MiddlewareFunction, RouterContextProvider } from 'react-router';
|
|
3
|
+
import { LuchyIdentity } from './identity.js';
|
|
4
|
+
import { LuchyPayload, LuchyTraits } from './server.js';
|
|
1
5
|
/**
|
|
2
|
-
* React Router (v7+)
|
|
3
|
-
*
|
|
6
|
+
* React Router (v7.9+) — server events derived from requests, plus the
|
|
7
|
+
* identity the browser script needs.
|
|
4
8
|
*
|
|
5
9
|
* A React Router app already says what it did, in the request: every console
|
|
6
10
|
* mutation is a form POST whose `intent` field names it (`rotate`,
|
|
7
11
|
* `invite-member`, `create-api-key`, …), every JSON API mutation is
|
|
8
12
|
* discriminated by its method, and every auth verb has a path that names the
|
|
9
|
-
* operation. So instead of sprinkling `
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* normalized route + intent (or method).
|
|
13
|
-
* new intent is tracked the day it is written.
|
|
13
|
+
* operation. So instead of sprinkling `track` calls across route modules —
|
|
14
|
+
* where a new intent is only tracked once somebody remembers to instrument it
|
|
15
|
+
* — a root middleware derives the event from the request it is already
|
|
16
|
+
* holding: normalized route + intent (or method).
|
|
14
17
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
18
|
+
* The same middleware resolves who the request belongs to, once, and hands a
|
|
19
|
+
* signed token to the root loader (`getLuchy`) so `<LuchyScript>` can put it
|
|
20
|
+
* on the browser script. Server events and browser pageviews then land in the
|
|
21
|
+
* same session.
|
|
22
|
+
*
|
|
23
|
+
* Only requests React Router routes are seen — anything the Worker answers
|
|
24
|
+
* before handing over to React Router is invisible here.
|
|
25
|
+
*
|
|
26
|
+
* Not Cloudflare-only: shipping goes through the injected `waitUntil`, so this
|
|
27
|
+
* module imports no runtime.
|
|
17
28
|
*
|
|
18
29
|
* @module
|
|
19
30
|
*/
|
|
31
|
+
export type { LuchyIdentity, LuchyTraits };
|
|
20
32
|
/** A property bag. Nested objects are not supported by the ingest API. */
|
|
21
|
-
export type LuchyRequestPayload =
|
|
33
|
+
export type LuchyRequestPayload = LuchyPayload;
|
|
22
34
|
/**
|
|
23
35
|
* True for the methods worth an event.
|
|
24
36
|
*
|
|
@@ -62,8 +74,8 @@ export type ServerEventInput = {
|
|
|
62
74
|
/** The submitted `intent`, if the body carried one. */
|
|
63
75
|
intent?: string | null;
|
|
64
76
|
};
|
|
65
|
-
/** The naming and filtering knobs, shared by the
|
|
66
|
-
export type ServerEventNameConfig = Pick<
|
|
77
|
+
/** The naming and filtering knobs, shared by the middleware and the pure helper. */
|
|
78
|
+
export type ServerEventNameConfig = Pick<LuchyMiddlewareOptions, 'ignorePrefixes' | 'ignoreRouteSuffixes' | 'ignoreEvents' | 'trackFailures' | 'methodSuffix'>;
|
|
67
79
|
/**
|
|
68
80
|
* The event name for a mutation, or `null` when the request is not worth an
|
|
69
81
|
* event (a read, machine traffic, an ignored route, a failure the app does not
|
|
@@ -80,17 +92,55 @@ export type ServerEventNameConfig = Pick<RequestTrackerOptions, 'ignorePrefixes'
|
|
|
80
92
|
* ```
|
|
81
93
|
*/
|
|
82
94
|
export declare function serverEventName(input: ServerEventInput, config?: ServerEventNameConfig): string | null;
|
|
83
|
-
|
|
95
|
+
/** What the root middleware gets from React Router. */
|
|
96
|
+
export type LuchyMiddlewareArgs = Parameters<MiddlewareFunction>[0];
|
|
97
|
+
/**
|
|
98
|
+
* Generic over what `next()` resolves to, so it fits both framework mode's
|
|
99
|
+
* `Route.MiddlewareFunction` (a Response) and data-mode route objects.
|
|
100
|
+
*/
|
|
101
|
+
export type LuchyMiddleware = <Result>(args: LuchyMiddlewareArgs, next: () => Promise<Result>) => Promise<Result>;
|
|
102
|
+
/**
|
|
103
|
+
* An identity plus the props to stamp on every event of the request, and the
|
|
104
|
+
* person's profile traits (sent via identify, deduped per isolate).
|
|
105
|
+
*/
|
|
106
|
+
export type LuchyRequestIdentity = LuchyIdentity & {
|
|
107
|
+
props?: LuchyRequestPayload;
|
|
108
|
+
traits?: LuchyTraits;
|
|
109
|
+
};
|
|
110
|
+
/** Clears the identify dedupe. For tests. */
|
|
111
|
+
export declare function resetIdentifyCache(): void;
|
|
112
|
+
export type LuchyMiddlewareOptions = {
|
|
84
113
|
/** A Luchy API key. The public ingest key is fine — it ships in the HTML. */
|
|
85
114
|
apiKey: string;
|
|
86
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* The project's secret key (`lsk_…`). Without it no identity is sent —
|
|
117
|
+
* ingest would drop an unsigned one anyway — and traits are not identified.
|
|
118
|
+
*/
|
|
119
|
+
secret?: string;
|
|
120
|
+
/** The ingest base, without a trailing slash. Defaults to the hosted API. */
|
|
87
121
|
endpoint?: string;
|
|
88
122
|
/**
|
|
89
|
-
* Defaults to `true`. When false
|
|
90
|
-
* request read, no network
|
|
91
|
-
*
|
|
123
|
+
* Defaults to `true`. When false no server event is produced — no clone, no
|
|
124
|
+
* request read, no network. `getLuchy` keeps working so the browser script
|
|
125
|
+
* still renders.
|
|
92
126
|
*/
|
|
93
127
|
enabled?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* How to keep the event alive after the response is sent. Called only for
|
|
130
|
+
* requests that produce an event. On Cloudflare:
|
|
131
|
+
* `({ context }) => (p) => context.get(cfContext).ctx.waitUntil(p)`.
|
|
132
|
+
*/
|
|
133
|
+
waitUntil: (args: LuchyMiddlewareArgs) => (promise: Promise<unknown>) => void;
|
|
134
|
+
/**
|
|
135
|
+
* Who the request belongs to. Resolved lazily — only when an event ships or
|
|
136
|
+
* `getLuchy` asks — and at most once per request. A rejection is reported
|
|
137
|
+
* to `onError` and the request is treated as anonymous.
|
|
138
|
+
*
|
|
139
|
+
* Return `traits` (`{ name, email }`, …) to keep the person's profile up to
|
|
140
|
+
* date: they are sent via identify through `waitUntil`, at most once per
|
|
141
|
+
* isolate per hour for the same user and traits. Requires `secret`.
|
|
142
|
+
*/
|
|
143
|
+
identity?: (args: LuchyMiddlewareArgs) => LuchyRequestIdentity | undefined | Promise<LuchyRequestIdentity | undefined>;
|
|
94
144
|
/**
|
|
95
145
|
* Raw-pathname prefixes to drop, matched before normalization. Merged with
|
|
96
146
|
* the built-in defaults (`/__manifest`) rather than replacing them.
|
|
@@ -115,50 +165,68 @@ export type RequestTrackerOptions = {
|
|
|
115
165
|
* same path should report as `route:patch` and `route:delete`.
|
|
116
166
|
*/
|
|
117
167
|
methodSuffix?: boolean;
|
|
118
|
-
/**
|
|
119
|
-
* App enrichment — session ids, actor labels, plan names. It runs inside
|
|
120
|
-
* `waitUntil` (never on the request's critical path), its result is merged
|
|
121
|
-
* over the tracker's own `status` field, and a rejection is swallowed: a
|
|
122
|
-
* failed lookup costs the payload, not the event.
|
|
123
|
-
*/
|
|
124
|
-
payload?: (request: Request, response: Response) => Promise<LuchyRequestPayload> | LuchyRequestPayload;
|
|
125
168
|
/** Called when anything in the pipeline fails. Nothing else surfaces it. */
|
|
126
169
|
onError?: (error: unknown) => void;
|
|
170
|
+
/** Transport override, for tests and scoped runtimes (service bindings). */
|
|
171
|
+
fetch?: typeof globalThis.fetch;
|
|
127
172
|
};
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
*/
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
export type FinishFn = (response: Response, ctx: FlushContext) => void;
|
|
137
|
-
export type RequestTracker = {
|
|
138
|
-
/**
|
|
139
|
-
* Called with the incoming request, before the React Router handler consumes
|
|
140
|
-
* its body. Returns the function to call with the response.
|
|
141
|
-
*/
|
|
142
|
-
begin(request: Request): FinishFn;
|
|
173
|
+
/** What the browser script needs: spread it into `<LuchyScript>`. */
|
|
174
|
+
export type LuchyClientConfig = {
|
|
175
|
+
apiKey: string;
|
|
176
|
+
/** The ingest base, as the script's `data-endpoint` takes it. */
|
|
177
|
+
endpoint: string;
|
|
178
|
+
/** The signed identity token, when the request has an identity. */
|
|
179
|
+
identity?: string;
|
|
180
|
+
props?: LuchyRequestPayload;
|
|
143
181
|
};
|
|
144
182
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
183
|
+
* The root middleware. Every mutation React Router serves becomes an event;
|
|
184
|
+
* every request gets the state `getLuchy` reads.
|
|
147
185
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
186
|
+
* ```ts
|
|
187
|
+
* // app/root.tsx
|
|
188
|
+
* export const middleware: Route.MiddlewareFunction[] = [
|
|
189
|
+
* luchyMiddleware({
|
|
190
|
+
* apiKey: env.LUCHY_API_KEY,
|
|
191
|
+
* secret: env.LUCHY_SECRET,
|
|
192
|
+
* waitUntil: ({ context }) => (p) => context.get(cfContext).ctx.waitUntil(p),
|
|
193
|
+
* identity: async ({ context }) => {
|
|
194
|
+
* const s = await context.get(appContext).getMaybeSessionContext();
|
|
195
|
+
* if (!s) {
|
|
196
|
+
* return undefined;
|
|
197
|
+
* }
|
|
198
|
+
*
|
|
199
|
+
* return { user: s.user.id, tenant: String(s.workspaceId) };
|
|
200
|
+
* }
|
|
201
|
+
* })
|
|
202
|
+
* ];
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export declare function luchyMiddleware(options: LuchyMiddlewareOptions): LuchyMiddleware;
|
|
206
|
+
/**
|
|
207
|
+
* The browser script's config for this request, for the root loader. Resolves
|
|
208
|
+
* the request's identity (once — the middleware shares it) and signs it.
|
|
152
209
|
*
|
|
153
|
-
* @example
|
|
154
210
|
* ```ts
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* const response = await requestHandler(request, loadContext)
|
|
159
|
-
* finish(response, ctx)
|
|
160
|
-
* return response
|
|
211
|
+
* export async function loader({ context }: Route.LoaderArgs) {
|
|
212
|
+
* return { luchy: await getLuchy(context) };
|
|
213
|
+
* }
|
|
161
214
|
* ```
|
|
162
215
|
*/
|
|
163
|
-
export declare function
|
|
216
|
+
export declare function getLuchy(context: Readonly<RouterContextProvider>): Promise<LuchyClientConfig>;
|
|
217
|
+
export type LuchyScriptProps = LuchyClientConfig & {
|
|
218
|
+
/** Defaults to the CDN build. */
|
|
219
|
+
src?: string;
|
|
220
|
+
/** For a CSP that allows scripts by nonce. */
|
|
221
|
+
nonce?: string;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* The browser script tag. Render it in the root layout with what `getLuchy`
|
|
225
|
+
* returned. The script re-reads `data-identity` / `data-props` on every send,
|
|
226
|
+
* so a re-render after login or logout is picked up without a reload.
|
|
227
|
+
*
|
|
228
|
+
* It must be server-rendered: React does not execute `<script>` elements it
|
|
229
|
+
* creates on the client.
|
|
230
|
+
*/
|
|
231
|
+
export declare function LuchyScript(props: LuchyScriptProps): ReactElement;
|
|
164
232
|
//# sourceMappingURL=react-router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"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"}
|