@mrrocket/sdk 0.2.0 → 0.3.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/CHANGELOG.md +7 -0
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +3 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +23 -3
- package/dist/client.js +44 -8
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +67 -0
- package/dist/core.js +59 -0
- package/dist/core.js.map +1 -1
- package/dist/errors.d.ts +35 -0
- package/dist/errors.js +83 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +13 -1
- package/dist/server.js +8 -1
- package/dist/server.js.map +1 -1
- package/dist/styles.d.ts +1 -1
- package/dist/styles.js +2 -2
- package/dist/styles.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package. It follows semantic versioning; before 1.0 a minor version may change an API.
|
|
4
4
|
|
|
5
|
+
## 0.3.0
|
|
6
|
+
|
|
7
|
+
- **Error tracking** for MRRocket › Build › Errors, on by default (`errors` prop): uncaught errors and unhandled promise rejections, with the page URL and the last 20 pages and clicks, go to `POST /api/sdk/errors` in batches every 2 s (`sendBeacon` on page hide), under the normal analytics consent and never from a preview. The same error once per page load, at most 20 per page load. `useCockpit().captureError(error)` reports one you caught; the `release` prop names your release. Independent of session recordings.
|
|
8
|
+
- **Feature flags** from MRRocket › Build › Feature flags: `useFlag(key)` and `useFlagVariant(key)` in the browser, `getFlag` and `getFlagVariant` on the server (`/next/server`). The config carries the Production flags; each is evaluated in the SDK from the person's `identify()` properties ("Email", "Plan" …, `@domain` matches an address, cohorts never match in the browser) and a stable bucket per flag and person. New exports `evaluateFlag`, `SDK_VERSION`, types `FlagConfig`, `FlagCondition`, `FlagConditionSet`, `FlagPerson`, `ErrorReport`.
|
|
9
|
+
- Event batches carry `sdk: { v, errors }`, so MRRocket can say which SDK a site runs and whether it reports errors.
|
|
10
|
+
- Fix: unselected stars of a 1–5 survey question are gray again (a button reset made them take the text color).
|
|
11
|
+
|
|
5
12
|
## 0.2.0
|
|
6
13
|
|
|
7
14
|
- **Session recordings** (rrweb 2.1.6 via `@rrweb/record`, pinned): recorded only with the visitor's own replay consent, separate from analytics and off by default: new `replay` prop and `setConsent({ replay })` (`setConsent` now also takes `{ analytics?, replay? }`; `useCockpit()` returns `replay`). The recorder is a dynamic import, loaded only when a visit records (about 26 KB gzip); pages without recording load about 1.9 KB more than 0.1.0.
|
package/dist/browser.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* that the build keeps one client chunk: server components import these as client references, while the plain
|
|
4
4
|
* helpers (`core`, `server`, `proxy`) stay free of the directive.
|
|
5
5
|
*/
|
|
6
|
-
export { CockpitProvider, Experiment, Text, useCockpit, useVariant, type CockpitProviderProps } from "./client.js";
|
|
6
|
+
export { CockpitProvider, Experiment, Text, useCockpit, useFlag, useFlagVariant, useVariant, type CockpitProviderProps } from "./client.js";
|
|
7
7
|
export { FeedbackBoard, FeedbackWidget, useFeedback, type FeedbackBoardProps, type FeedbackWidgetProps } from "./feedback.js";
|
|
8
8
|
export { Survey } from "./survey.js";
|
|
9
9
|
export type { CockpitUser } from "./context.js";
|
package/dist/browser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/browser.ts
|
|
4
|
-
import { CockpitProvider, Experiment, Text, useCockpit, useVariant } from "./client.js";
|
|
4
|
+
import { CockpitProvider, Experiment, Text, useCockpit, useFlag, useFlagVariant, useVariant } from "./client.js";
|
|
5
5
|
import { FeedbackBoard, FeedbackWidget, useFeedback } from "./feedback.js";
|
|
6
6
|
import { Survey } from "./survey.js";
|
|
7
7
|
import {
|
|
@@ -19,6 +19,8 @@ export {
|
|
|
19
19
|
createApi,
|
|
20
20
|
useCockpit,
|
|
21
21
|
useFeedback,
|
|
22
|
+
useFlag,
|
|
23
|
+
useFlagVariant,
|
|
22
24
|
useStyles,
|
|
23
25
|
useVariant
|
|
24
26
|
};
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Everything of the SDK that needs the browser or React state. It is its own file with the \"use client\" directive so\n * that the build keeps one client chunk: server components import these as client references, while the plain\n * helpers (`core`, `server`, `proxy`) stay free of the directive.\n */\nexport { CockpitProvider, Experiment, Text, useCockpit, useVariant, type CockpitProviderProps } from \"./client\";\nexport { FeedbackBoard, FeedbackWidget, useFeedback, type FeedbackBoardProps, type FeedbackWidgetProps } from \"./feedback\";\nexport { Survey } from \"./survey\";\nexport type { CockpitUser } from \"./context\";\nexport {\n type ChangelogEntry,\n type FeedbackBoardInfo,\n type FeedbackComment,\n type FeedbackList,\n type FeedbackPost,\n type FeedbackRoadmap,\n type PostStatus,\n type SurveyAnswer,\n type SurveyDefinition,\n type SurveyQuestion,\n type SurveyQuestionKind,\n createApi,\n} from \"./api\";\nexport { CSS as WIDGET_CSS, useStyles } from \"./styles\";\n"],"mappings":";;;AAOA,SAAS,iBAAiB,YAAY,MAAM,YAAY,kBAA6C;
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Everything of the SDK that needs the browser or React state. It is its own file with the \"use client\" directive so\n * that the build keeps one client chunk: server components import these as client references, while the plain\n * helpers (`core`, `server`, `proxy`) stay free of the directive.\n */\nexport { CockpitProvider, Experiment, Text, useCockpit, useFlag, useFlagVariant, useVariant, type CockpitProviderProps } from \"./client\";\nexport { FeedbackBoard, FeedbackWidget, useFeedback, type FeedbackBoardProps, type FeedbackWidgetProps } from \"./feedback\";\nexport { Survey } from \"./survey\";\nexport type { CockpitUser } from \"./context\";\nexport {\n type ChangelogEntry,\n type FeedbackBoardInfo,\n type FeedbackComment,\n type FeedbackList,\n type FeedbackPost,\n type FeedbackRoadmap,\n type PostStatus,\n type SurveyAnswer,\n type SurveyDefinition,\n type SurveyQuestion,\n type SurveyQuestionKind,\n createApi,\n} from \"./api\";\nexport { CSS as WIDGET_CSS, useStyles } from \"./styles\";\n"],"mappings":";;;AAOA,SAAS,iBAAiB,YAAY,MAAM,YAAY,SAAS,gBAAgB,kBAA6C;AAC9H,SAAS,eAAe,gBAAgB,mBAAsE;AAC9G,SAAS,cAAc;AAEvB;AAAA,EAYI;AAAA,OACG;AACP,SAAgB,KAAY,iBAAiB;","names":[]}
|
package/dist/client.d.ts
CHANGED
|
@@ -32,6 +32,14 @@ export interface CockpitProviderProps {
|
|
|
32
32
|
heatmaps?: boolean;
|
|
33
33
|
/** Ask visitors the surveys you run in Cockpit. Set false to switch them off. */
|
|
34
34
|
surveys?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Error tracking for Build › Errors (default true): uncaught errors and unhandled promise rejections, with the page
|
|
37
|
+
* and the last pages and clicks, under the normal analytics consent. Report errors you catch with
|
|
38
|
+
* `useCockpit().captureError(error)`.
|
|
39
|
+
*/
|
|
40
|
+
errors?: boolean;
|
|
41
|
+
/** Your release (a version or a commit), so MRRocket can tell which release an error came with. */
|
|
42
|
+
release?: string;
|
|
35
43
|
/**
|
|
36
44
|
* Affiliate referrals (`?ref=` links and MRRocket's tracked links). "consent" (default): stored in the first-party
|
|
37
45
|
* `mrr_ref` cookie only while consent is "cookies", in memory otherwise. "always": stored whatever the consent (only
|
|
@@ -49,14 +57,16 @@ export interface CockpitProviderProps {
|
|
|
49
57
|
* `preview` forces variants for this render, for example from the `ck_preview` search param on the server,
|
|
50
58
|
* so a Cockpit preview never flickers. Without it the provider reads the param itself after the first render.
|
|
51
59
|
*/
|
|
52
|
-
export declare const CockpitProvider: ({ apiKey: apiKeyProp, host: hostProp, preview: forced, embedded: embeddedProp, config: bootstrapConfig, distinctId: bootstrapId, consent: consentProp, respectDnt, replay: replayProp, heatmaps, surveys, attribution, referralParam, children, }: CockpitProviderProps) => import("react").JSX.Element;
|
|
60
|
+
export declare const CockpitProvider: ({ apiKey: apiKeyProp, host: hostProp, preview: forced, embedded: embeddedProp, config: bootstrapConfig, distinctId: bootstrapId, consent: consentProp, respectDnt, replay: replayProp, heatmaps, surveys, errors, release, attribution, referralParam, children, }: CockpitProviderProps) => import("react").JSX.Element;
|
|
53
61
|
/**
|
|
54
|
-
* `track`, `identify`, the consent mode, the replay consent and their setter, the visitor id and whether
|
|
55
|
-
* experiments have loaded. `setConsent("cookies")` sets the mode, `setConsent({ replay: true })` the
|
|
62
|
+
* `track`, `identify`, `captureError`, the consent mode, the replay consent and their setter, the visitor id and whether
|
|
63
|
+
* the experiments and flags have loaded. `setConsent("cookies")` sets the mode, `setConsent({ replay: true })` the
|
|
64
|
+
* replay consent.
|
|
56
65
|
*/
|
|
57
66
|
export declare const useCockpit: () => {
|
|
58
67
|
track: (event: string, props?: Record<string, unknown>) => void;
|
|
59
68
|
identify: (id: string, props?: Record<string, unknown>) => void;
|
|
69
|
+
captureError: (error: unknown) => void;
|
|
60
70
|
distinctId: string;
|
|
61
71
|
ready: boolean;
|
|
62
72
|
embedded: boolean;
|
|
@@ -64,6 +74,16 @@ export declare const useCockpit: () => {
|
|
|
64
74
|
replay: boolean;
|
|
65
75
|
setConsent: (choice: ConsentChoice) => void;
|
|
66
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* A feature flag from MRRocket › Build › Feature flags: true while it is on for this visitor (its audience and rollout
|
|
79
|
+
* in Production), false while it is off, unknown, or the flags have not loaded yet (`useCockpit().ready`). The person's
|
|
80
|
+
* properties from `identify(id, props)` feed the audience conditions ("Email", "Plan" …).
|
|
81
|
+
*
|
|
82
|
+
* const newCheckout = useFlag("new-checkout");
|
|
83
|
+
*/
|
|
84
|
+
export declare const useFlag: (key: string) => boolean;
|
|
85
|
+
/** The variant of a multivariate flag for this visitor ("control", "test" …), or null while the flag is off. */
|
|
86
|
+
export declare const useFlagVariant: (key: string) => string | null;
|
|
67
87
|
/** The variant this visitor sees, for example `"control"` or `"a"`. Records the exposure. */
|
|
68
88
|
export declare const useVariant: (key: string) => VariantId;
|
|
69
89
|
/** Renders the element for the visitor's variant; control when the variant has no element. */
|
package/dist/client.js
CHANGED
|
@@ -6,6 +6,7 @@ import { usePathname } from "next/navigation";
|
|
|
6
6
|
import { Ctx, useCtx } from "./context.js";
|
|
7
7
|
import { Survey, forgetStoredDismissals } from "./survey.js";
|
|
8
8
|
import { RageDetector, clickInfo, seenDepth } from "./capture.js";
|
|
9
|
+
import { startErrorCapture } from "./errors.js";
|
|
9
10
|
import {
|
|
10
11
|
DEFAULT_REFERRAL_DAYS,
|
|
11
12
|
captureReferral,
|
|
@@ -19,9 +20,11 @@ import {
|
|
|
19
20
|
EMBED_PARAM,
|
|
20
21
|
ID_COOKIE,
|
|
21
22
|
PREVIEW_PARAM,
|
|
23
|
+
SDK_VERSION,
|
|
22
24
|
SESSION_COOKIE,
|
|
23
25
|
SESSION_TIMEOUT_MS,
|
|
24
26
|
envKey,
|
|
27
|
+
flagFor,
|
|
25
28
|
newDistinctId,
|
|
26
29
|
parsePreview,
|
|
27
30
|
readUtm,
|
|
@@ -88,8 +91,8 @@ var warnOnce = (message) => {
|
|
|
88
91
|
console.warn(`[MRRocket] ${message}`);
|
|
89
92
|
};
|
|
90
93
|
var byteLength = (s) => new TextEncoder().encode(s).length;
|
|
91
|
-
var bodies = (key, events) => {
|
|
92
|
-
const head = `{"key":${JSON.stringify(key)},"events":[`;
|
|
94
|
+
var bodies = (key, events, sdk) => {
|
|
95
|
+
const head = `{"key":${JSON.stringify(key)},"sdk":${JSON.stringify(sdk)},"events":[`;
|
|
93
96
|
const empty = byteLength(head) + 2;
|
|
94
97
|
const out = [];
|
|
95
98
|
let chunk = [];
|
|
@@ -122,6 +125,8 @@ var CockpitProvider = ({
|
|
|
122
125
|
replay: replayProp = false,
|
|
123
126
|
heatmaps = true,
|
|
124
127
|
surveys = true,
|
|
128
|
+
errors = true,
|
|
129
|
+
release,
|
|
125
130
|
attribution = "consent",
|
|
126
131
|
referralParam = "ref",
|
|
127
132
|
children
|
|
@@ -222,7 +227,7 @@ var CockpitProvider = ({
|
|
|
222
227
|
return;
|
|
223
228
|
}
|
|
224
229
|
const url = `${host}/api/sdk/events`;
|
|
225
|
-
for (const body of bodies(apiKey, queue.current.splice(0))) {
|
|
230
|
+
for (const body of bodies(apiKey, queue.current.splice(0), { v: SDK_VERSION, errors })) {
|
|
226
231
|
const sent = beacon && typeof navigator !== "undefined" && "sendBeacon" in navigator && navigator.sendBeacon(url, body);
|
|
227
232
|
if (!sent)
|
|
228
233
|
fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: true }).then((r) => {
|
|
@@ -230,7 +235,7 @@ var CockpitProvider = ({
|
|
|
230
235
|
}).catch(() => warnOnce(`Could not reach ${host || "this origin"} to send events. Check NEXT_PUBLIC_MRROCKET_HOST.`));
|
|
231
236
|
}
|
|
232
237
|
},
|
|
233
|
-
[apiKey, host]
|
|
238
|
+
[apiKey, host, errors]
|
|
234
239
|
);
|
|
235
240
|
useEffect(() => {
|
|
236
241
|
const timer = window.setInterval(() => flush(), 2e3);
|
|
@@ -265,6 +270,7 @@ var CockpitProvider = ({
|
|
|
265
270
|
});
|
|
266
271
|
}, []);
|
|
267
272
|
const replayer = useRef(null);
|
|
273
|
+
const errorer = useRef(null);
|
|
268
274
|
const track = useCallback(
|
|
269
275
|
(event, props) => {
|
|
270
276
|
record(event, props);
|
|
@@ -340,7 +346,25 @@ var CockpitProvider = ({
|
|
|
340
346
|
useEffect(() => {
|
|
341
347
|
enterPage();
|
|
342
348
|
replayer.current?.page(pathname);
|
|
349
|
+
errorer.current?.crumb("navigation", pathname);
|
|
343
350
|
}, [pathname]);
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
if (!errors || !apiKey || embedded) return;
|
|
353
|
+
const capture = startErrorCapture({
|
|
354
|
+
host,
|
|
355
|
+
apiKey,
|
|
356
|
+
release,
|
|
357
|
+
context: () => ({ send: live.current.tracking, distinctId: live.current.sendId, sessionId: page.session?.id }),
|
|
358
|
+
warn: warnOnce
|
|
359
|
+
});
|
|
360
|
+
errorer.current = capture;
|
|
361
|
+
capture.crumb("navigation", window.location.pathname);
|
|
362
|
+
return () => {
|
|
363
|
+
capture.stop();
|
|
364
|
+
if (errorer.current === capture) errorer.current = null;
|
|
365
|
+
};
|
|
366
|
+
}, [errors, apiKey, host, release, embedded]);
|
|
367
|
+
const captureError = useCallback((error) => errorer.current?.capture(error, "manual"), []);
|
|
344
368
|
useEffect(() => {
|
|
345
369
|
if (!tracking || lastPage.current === pathname) return;
|
|
346
370
|
lastPage.current = pathname;
|
|
@@ -355,6 +379,7 @@ var CockpitProvider = ({
|
|
|
355
379
|
const c = clickInfo(e);
|
|
356
380
|
if (!c) return;
|
|
357
381
|
const raged = rage.current.push(c.x, c.y, Date.now());
|
|
382
|
+
if (c.label || c.selector) errorer.current?.crumb("click", c.label || c.selector);
|
|
358
383
|
replayer.current?.click(c, raged);
|
|
359
384
|
if (!heatmaps) return;
|
|
360
385
|
const props = { x: c.x, y: c.y, vw: c.vw, vh: c.vh, docH: c.docH, selector: c.selector, label: c.label, dead: !c.interactive };
|
|
@@ -430,8 +455,8 @@ var CockpitProvider = ({
|
|
|
430
455
|
}, [recording, replayConfig, host, apiKey]);
|
|
431
456
|
const texts = useMemo(() => textChanges(config, distinctId, preview), [config, distinctId, preview]);
|
|
432
457
|
const value = useMemo(
|
|
433
|
-
() => ({ apiKey, host, user, ready, distinctId, config, preview, embedded, consent, effectiveConsent, replayConsent, setConsent, texts, track, identify, expose }),
|
|
434
|
-
[apiKey, host, user, ready, distinctId, config, preview, embedded, consent, effectiveConsent, replayConsent, setConsent, texts, track, identify, expose]
|
|
458
|
+
() => ({ apiKey, host, user, ready, distinctId, config, preview, embedded, consent, effectiveConsent, replayConsent, setConsent, texts, track, identify, expose, captureError }),
|
|
459
|
+
[apiKey, host, user, ready, distinctId, config, preview, embedded, consent, effectiveConsent, replayConsent, setConsent, texts, track, identify, expose, captureError]
|
|
435
460
|
);
|
|
436
461
|
return /* @__PURE__ */ jsxs(Ctx.Provider, { value, children: [
|
|
437
462
|
children,
|
|
@@ -439,8 +464,17 @@ var CockpitProvider = ({
|
|
|
439
464
|
] });
|
|
440
465
|
};
|
|
441
466
|
var useCockpit = () => {
|
|
442
|
-
const { track, identify, distinctId, ready, embedded, consent, replayConsent, setConsent } = useCtx();
|
|
443
|
-
return { track, identify, distinctId, ready, embedded, consent, replay: replayConsent, setConsent };
|
|
467
|
+
const { track, identify, captureError, distinctId, ready, embedded, consent, replayConsent, setConsent } = useCtx();
|
|
468
|
+
return { track, identify, captureError, distinctId, ready, embedded, consent, replay: replayConsent, setConsent };
|
|
469
|
+
};
|
|
470
|
+
var useFlag = (key) => useFlagValue(key) !== false;
|
|
471
|
+
var useFlagVariant = (key) => {
|
|
472
|
+
const v = useFlagValue(key);
|
|
473
|
+
return typeof v === "string" ? v : null;
|
|
474
|
+
};
|
|
475
|
+
var useFlagValue = (key) => {
|
|
476
|
+
const { config, distinctId, user } = useCtx();
|
|
477
|
+
return useMemo(() => flagFor(key, config, distinctId, { ...user }), [key, config, distinctId, user]);
|
|
444
478
|
};
|
|
445
479
|
var useVariant = (key) => {
|
|
446
480
|
const { config, distinctId, preview, expose } = useCtx();
|
|
@@ -463,6 +497,8 @@ export {
|
|
|
463
497
|
Experiment,
|
|
464
498
|
Text,
|
|
465
499
|
useCockpit,
|
|
500
|
+
useFlag,
|
|
501
|
+
useFlagVariant,
|
|
466
502
|
useVariant
|
|
467
503
|
};
|
|
468
504
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.tsx"],"sourcesContent":["\"use client\";\n\nimport { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { Ctx, type CockpitContext, type CockpitUser, useCtx } from \"./context\";\nimport { Survey, forgetStoredDismissals } from \"./survey\";\nimport { RageDetector, clickInfo, seenDepth } from \"./capture\";\nimport type { ReplayHandle } from \"./replay\";\nimport {\n type AttributionMode,\n DEFAULT_REFERRAL_DAYS,\n captureReferral,\n carriesReferral,\n forgetStoredReferral,\n getReferral,\n resetReferral,\n storeReferral,\n} from \"./referral\";\nimport {\n type CockpitConfig,\n type CockpitEvent,\n type ConsentChoice,\n type ConsentMode,\n EMBED_PARAM,\n ID_COOKIE,\n PREVIEW_PARAM,\n SESSION_COOKIE,\n SESSION_TIMEOUT_MS,\n type UtmKey,\n type VariantId,\n envKey,\n newDistinctId,\n parsePreview,\n readUtm,\n resolveHost,\n resolveVariant,\n textChanges,\n} from \"./core\";\n\nconst YEAR_S = 60 * 60 * 24 * 365;\n/** The server takes at most 100 events and 64 KB per request; stay a little under the size. */\nconst MAX_BATCH = 100;\nconst MAX_BYTES = 60_000;\n\nconst readCookie = (name: string) => {\n if (typeof document === \"undefined\") return undefined;\n const raw = document.cookie\n .split(\"; \")\n .find((c) => c.startsWith(`${name}=`))\n ?.slice(name.length + 1);\n if (!raw) return undefined;\n try {\n return decodeURIComponent(raw);\n } catch {\n return raw;\n }\n};\n\nconst writeCookie = (name: string, value: string, maxAge: number) => {\n const secure = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAge}; samesite=lax${secure}`;\n};\n\nconst deleteCookie = (name: string) => {\n document.cookie = `${name}=; path=/; max-age=0; samesite=lax`;\n};\n\n/**\n * What lives for one page load, outside React so it survives a provider that remounts on navigation: the in-memory\n * visitor id, the session, the current page's URL and referrer, and the campaign (UTM) parameters.\n * Only touched in the browser (effects and event handlers), never during a server render.\n */\nconst page: {\n id?: string;\n session?: { id: string; last: number };\n url?: string;\n referrer?: string;\n utm: Partial<Record<UtmKey, string>>;\n} = { utm: {} };\n\nconst memoryId = () => (page.id ??= newDistinctId());\n\n/**\n * The visitor id for experiments, surveys and feedback. Only \"cookies\" reads or writes `ck_id`; the other modes keep a\n * random id in memory for this page load. The page id follows the cookie, so leaving \"cookies\" keeps the same variants.\n */\nconst visitorId = (mode: ConsentMode, write: boolean) => {\n if (mode !== \"cookies\") return memoryId();\n const stored = readCookie(ID_COOKIE);\n if (stored) return (page.id = stored);\n const id = memoryId();\n if (write) writeCookie(ID_COOKIE, id, YEAR_S);\n return id;\n};\n\n/** The session id for an event. Every event renews it; after 30 minutes without one a new session starts. */\nconst touchSession = (persist: boolean) => {\n const now = Date.now();\n const s = page.session;\n // In \"cookies\" mode the cookie wins, so tabs share a session.\n const id = (persist ? readCookie(SESSION_COOKIE) : undefined) ?? (s && now - s.last < SESSION_TIMEOUT_MS ? s.id : newDistinctId());\n page.session = { id, last: now };\n if (persist) writeCookie(SESSION_COOKIE, id, SESSION_TIMEOUT_MS / 1000);\n return id;\n};\n\n/** On every route change: the previous in-app URL becomes the referrer, and a URL with UTM parameters replaces the old ones. */\nconst enterPage = () => {\n const href = window.location.href;\n if (page.url === href) return;\n page.referrer = page.url ?? (document.referrer || undefined);\n page.url = href;\n const utm = readUtm(window.location.search);\n if (Object.keys(utm).length) page.utm = utm;\n};\n\n/** Do Not Track or Global Privacy Control. */\nconst privacySignal = () => {\n const nav = navigator as Navigator & { doNotTrack?: string | null; globalPrivacyControl?: boolean };\n const win = window as unknown as { doNotTrack?: string | null };\n return nav.doNotTrack === \"1\" || win.doNotTrack === \"1\" || nav.globalPrivacyControl === true;\n};\n\n/** Tells the developer once, in the browser console, why the SDK is not reaching MRRocket (a wrong host or key is the usual cause). */\nconst warned = new Set<string>();\nconst warnOnce = (message: string) => {\n if (warned.has(message)) return;\n warned.add(message);\n console.warn(`[MRRocket] ${message}`);\n};\n\nconst byteLength = (s: string) => new TextEncoder().encode(s).length;\n\n/** Request bodies of at most 100 events each, under the size limit. An event too large on its own is dropped. */\nconst bodies = (key: string, events: CockpitEvent[]) => {\n const head = `{\"key\":${JSON.stringify(key)},\"events\":[`;\n const empty = byteLength(head) + 2;\n const out: string[] = [];\n let chunk: string[] = [];\n let size = empty;\n const close = () => {\n if (chunk.length) out.push(`${head}${chunk.join(\",\")}]}`);\n chunk = [];\n size = empty;\n };\n for (const event of events) {\n const json = JSON.stringify(event);\n const n = byteLength(json) + 1;\n if (empty + n > MAX_BYTES) continue;\n if (chunk.length === MAX_BATCH || size + n > MAX_BYTES) close();\n chunk.push(json);\n size += n;\n }\n close();\n return out;\n};\n\nexport interface CockpitProviderProps {\n /** The product's public key from Cockpit › Settings › SDK. Safe in the browser. Default: `NEXT_PUBLIC_MRROCKET_KEY`. */\n apiKey?: string;\n /** Where MRRocket runs, like `https://app.example.com`. Default: `NEXT_PUBLIC_MRROCKET_HOST`, else the page's own origin. */\n host?: string;\n preview?: Record<string, VariantId>;\n embedded?: boolean;\n /** Config fetched on the server, to render the right variant on the first paint. */\n config?: CockpitConfig;\n /** Your own id for this visitor, for example a signed-in user id. Keeps variants stable without cookies. */\n distinctId?: string;\n /**\n * \"cookieless\" (default) stores nothing in the browser, \"cookies\" keeps a visitor cookie for a year, \"off\" sends\n * nothing. The SDK does not remember the choice: set it on every page load, from the prop or setConsent().\n */\n consent?: ConsentMode;\n /** Send nothing when the browser asks not to be tracked (Do Not Track or Global Privacy Control). */\n respectDnt?: boolean;\n /**\n * The visitor's consent to session recordings, separate from analytics (default false). Recording also needs the\n * product to record (Cockpit › Sessions) and analytics not \"off\". Without it nothing is recorded or buffered.\n * Change it later with `setConsent({ replay })`.\n */\n replay?: boolean;\n /**\n * Clicks and scroll depth for Heatmaps (default true): where a click landed, a short selector and a link's or\n * button's label, never typed text. Sent under the normal analytics consent as `$click`, `$rageclick` and `$scroll`.\n */\n heatmaps?: boolean;\n /** Ask visitors the surveys you run in Cockpit. Set false to switch them off. */\n surveys?: boolean;\n /**\n * Affiliate referrals (`?ref=` links and MRRocket's tracked links). \"consent\" (default): stored in the first-party\n * `mrr_ref` cookie only while consent is \"cookies\", in memory otherwise. \"always\": stored whatever the consent (only\n * where your legal basis allows it). \"off\": referrals are ignored.\n */\n attribution?: AttributionMode;\n /** The query parameter of direct partner links, default `ref` (`https://your.site/?ref=maya`). */\n referralParam?: string;\n children: ReactNode;\n}\n\n/**\n * Put it once around your app, in `app/layout.tsx`. It loads your experiments, keeps each visitor in one variant,\n * records page views and sends your events to Cockpit in small batches.\n *\n * `preview` forces variants for this render, for example from the `ck_preview` search param on the server,\n * so a Cockpit preview never flickers. Without it the provider reads the param itself after the first render.\n */\nexport const CockpitProvider = ({\n apiKey: apiKeyProp,\n host: hostProp,\n preview: forced,\n embedded: embeddedProp,\n config: bootstrapConfig,\n distinctId: bootstrapId,\n consent: consentProp = \"cookieless\",\n respectDnt = false,\n replay: replayProp = false,\n heatmaps = true,\n surveys = true,\n attribution = \"consent\",\n referralParam = \"ref\",\n children,\n}: CockpitProviderProps) => {\n const pathname = usePathname();\n const apiKey = apiKeyProp ?? envKey() ?? \"\";\n const host = resolveHost(hostProp);\n const [config, setConfig] = useState<CockpitConfig | null>(bootstrapConfig ?? null);\n const [consent, setConsentState] = useState<ConsentMode>(consentProp);\n const [replayConsent, setReplayConsent] = useState(replayProp);\n const [mounted, setMounted] = useState(false);\n const [dnt, setDnt] = useState(false);\n // The visitor id and the consent mode it was made for; null until the browser has been read.\n const [visitor, setVisitor] = useState<{ mode: ConsentMode; id: string } | null>(null);\n const [identified, setIdentified] = useState<string | undefined>();\n const [urlPreview, setUrlPreview] = useState<Record<string, VariantId>>({});\n const [urlEmbedded, setUrlEmbedded] = useState(false);\n const [user, setUser] = useState<CockpitUser | undefined>();\n const [ready, setReady] = useState(!!bootstrapConfig);\n const queue = useRef<CockpitEvent[]>([]);\n // Calls from before the visitor was known (a child's effect runs before ours); replayed once it is.\n const early = useRef<(() => void)[]>([]);\n const exposed = useRef(new Set<string>());\n const lastPage = useRef<string | null>(null);\n\n const bootstrap = bootstrapId && bootstrapId !== \"anonymous\" ? bootstrapId : undefined;\n const preview = useMemo(() => ({ ...urlPreview, ...forced }), [urlPreview, forced]);\n const embedded = embeddedProp ?? urlEmbedded;\n const effectiveConsent: ConsentMode = respectDnt && dnt ? \"off\" : consent;\n const distinctId = identified ?? bootstrap ?? visitor?.id ?? \"anonymous\";\n const tracking = !embedded && effectiveConsent !== \"off\" && visitor?.mode === effectiveConsent;\n // Without cookies, events carry an id only after identify(); the server counts everyone else by its daily hash.\n const sendId = effectiveConsent === \"cookies\" ? distinctId : identified;\n\n // Preview flags and the browser's privacy signal, once, in the browser.\n useEffect(() => {\n const params = new URLSearchParams(window.location.search);\n setUrlPreview(parsePreview(params.get(PREVIEW_PARAM)));\n setUrlEmbedded(params.get(EMBED_PARAM) === \"1\");\n setDnt(privacySignal());\n setMounted(true);\n }, []);\n\n const setConsent = useCallback((choice: ConsentChoice) => {\n const { analytics: mode, replay } = typeof choice === \"string\" ? { analytics: choice, replay: undefined } : choice;\n if (mode === \"off\") queue.current.length = 0;\n if (mode) setConsentState(mode);\n if (typeof replay === \"boolean\") setReplayConsent(replay);\n }, []);\n\n // Follow the `consent` and `replay` props when they change; setConsent() calls in between stay until they do.\n const lastProp = useRef(consentProp);\n useEffect(() => {\n if (lastProp.current === consentProp) return;\n lastProp.current = consentProp;\n setConsent(consentProp);\n }, [consentProp, setConsent]);\n const lastReplayProp = useRef(replayProp);\n useEffect(() => {\n if (lastReplayProp.current === replayProp) return;\n lastReplayProp.current = replayProp;\n setConsent({ replay: replayProp });\n }, [replayProp, setConsent]);\n\n // Storage follows consent. Leaving \"cookies\" deletes what the SDK stored; this page keeps its id in memory.\n const storedFor = useRef<ConsentMode | null>(null);\n useEffect(() => {\n if (!mounted) return;\n const before = storedFor.current;\n storedFor.current = effectiveConsent;\n if (before === \"cookies\" && effectiveConsent !== \"cookies\") {\n deleteCookie(ID_COOKIE);\n deleteCookie(SESSION_COOKIE);\n forgetStoredDismissals();\n }\n if (effectiveConsent === \"off\") {\n queue.current.length = 0;\n page.session = undefined;\n }\n setVisitor({ mode: effectiveConsent, id: visitorId(effectiveConsent, !bootstrap) });\n }, [mounted, effectiveConsent, bootstrap]);\n\n // Experiments for this key. Without a key or when MRRocket is unreachable the page keeps control and carries on.\n useEffect(() => {\n if (!apiKey) warnOnce(\"No public key. Pass apiKey or set NEXT_PUBLIC_MRROCKET_KEY; nothing is tracked until then.\");\n if (bootstrapConfig || !apiKey) {\n setReady(true);\n return;\n }\n let live = true;\n fetch(`${host}/api/sdk/config?key=${encodeURIComponent(apiKey)}`)\n .then((r) => {\n if (!r.ok) warnOnce(`Config request to ${host || \"this origin\"} answered ${r.status}. Check the public key and NEXT_PUBLIC_MRROCKET_HOST.`);\n return r.ok ? r.json() : null;\n })\n .then((c: CockpitConfig | null) => {\n if (!live) return;\n if (c && Array.isArray(c.experiments)) setConfig(c);\n setReady(true);\n })\n .catch(() => {\n warnOnce(`Could not reach ${host || \"this origin\"}. Check NEXT_PUBLIC_MRROCKET_HOST. The page keeps working with control variants.`);\n if (live) setReady(true);\n });\n return () => {\n live = false;\n };\n }, [apiKey, host, bootstrapConfig]);\n\n const flush = useCallback(\n (beacon = false) => {\n if (!queue.current.length) return;\n if (!apiKey) {\n queue.current.length = 0;\n return;\n }\n const url = `${host}/api/sdk/events`;\n for (const body of bodies(apiKey, queue.current.splice(0))) {\n const sent = beacon && typeof navigator !== \"undefined\" && \"sendBeacon\" in navigator && navigator.sendBeacon(url, body);\n if (!sent)\n fetch(url, { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body, keepalive: true })\n .then((r) => {\n if (!r.ok) warnOnce(`Events were rejected with ${r.status}. Check the public key (Settings › SDK) and NEXT_PUBLIC_MRROCKET_HOST.`);\n })\n .catch(() => warnOnce(`Could not reach ${host || \"this origin\"} to send events. Check NEXT_PUBLIC_MRROCKET_HOST.`));\n }\n },\n [apiKey, host],\n );\n\n useEffect(() => {\n const timer = window.setInterval(() => flush(), 2000);\n const leave = () => flush(true);\n window.addEventListener(\"pagehide\", leave);\n return () => {\n window.clearInterval(timer);\n window.removeEventListener(\"pagehide\", leave);\n flush(true);\n };\n }, [flush]);\n\n // The latest values for callbacks that must not change identity, so effects that use them run once.\n const live = useRef({ resolved: false, tracking, distinctId, sendId, persist: false, identified: false });\n live.current = { resolved: visitor !== null, tracking, distinctId, sendId, persist: visitor?.mode === \"cookies\", identified: !!identified };\n\n const record = useCallback((event: string, props: Record<string, unknown> | undefined, ts = Date.now(), url?: string) => {\n const now = live.current;\n if (!now.resolved) {\n early.current.push(() => record(event, props, ts, url));\n return;\n }\n // Previews in Cockpit never count, and nothing is sent without consent.\n if (!now.tracking) return;\n const referral = carriesReferral(event) ? getReferral() : null;\n queue.current.push({\n event,\n props: referral ? { ...props, $referral: { code: referral.code, clickId: referral.clickId } } : props,\n distinctId: now.sendId,\n sessionId: touchSession(now.persist),\n ts,\n url: url ?? window.location.href,\n referrer: page.referrer,\n ...page.utm,\n screen: { w: window.screen.width, h: window.screen.height },\n });\n }, []);\n\n // The running recorder, if this visit records (see the recording effect below).\n const replayer = useRef<ReplayHandle | null>(null);\n\n const track = useCallback(\n (event: string, props?: Record<string, unknown>) => {\n record(event, props);\n if (live.current.tracking) replayer.current?.event(event);\n },\n [record],\n );\n\n const identify = useCallback(\n (id: string, props?: Record<string, unknown>) => {\n const now = live.current;\n if (!now.resolved) {\n early.current.push(() => identify(id, props));\n return;\n }\n const previous = now.sendId;\n // From here on events carry this id in every mode; only \"cookies\" keeps it for the next visit.\n live.current = { ...now, distinctId: id, sendId: id, identified: true };\n record(\"$identify\", { ...props, id, ...(previous && previous !== id ? { previousId: previous } : {}) });\n replayer.current?.identify(id);\n if (now.persist) writeCookie(ID_COOKIE, id, YEAR_S);\n setIdentified(id);\n setUser({ id, ...(props as Omit<CockpitUser, \"id\">) });\n },\n [record],\n );\n\n // Replay what came in before the visitor was known: sent when tracking is on, dropped when it is not.\n useEffect(() => {\n if (!visitor) return;\n for (const run of early.current.splice(0)) run();\n }, [visitor]);\n\n // Affiliate referrals: read once the consent is known and the program's cookie days have loaded.\n const referralDays = config?.referral?.cookieDays ?? DEFAULT_REFERRAL_DAYS;\n const storeReferrals = attribution === \"always\" ? effectiveConsent !== \"off\" : attribution === \"consent\" && effectiveConsent === \"cookies\";\n const captured = useRef<string | null>(null);\n const storedReferral = useRef(false);\n useEffect(() => {\n if (!visitor || !ready || embedded) return;\n if (attribution === \"off\" || effectiveConsent === \"off\") {\n resetReferral();\n if (attribution === \"off\" || storedReferral.current) forgetStoredReferral();\n return;\n }\n const href = window.location.href;\n if (captured.current === href) return;\n captured.current = href;\n void captureReferral({\n search: window.location.search,\n param: referralParam,\n store: storeReferrals,\n days: referralDays,\n beacon: apiKey\n ? ({ code, clickId }) =>\n fetch(`${host}/api/sdk/affiliate/click`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ key: apiKey, code, clickId, url: href, referrer: document.referrer || undefined }),\n keepalive: true,\n })\n .then((r) => (r.ok ? (r.json() as Promise<{ clickId?: string | null }>) : null))\n .then((b) => b?.clickId ?? null)\n : undefined,\n });\n }, [visitor, ready, embedded, attribution, effectiveConsent, pathname, referralParam, storeReferrals, referralDays, apiKey, host]);\n\n // Consent given later keeps this page load's referral; consent withdrawn deletes what was stored.\n useEffect(() => {\n if (!mounted) return;\n const r = getReferral();\n if (storeReferrals && r) storeReferral(r, referralDays);\n else if (!storeReferrals && storedReferral.current) forgetStoredReferral();\n storedReferral.current = storeReferrals;\n }, [mounted, storeReferrals, referralDays]);\n\n // `tracking` is a dependency so that variants seen before consent are recorded once tracking starts.\n const expose = useCallback(\n (key: string, variant: VariantId) => {\n if (!tracking) return;\n const mark = `${pathname}|${key}`;\n if (exposed.current.has(mark)) return;\n exposed.current.add(mark);\n track(\"$exposure\", { experiment: key, variant });\n },\n [pathname, track, tracking],\n );\n\n // The referrer and campaign of each page; before the page view below, so it already carries them.\n useEffect(() => {\n enterPage();\n replayer.current?.page(pathname);\n }, [pathname]);\n\n // One page view per route change while tracking, so a later setConsent() records the current page right away\n // (a ref keeps React's dev double-run from counting twice).\n useEffect(() => {\n if (!tracking || lastPage.current === pathname) return;\n lastPage.current = pathname;\n // Exposures are remembered per path (\"/pricing|test\"), so nothing needs clearing here.\n track(\"$pageview\", { path: pathname });\n }, [pathname, tracking, track]);\n\n // Clicks on anything marked `data-ck-goal=\"signup_started\"` count as that goal. Every click also feeds Heatmaps\n // (`$click`, `$rageclick` on the third quick click on one spot) and, when this visit records, the recording.\n const rage = useRef(new RageDetector());\n useEffect(() => {\n const onClick = (e: MouseEvent) => {\n const el = (e.target as HTMLElement | null)?.closest<HTMLElement>(\"[data-ck-goal]\");\n if (el?.dataset.ckGoal) track(el.dataset.ckGoal, { text: el.textContent?.trim().slice(0, 80) });\n if (!live.current.tracking) return;\n const c = clickInfo(e);\n if (!c) return;\n const raged = rage.current.push(c.x, c.y, Date.now());\n replayer.current?.click(c, raged);\n if (!heatmaps) return;\n const props = { x: c.x, y: c.y, vw: c.vw, vh: c.vh, docH: c.docH, selector: c.selector, label: c.label, dead: !c.interactive };\n record(\"$click\", props);\n if (raged) record(\"$rageclick\", props);\n };\n document.addEventListener(\"click\", onClick);\n return () => document.removeEventListener(\"click\", onClick);\n }, [track, record, heatmaps]);\n\n // Scroll depth for Heatmaps: the deepest point seen on each page, sent when the page changes or hides.\n const depth = useRef<{ url: string; max: number; docH: number } | null>(null);\n useEffect(() => {\n if (!heatmaps || !tracking || embedded) return;\n const send = () => {\n const d = depth.current;\n if (d && d.max > 0) record(\"$scroll\", { depth: d.max, docH: d.docH, vw: window.innerWidth, vh: window.innerHeight }, Date.now(), d.url);\n depth.current = null;\n };\n const measure = () => {\n const doc = document.documentElement;\n const docH = Math.max(doc.scrollHeight, document.body?.scrollHeight ?? 0);\n const max = seenDepth();\n const href = window.location.href;\n if (depth.current?.url !== href) depth.current = { url: href, max, docH };\n else if (max > depth.current.max) depth.current = { url: href, max, docH };\n };\n measure();\n let frame = 0;\n const onScroll = () => {\n if (frame) return;\n frame = window.requestAnimationFrame(() => {\n frame = 0;\n measure();\n });\n };\n const onHide = () => {\n send();\n flush(true);\n };\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n window.addEventListener(\"pagehide\", onHide);\n return () => {\n window.removeEventListener(\"scroll\", onScroll);\n window.removeEventListener(\"pagehide\", onHide);\n window.cancelAnimationFrame(frame);\n send();\n };\n }, [heatmaps, tracking, embedded, pathname, record, flush]);\n\n // Session recording: only with the visitor's replay consent, while tracking, and when the product records. The\n // recorder and rrweb are loaded here, lazily; withdrawing consent stops it and drops what was not sent.\n const replayConfig = config?.replay;\n const recording = tracking && replayConsent && !!replayConfig && !!apiKey;\n useEffect(() => {\n if (!recording || !replayConfig) return;\n let active = true;\n let handle: ReplayHandle | null = null;\n // `typeof window` is a build-time constant in Next.js: on the server this folds to null and the import (with\n // rrweb) never enters the server bundle.\n void (typeof window === \"undefined\" ? Promise.resolve(null) : import(\"./replay\"))\n .then((m) =>\n m?.startReplay({\n host,\n apiKey,\n sessionId: touchSession(live.current.persist),\n distinctId: live.current.sendId,\n identified: live.current.identified,\n sampleRate: replayConfig.sampleRate,\n warn: warnOnce,\n }),\n )\n .then((h) => {\n if (!active) h?.stop(true);\n else replayer.current = handle = h ?? null;\n })\n .catch(() => warnOnce(\"Session recording could not start (the recorder did not load).\"));\n return () => {\n active = false;\n handle?.stop(true);\n if (replayer.current === handle) replayer.current = null;\n };\n }, [recording, replayConfig, host, apiKey]);\n\n const texts = useMemo(() => textChanges(config, distinctId, preview), [config, distinctId, preview]);\n\n const value = useMemo<CockpitContext>(\n () => ({ apiKey, host, user, ready, distinctId, config, preview, embedded, consent, effectiveConsent, replayConsent, setConsent, texts, track, identify, expose }),\n [apiKey, host, user, ready, distinctId, config, preview, embedded, consent, effectiveConsent, replayConsent, setConsent, texts, track, identify, expose],\n );\n return (\n <Ctx.Provider value={value}>\n {children}\n {surveys ? <Survey /> : null}\n </Ctx.Provider>\n );\n};\n\n/**\n * `track`, `identify`, the consent mode, the replay consent and their setter, the visitor id and whether the\n * experiments have loaded. `setConsent(\"cookies\")` sets the mode, `setConsent({ replay: true })` the replay consent.\n */\nexport const useCockpit = () => {\n const { track, identify, distinctId, ready, embedded, consent, replayConsent, setConsent } = useCtx();\n return { track, identify, distinctId, ready, embedded, consent, replay: replayConsent, setConsent };\n};\n\n/** The variant this visitor sees, for example `\"control\"` or `\"a\"`. Records the exposure. */\nexport const useVariant = (key: string): VariantId => {\n const { config, distinctId, preview, expose } = useCtx();\n const variant = resolveVariant(key, config, distinctId, preview);\n useEffect(() => {\n if (config || preview[key]) expose(key, variant);\n }, [config, preview, key, variant, expose]);\n return variant;\n};\n\n/** Renders the element for the visitor's variant; control when the variant has no element. */\nexport const Experiment = ({ name, variants }: { name: string; variants: Record<VariantId, ReactNode> & { control: ReactNode } }) => {\n const variant = useVariant(name);\n return <>{variants[variant] ?? variants.control}</>;\n};\n\n/**\n * A text that experiments may change without a deploy: Cockpit sends the new copy for `id` and this renders it.\n * Write your default as the children.\n */\nexport const Text = ({ id, children }: { id: string; children: string }) => {\n const { texts } = useCtx();\n return <>{texts[id] ?? children}</>;\n};\n"],"mappings":"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","names":["live"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.tsx"],"sourcesContent":["\"use client\";\n\nimport { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { Ctx, type CockpitContext, type CockpitUser, useCtx } from \"./context\";\nimport { Survey, forgetStoredDismissals } from \"./survey\";\nimport { RageDetector, clickInfo, seenDepth } from \"./capture\";\nimport type { ReplayHandle } from \"./replay\";\nimport { type ErrorCapture, startErrorCapture } from \"./errors\";\nimport {\n type AttributionMode,\n DEFAULT_REFERRAL_DAYS,\n captureReferral,\n carriesReferral,\n forgetStoredReferral,\n getReferral,\n resetReferral,\n storeReferral,\n} from \"./referral\";\nimport {\n type CockpitConfig,\n type CockpitEvent,\n type ConsentChoice,\n type ConsentMode,\n EMBED_PARAM,\n ID_COOKIE,\n PREVIEW_PARAM,\n SDK_VERSION,\n SESSION_COOKIE,\n SESSION_TIMEOUT_MS,\n type UtmKey,\n type VariantId,\n envKey,\n flagFor,\n newDistinctId,\n parsePreview,\n readUtm,\n resolveHost,\n resolveVariant,\n textChanges,\n} from \"./core\";\n\nconst YEAR_S = 60 * 60 * 24 * 365;\n/** The server takes at most 100 events and 64 KB per request; stay a little under the size. */\nconst MAX_BATCH = 100;\nconst MAX_BYTES = 60_000;\n\nconst readCookie = (name: string) => {\n if (typeof document === \"undefined\") return undefined;\n const raw = document.cookie\n .split(\"; \")\n .find((c) => c.startsWith(`${name}=`))\n ?.slice(name.length + 1);\n if (!raw) return undefined;\n try {\n return decodeURIComponent(raw);\n } catch {\n return raw;\n }\n};\n\nconst writeCookie = (name: string, value: string, maxAge: number) => {\n const secure = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAge}; samesite=lax${secure}`;\n};\n\nconst deleteCookie = (name: string) => {\n document.cookie = `${name}=; path=/; max-age=0; samesite=lax`;\n};\n\n/**\n * What lives for one page load, outside React so it survives a provider that remounts on navigation: the in-memory\n * visitor id, the session, the current page's URL and referrer, and the campaign (UTM) parameters.\n * Only touched in the browser (effects and event handlers), never during a server render.\n */\nconst page: {\n id?: string;\n session?: { id: string; last: number };\n url?: string;\n referrer?: string;\n utm: Partial<Record<UtmKey, string>>;\n} = { utm: {} };\n\nconst memoryId = () => (page.id ??= newDistinctId());\n\n/**\n * The visitor id for experiments, surveys and feedback. Only \"cookies\" reads or writes `ck_id`; the other modes keep a\n * random id in memory for this page load. The page id follows the cookie, so leaving \"cookies\" keeps the same variants.\n */\nconst visitorId = (mode: ConsentMode, write: boolean) => {\n if (mode !== \"cookies\") return memoryId();\n const stored = readCookie(ID_COOKIE);\n if (stored) return (page.id = stored);\n const id = memoryId();\n if (write) writeCookie(ID_COOKIE, id, YEAR_S);\n return id;\n};\n\n/** The session id for an event. Every event renews it; after 30 minutes without one a new session starts. */\nconst touchSession = (persist: boolean) => {\n const now = Date.now();\n const s = page.session;\n // In \"cookies\" mode the cookie wins, so tabs share a session.\n const id = (persist ? readCookie(SESSION_COOKIE) : undefined) ?? (s && now - s.last < SESSION_TIMEOUT_MS ? s.id : newDistinctId());\n page.session = { id, last: now };\n if (persist) writeCookie(SESSION_COOKIE, id, SESSION_TIMEOUT_MS / 1000);\n return id;\n};\n\n/** On every route change: the previous in-app URL becomes the referrer, and a URL with UTM parameters replaces the old ones. */\nconst enterPage = () => {\n const href = window.location.href;\n if (page.url === href) return;\n page.referrer = page.url ?? (document.referrer || undefined);\n page.url = href;\n const utm = readUtm(window.location.search);\n if (Object.keys(utm).length) page.utm = utm;\n};\n\n/** Do Not Track or Global Privacy Control. */\nconst privacySignal = () => {\n const nav = navigator as Navigator & { doNotTrack?: string | null; globalPrivacyControl?: boolean };\n const win = window as unknown as { doNotTrack?: string | null };\n return nav.doNotTrack === \"1\" || win.doNotTrack === \"1\" || nav.globalPrivacyControl === true;\n};\n\n/** Tells the developer once, in the browser console, why the SDK is not reaching MRRocket (a wrong host or key is the usual cause). */\nconst warned = new Set<string>();\nconst warnOnce = (message: string) => {\n if (warned.has(message)) return;\n warned.add(message);\n console.warn(`[MRRocket] ${message}`);\n};\n\nconst byteLength = (s: string) => new TextEncoder().encode(s).length;\n\n/**\n * Request bodies of at most 100 events each, under the size limit. An event too large on its own is dropped. `sdk` tells\n * MRRocket which SDK the site runs and whether it reports errors (Build › Errors and Feature flags show it).\n */\nconst bodies = (key: string, events: CockpitEvent[], sdk: { v: string; errors: boolean }) => {\n const head = `{\"key\":${JSON.stringify(key)},\"sdk\":${JSON.stringify(sdk)},\"events\":[`;\n const empty = byteLength(head) + 2;\n const out: string[] = [];\n let chunk: string[] = [];\n let size = empty;\n const close = () => {\n if (chunk.length) out.push(`${head}${chunk.join(\",\")}]}`);\n chunk = [];\n size = empty;\n };\n for (const event of events) {\n const json = JSON.stringify(event);\n const n = byteLength(json) + 1;\n if (empty + n > MAX_BYTES) continue;\n if (chunk.length === MAX_BATCH || size + n > MAX_BYTES) close();\n chunk.push(json);\n size += n;\n }\n close();\n return out;\n};\n\nexport interface CockpitProviderProps {\n /** The product's public key from Cockpit › Settings › SDK. Safe in the browser. Default: `NEXT_PUBLIC_MRROCKET_KEY`. */\n apiKey?: string;\n /** Where MRRocket runs, like `https://app.example.com`. Default: `NEXT_PUBLIC_MRROCKET_HOST`, else the page's own origin. */\n host?: string;\n preview?: Record<string, VariantId>;\n embedded?: boolean;\n /** Config fetched on the server, to render the right variant on the first paint. */\n config?: CockpitConfig;\n /** Your own id for this visitor, for example a signed-in user id. Keeps variants stable without cookies. */\n distinctId?: string;\n /**\n * \"cookieless\" (default) stores nothing in the browser, \"cookies\" keeps a visitor cookie for a year, \"off\" sends\n * nothing. The SDK does not remember the choice: set it on every page load, from the prop or setConsent().\n */\n consent?: ConsentMode;\n /** Send nothing when the browser asks not to be tracked (Do Not Track or Global Privacy Control). */\n respectDnt?: boolean;\n /**\n * The visitor's consent to session recordings, separate from analytics (default false). Recording also needs the\n * product to record (Cockpit › Sessions) and analytics not \"off\". Without it nothing is recorded or buffered.\n * Change it later with `setConsent({ replay })`.\n */\n replay?: boolean;\n /**\n * Clicks and scroll depth for Heatmaps (default true): where a click landed, a short selector and a link's or\n * button's label, never typed text. Sent under the normal analytics consent as `$click`, `$rageclick` and `$scroll`.\n */\n heatmaps?: boolean;\n /** Ask visitors the surveys you run in Cockpit. Set false to switch them off. */\n surveys?: boolean;\n /**\n * Error tracking for Build › Errors (default true): uncaught errors and unhandled promise rejections, with the page\n * and the last pages and clicks, under the normal analytics consent. Report errors you catch with\n * `useCockpit().captureError(error)`.\n */\n errors?: boolean;\n /** Your release (a version or a commit), so MRRocket can tell which release an error came with. */\n release?: string;\n /**\n * Affiliate referrals (`?ref=` links and MRRocket's tracked links). \"consent\" (default): stored in the first-party\n * `mrr_ref` cookie only while consent is \"cookies\", in memory otherwise. \"always\": stored whatever the consent (only\n * where your legal basis allows it). \"off\": referrals are ignored.\n */\n attribution?: AttributionMode;\n /** The query parameter of direct partner links, default `ref` (`https://your.site/?ref=maya`). */\n referralParam?: string;\n children: ReactNode;\n}\n\n/**\n * Put it once around your app, in `app/layout.tsx`. It loads your experiments, keeps each visitor in one variant,\n * records page views and sends your events to Cockpit in small batches.\n *\n * `preview` forces variants for this render, for example from the `ck_preview` search param on the server,\n * so a Cockpit preview never flickers. Without it the provider reads the param itself after the first render.\n */\nexport const CockpitProvider = ({\n apiKey: apiKeyProp,\n host: hostProp,\n preview: forced,\n embedded: embeddedProp,\n config: bootstrapConfig,\n distinctId: bootstrapId,\n consent: consentProp = \"cookieless\",\n respectDnt = false,\n replay: replayProp = false,\n heatmaps = true,\n surveys = true,\n errors = true,\n release,\n attribution = \"consent\",\n referralParam = \"ref\",\n children,\n}: CockpitProviderProps) => {\n const pathname = usePathname();\n const apiKey = apiKeyProp ?? envKey() ?? \"\";\n const host = resolveHost(hostProp);\n const [config, setConfig] = useState<CockpitConfig | null>(bootstrapConfig ?? null);\n const [consent, setConsentState] = useState<ConsentMode>(consentProp);\n const [replayConsent, setReplayConsent] = useState(replayProp);\n const [mounted, setMounted] = useState(false);\n const [dnt, setDnt] = useState(false);\n // The visitor id and the consent mode it was made for; null until the browser has been read.\n const [visitor, setVisitor] = useState<{ mode: ConsentMode; id: string } | null>(null);\n const [identified, setIdentified] = useState<string | undefined>();\n const [urlPreview, setUrlPreview] = useState<Record<string, VariantId>>({});\n const [urlEmbedded, setUrlEmbedded] = useState(false);\n const [user, setUser] = useState<CockpitUser | undefined>();\n const [ready, setReady] = useState(!!bootstrapConfig);\n const queue = useRef<CockpitEvent[]>([]);\n // Calls from before the visitor was known (a child's effect runs before ours); replayed once it is.\n const early = useRef<(() => void)[]>([]);\n const exposed = useRef(new Set<string>());\n const lastPage = useRef<string | null>(null);\n\n const bootstrap = bootstrapId && bootstrapId !== \"anonymous\" ? bootstrapId : undefined;\n const preview = useMemo(() => ({ ...urlPreview, ...forced }), [urlPreview, forced]);\n const embedded = embeddedProp ?? urlEmbedded;\n const effectiveConsent: ConsentMode = respectDnt && dnt ? \"off\" : consent;\n const distinctId = identified ?? bootstrap ?? visitor?.id ?? \"anonymous\";\n const tracking = !embedded && effectiveConsent !== \"off\" && visitor?.mode === effectiveConsent;\n // Without cookies, events carry an id only after identify(); the server counts everyone else by its daily hash.\n const sendId = effectiveConsent === \"cookies\" ? distinctId : identified;\n\n // Preview flags and the browser's privacy signal, once, in the browser.\n useEffect(() => {\n const params = new URLSearchParams(window.location.search);\n setUrlPreview(parsePreview(params.get(PREVIEW_PARAM)));\n setUrlEmbedded(params.get(EMBED_PARAM) === \"1\");\n setDnt(privacySignal());\n setMounted(true);\n }, []);\n\n const setConsent = useCallback((choice: ConsentChoice) => {\n const { analytics: mode, replay } = typeof choice === \"string\" ? { analytics: choice, replay: undefined } : choice;\n if (mode === \"off\") queue.current.length = 0;\n if (mode) setConsentState(mode);\n if (typeof replay === \"boolean\") setReplayConsent(replay);\n }, []);\n\n // Follow the `consent` and `replay` props when they change; setConsent() calls in between stay until they do.\n const lastProp = useRef(consentProp);\n useEffect(() => {\n if (lastProp.current === consentProp) return;\n lastProp.current = consentProp;\n setConsent(consentProp);\n }, [consentProp, setConsent]);\n const lastReplayProp = useRef(replayProp);\n useEffect(() => {\n if (lastReplayProp.current === replayProp) return;\n lastReplayProp.current = replayProp;\n setConsent({ replay: replayProp });\n }, [replayProp, setConsent]);\n\n // Storage follows consent. Leaving \"cookies\" deletes what the SDK stored; this page keeps its id in memory.\n const storedFor = useRef<ConsentMode | null>(null);\n useEffect(() => {\n if (!mounted) return;\n const before = storedFor.current;\n storedFor.current = effectiveConsent;\n if (before === \"cookies\" && effectiveConsent !== \"cookies\") {\n deleteCookie(ID_COOKIE);\n deleteCookie(SESSION_COOKIE);\n forgetStoredDismissals();\n }\n if (effectiveConsent === \"off\") {\n queue.current.length = 0;\n page.session = undefined;\n }\n setVisitor({ mode: effectiveConsent, id: visitorId(effectiveConsent, !bootstrap) });\n }, [mounted, effectiveConsent, bootstrap]);\n\n // Experiments for this key. Without a key or when MRRocket is unreachable the page keeps control and carries on.\n useEffect(() => {\n if (!apiKey) warnOnce(\"No public key. Pass apiKey or set NEXT_PUBLIC_MRROCKET_KEY; nothing is tracked until then.\");\n if (bootstrapConfig || !apiKey) {\n setReady(true);\n return;\n }\n let live = true;\n fetch(`${host}/api/sdk/config?key=${encodeURIComponent(apiKey)}`)\n .then((r) => {\n if (!r.ok) warnOnce(`Config request to ${host || \"this origin\"} answered ${r.status}. Check the public key and NEXT_PUBLIC_MRROCKET_HOST.`);\n return r.ok ? r.json() : null;\n })\n .then((c: CockpitConfig | null) => {\n if (!live) return;\n if (c && Array.isArray(c.experiments)) setConfig(c);\n setReady(true);\n })\n .catch(() => {\n warnOnce(`Could not reach ${host || \"this origin\"}. Check NEXT_PUBLIC_MRROCKET_HOST. The page keeps working with control variants.`);\n if (live) setReady(true);\n });\n return () => {\n live = false;\n };\n }, [apiKey, host, bootstrapConfig]);\n\n const flush = useCallback(\n (beacon = false) => {\n if (!queue.current.length) return;\n if (!apiKey) {\n queue.current.length = 0;\n return;\n }\n const url = `${host}/api/sdk/events`;\n for (const body of bodies(apiKey, queue.current.splice(0), { v: SDK_VERSION, errors })) {\n const sent = beacon && typeof navigator !== \"undefined\" && \"sendBeacon\" in navigator && navigator.sendBeacon(url, body);\n if (!sent)\n fetch(url, { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body, keepalive: true })\n .then((r) => {\n if (!r.ok) warnOnce(`Events were rejected with ${r.status}. Check the public key (Settings › SDK) and NEXT_PUBLIC_MRROCKET_HOST.`);\n })\n .catch(() => warnOnce(`Could not reach ${host || \"this origin\"} to send events. Check NEXT_PUBLIC_MRROCKET_HOST.`));\n }\n },\n [apiKey, host, errors],\n );\n\n useEffect(() => {\n const timer = window.setInterval(() => flush(), 2000);\n const leave = () => flush(true);\n window.addEventListener(\"pagehide\", leave);\n return () => {\n window.clearInterval(timer);\n window.removeEventListener(\"pagehide\", leave);\n flush(true);\n };\n }, [flush]);\n\n // The latest values for callbacks that must not change identity, so effects that use them run once.\n const live = useRef({ resolved: false, tracking, distinctId, sendId, persist: false, identified: false });\n live.current = { resolved: visitor !== null, tracking, distinctId, sendId, persist: visitor?.mode === \"cookies\", identified: !!identified };\n\n const record = useCallback((event: string, props: Record<string, unknown> | undefined, ts = Date.now(), url?: string) => {\n const now = live.current;\n if (!now.resolved) {\n early.current.push(() => record(event, props, ts, url));\n return;\n }\n // Previews in Cockpit never count, and nothing is sent without consent.\n if (!now.tracking) return;\n const referral = carriesReferral(event) ? getReferral() : null;\n queue.current.push({\n event,\n props: referral ? { ...props, $referral: { code: referral.code, clickId: referral.clickId } } : props,\n distinctId: now.sendId,\n sessionId: touchSession(now.persist),\n ts,\n url: url ?? window.location.href,\n referrer: page.referrer,\n ...page.utm,\n screen: { w: window.screen.width, h: window.screen.height },\n });\n }, []);\n\n // The running recorder, if this visit records (see the recording effect below).\n const replayer = useRef<ReplayHandle | null>(null);\n // Error tracking, while `errors` is on (see the effect below).\n const errorer = useRef<ErrorCapture | null>(null);\n\n const track = useCallback(\n (event: string, props?: Record<string, unknown>) => {\n record(event, props);\n if (live.current.tracking) replayer.current?.event(event);\n },\n [record],\n );\n\n const identify = useCallback(\n (id: string, props?: Record<string, unknown>) => {\n const now = live.current;\n if (!now.resolved) {\n early.current.push(() => identify(id, props));\n return;\n }\n const previous = now.sendId;\n // From here on events carry this id in every mode; only \"cookies\" keeps it for the next visit.\n live.current = { ...now, distinctId: id, sendId: id, identified: true };\n record(\"$identify\", { ...props, id, ...(previous && previous !== id ? { previousId: previous } : {}) });\n replayer.current?.identify(id);\n if (now.persist) writeCookie(ID_COOKIE, id, YEAR_S);\n setIdentified(id);\n setUser({ id, ...(props as Omit<CockpitUser, \"id\">) });\n },\n [record],\n );\n\n // Replay what came in before the visitor was known: sent when tracking is on, dropped when it is not.\n useEffect(() => {\n if (!visitor) return;\n for (const run of early.current.splice(0)) run();\n }, [visitor]);\n\n // Affiliate referrals: read once the consent is known and the program's cookie days have loaded.\n const referralDays = config?.referral?.cookieDays ?? DEFAULT_REFERRAL_DAYS;\n const storeReferrals = attribution === \"always\" ? effectiveConsent !== \"off\" : attribution === \"consent\" && effectiveConsent === \"cookies\";\n const captured = useRef<string | null>(null);\n const storedReferral = useRef(false);\n useEffect(() => {\n if (!visitor || !ready || embedded) return;\n if (attribution === \"off\" || effectiveConsent === \"off\") {\n resetReferral();\n if (attribution === \"off\" || storedReferral.current) forgetStoredReferral();\n return;\n }\n const href = window.location.href;\n if (captured.current === href) return;\n captured.current = href;\n void captureReferral({\n search: window.location.search,\n param: referralParam,\n store: storeReferrals,\n days: referralDays,\n beacon: apiKey\n ? ({ code, clickId }) =>\n fetch(`${host}/api/sdk/affiliate/click`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ key: apiKey, code, clickId, url: href, referrer: document.referrer || undefined }),\n keepalive: true,\n })\n .then((r) => (r.ok ? (r.json() as Promise<{ clickId?: string | null }>) : null))\n .then((b) => b?.clickId ?? null)\n : undefined,\n });\n }, [visitor, ready, embedded, attribution, effectiveConsent, pathname, referralParam, storeReferrals, referralDays, apiKey, host]);\n\n // Consent given later keeps this page load's referral; consent withdrawn deletes what was stored.\n useEffect(() => {\n if (!mounted) return;\n const r = getReferral();\n if (storeReferrals && r) storeReferral(r, referralDays);\n else if (!storeReferrals && storedReferral.current) forgetStoredReferral();\n storedReferral.current = storeReferrals;\n }, [mounted, storeReferrals, referralDays]);\n\n // `tracking` is a dependency so that variants seen before consent are recorded once tracking starts.\n const expose = useCallback(\n (key: string, variant: VariantId) => {\n if (!tracking) return;\n const mark = `${pathname}|${key}`;\n if (exposed.current.has(mark)) return;\n exposed.current.add(mark);\n track(\"$exposure\", { experiment: key, variant });\n },\n [pathname, track, tracking],\n );\n\n // The referrer and campaign of each page; before the page view below, so it already carries them.\n useEffect(() => {\n enterPage();\n replayer.current?.page(pathname);\n errorer.current?.crumb(\"navigation\", pathname);\n }, [pathname]);\n\n // Errors of the page for Build › Errors; sent only while tracking (consent, not a preview), like events.\n useEffect(() => {\n if (!errors || !apiKey || embedded) return;\n const capture = startErrorCapture({\n host,\n apiKey,\n release,\n context: () => ({ send: live.current.tracking, distinctId: live.current.sendId, sessionId: page.session?.id }),\n warn: warnOnce,\n });\n errorer.current = capture;\n capture.crumb(\"navigation\", window.location.pathname);\n return () => {\n capture.stop();\n if (errorer.current === capture) errorer.current = null;\n };\n }, [errors, apiKey, host, release, embedded]);\n\n const captureError = useCallback((error: unknown) => errorer.current?.capture(error, \"manual\"), []);\n\n // One page view per route change while tracking, so a later setConsent() records the current page right away\n // (a ref keeps React's dev double-run from counting twice).\n useEffect(() => {\n if (!tracking || lastPage.current === pathname) return;\n lastPage.current = pathname;\n // Exposures are remembered per path (\"/pricing|test\"), so nothing needs clearing here.\n track(\"$pageview\", { path: pathname });\n }, [pathname, tracking, track]);\n\n // Clicks on anything marked `data-ck-goal=\"signup_started\"` count as that goal. Every click also feeds Heatmaps\n // (`$click`, `$rageclick` on the third quick click on one spot) and, when this visit records, the recording.\n const rage = useRef(new RageDetector());\n useEffect(() => {\n const onClick = (e: MouseEvent) => {\n const el = (e.target as HTMLElement | null)?.closest<HTMLElement>(\"[data-ck-goal]\");\n if (el?.dataset.ckGoal) track(el.dataset.ckGoal, { text: el.textContent?.trim().slice(0, 80) });\n if (!live.current.tracking) return;\n const c = clickInfo(e);\n if (!c) return;\n const raged = rage.current.push(c.x, c.y, Date.now());\n if (c.label || c.selector) errorer.current?.crumb(\"click\", c.label || c.selector);\n replayer.current?.click(c, raged);\n if (!heatmaps) return;\n const props = { x: c.x, y: c.y, vw: c.vw, vh: c.vh, docH: c.docH, selector: c.selector, label: c.label, dead: !c.interactive };\n record(\"$click\", props);\n if (raged) record(\"$rageclick\", props);\n };\n document.addEventListener(\"click\", onClick);\n return () => document.removeEventListener(\"click\", onClick);\n }, [track, record, heatmaps]);\n\n // Scroll depth for Heatmaps: the deepest point seen on each page, sent when the page changes or hides.\n const depth = useRef<{ url: string; max: number; docH: number } | null>(null);\n useEffect(() => {\n if (!heatmaps || !tracking || embedded) return;\n const send = () => {\n const d = depth.current;\n if (d && d.max > 0) record(\"$scroll\", { depth: d.max, docH: d.docH, vw: window.innerWidth, vh: window.innerHeight }, Date.now(), d.url);\n depth.current = null;\n };\n const measure = () => {\n const doc = document.documentElement;\n const docH = Math.max(doc.scrollHeight, document.body?.scrollHeight ?? 0);\n const max = seenDepth();\n const href = window.location.href;\n if (depth.current?.url !== href) depth.current = { url: href, max, docH };\n else if (max > depth.current.max) depth.current = { url: href, max, docH };\n };\n measure();\n let frame = 0;\n const onScroll = () => {\n if (frame) return;\n frame = window.requestAnimationFrame(() => {\n frame = 0;\n measure();\n });\n };\n const onHide = () => {\n send();\n flush(true);\n };\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n window.addEventListener(\"pagehide\", onHide);\n return () => {\n window.removeEventListener(\"scroll\", onScroll);\n window.removeEventListener(\"pagehide\", onHide);\n window.cancelAnimationFrame(frame);\n send();\n };\n }, [heatmaps, tracking, embedded, pathname, record, flush]);\n\n // Session recording: only with the visitor's replay consent, while tracking, and when the product records. The\n // recorder and rrweb are loaded here, lazily; withdrawing consent stops it and drops what was not sent.\n const replayConfig = config?.replay;\n const recording = tracking && replayConsent && !!replayConfig && !!apiKey;\n useEffect(() => {\n if (!recording || !replayConfig) return;\n let active = true;\n let handle: ReplayHandle | null = null;\n // `typeof window` is a build-time constant in Next.js: on the server this folds to null and the import (with\n // rrweb) never enters the server bundle.\n void (typeof window === \"undefined\" ? Promise.resolve(null) : import(\"./replay\"))\n .then((m) =>\n m?.startReplay({\n host,\n apiKey,\n sessionId: touchSession(live.current.persist),\n distinctId: live.current.sendId,\n identified: live.current.identified,\n sampleRate: replayConfig.sampleRate,\n warn: warnOnce,\n }),\n )\n .then((h) => {\n if (!active) h?.stop(true);\n else replayer.current = handle = h ?? null;\n })\n .catch(() => warnOnce(\"Session recording could not start (the recorder did not load).\"));\n return () => {\n active = false;\n handle?.stop(true);\n if (replayer.current === handle) replayer.current = null;\n };\n }, [recording, replayConfig, host, apiKey]);\n\n const texts = useMemo(() => textChanges(config, distinctId, preview), [config, distinctId, preview]);\n\n const value = useMemo<CockpitContext>(\n () => ({ apiKey, host, user, ready, distinctId, config, preview, embedded, consent, effectiveConsent, replayConsent, setConsent, texts, track, identify, expose, captureError }),\n [apiKey, host, user, ready, distinctId, config, preview, embedded, consent, effectiveConsent, replayConsent, setConsent, texts, track, identify, expose, captureError],\n );\n return (\n <Ctx.Provider value={value}>\n {children}\n {surveys ? <Survey /> : null}\n </Ctx.Provider>\n );\n};\n\n/**\n * `track`, `identify`, `captureError`, the consent mode, the replay consent and their setter, the visitor id and whether\n * the experiments and flags have loaded. `setConsent(\"cookies\")` sets the mode, `setConsent({ replay: true })` the\n * replay consent.\n */\nexport const useCockpit = () => {\n const { track, identify, captureError, distinctId, ready, embedded, consent, replayConsent, setConsent } = useCtx();\n return { track, identify, captureError, distinctId, ready, embedded, consent, replay: replayConsent, setConsent };\n};\n\n/**\n * A feature flag from MRRocket › Build › Feature flags: true while it is on for this visitor (its audience and rollout\n * in Production), false while it is off, unknown, or the flags have not loaded yet (`useCockpit().ready`). The person's\n * properties from `identify(id, props)` feed the audience conditions (\"Email\", \"Plan\" …).\n *\n * const newCheckout = useFlag(\"new-checkout\");\n */\nexport const useFlag = (key: string): boolean => useFlagValue(key) !== false;\n\n/** The variant of a multivariate flag for this visitor (\"control\", \"test\" …), or null while the flag is off. */\nexport const useFlagVariant = (key: string): string | null => {\n const v = useFlagValue(key);\n return typeof v === \"string\" ? v : null;\n};\n\nconst useFlagValue = (key: string) => {\n const { config, distinctId, user } = useCtx();\n return useMemo(() => flagFor(key, config, distinctId, { ...user }), [key, config, distinctId, user]);\n};\n\n/** The variant this visitor sees, for example `\"control\"` or `\"a\"`. Records the exposure. */\nexport const useVariant = (key: string): VariantId => {\n const { config, distinctId, preview, expose } = useCtx();\n const variant = resolveVariant(key, config, distinctId, preview);\n useEffect(() => {\n if (config || preview[key]) expose(key, variant);\n }, [config, preview, key, variant, expose]);\n return variant;\n};\n\n/** Renders the element for the visitor's variant; control when the variant has no element. */\nexport const Experiment = ({ name, variants }: { name: string; variants: Record<VariantId, ReactNode> & { control: ReactNode } }) => {\n const variant = useVariant(name);\n return <>{variants[variant] ?? variants.control}</>;\n};\n\n/**\n * A text that experiments may change without a deploy: Cockpit sends the new copy for `id` and this renders it.\n * Write your default as the children.\n */\nexport const Text = ({ id, children }: { id: string; children: string }) => {\n const { texts } = useCtx();\n return <>{texts[id] ?? children}</>;\n};\n"],"mappings":"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","names":["live"]}
|
package/dist/context.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface CockpitContext {
|
|
|
28
28
|
identify: (id: string, props?: Record<string, unknown>) => void;
|
|
29
29
|
/** Records that the visitor saw a variant, once per experiment and page view. */
|
|
30
30
|
expose: (key: string, variant: VariantId) => void;
|
|
31
|
+
/** Reports an error you caught to Build › Errors (while error tracking is on). */
|
|
32
|
+
captureError: (error: unknown) => void;
|
|
31
33
|
}
|
|
32
34
|
export declare const Ctx: import("react").Context<CockpitContext | null>;
|
|
33
35
|
export declare const useCtx: () => CockpitContext;
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type { CockpitConfig, ConsentChoice, ConsentMode, VariantId } from \"./core\";\n\n/** Who the visitor is, from identify(). Sent along with feedback and survey answers. */\nexport interface CockpitUser {\n id: string;\n name?: string;\n email?: string;\n [key: string]: unknown;\n}\n\nexport interface CockpitContext {\n apiKey: string;\n host: string;\n user?: CockpitUser;\n ready: boolean;\n distinctId: string;\n config: CockpitConfig | null;\n preview: Record<string, VariantId>;\n /** Embedded in Cockpit as a preview: nothing is tracked. */\n embedded: boolean;\n /** The consent mode the site set, with the `consent` prop or setConsent(). */\n consent: ConsentMode;\n /** What applies right now: \"off\" when the browser sends Do Not Track and the provider respects it. */\n effectiveConsent: ConsentMode;\n /** The visitor's consent to session recordings (`replay` prop or `setConsent({ replay })`). */\n replayConsent: boolean;\n setConsent: (choice: ConsentChoice) => void;\n texts: Record<string, string>;\n track: (event: string, props?: Record<string, unknown>) => void;\n identify: (id: string, props?: Record<string, unknown>) => void;\n /** Records that the visitor saw a variant, once per experiment and page view. */\n expose: (key: string, variant: VariantId) => void;\n}\n\nexport const Ctx = createContext<CockpitContext | null>(null);\n\nexport const useCtx = () => {\n const c = useContext(Ctx);\n if (!c) throw new Error(\"Cockpit: wrap your app in <CockpitProvider apiKey=…>.\");\n return c;\n};\n"],"mappings":";;;AAEA,SAAS,eAAe,kBAAkB;
|
|
1
|
+
{"version":3,"sources":["../src/context.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type { CockpitConfig, ConsentChoice, ConsentMode, VariantId } from \"./core\";\n\n/** Who the visitor is, from identify(). Sent along with feedback and survey answers. */\nexport interface CockpitUser {\n id: string;\n name?: string;\n email?: string;\n [key: string]: unknown;\n}\n\nexport interface CockpitContext {\n apiKey: string;\n host: string;\n user?: CockpitUser;\n ready: boolean;\n distinctId: string;\n config: CockpitConfig | null;\n preview: Record<string, VariantId>;\n /** Embedded in Cockpit as a preview: nothing is tracked. */\n embedded: boolean;\n /** The consent mode the site set, with the `consent` prop or setConsent(). */\n consent: ConsentMode;\n /** What applies right now: \"off\" when the browser sends Do Not Track and the provider respects it. */\n effectiveConsent: ConsentMode;\n /** The visitor's consent to session recordings (`replay` prop or `setConsent({ replay })`). */\n replayConsent: boolean;\n setConsent: (choice: ConsentChoice) => void;\n texts: Record<string, string>;\n track: (event: string, props?: Record<string, unknown>) => void;\n identify: (id: string, props?: Record<string, unknown>) => void;\n /** Records that the visitor saw a variant, once per experiment and page view. */\n expose: (key: string, variant: VariantId) => void;\n /** Reports an error you caught to Build › Errors (while error tracking is on). */\n captureError: (error: unknown) => void;\n}\n\nexport const Ctx = createContext<CockpitContext | null>(null);\n\nexport const useCtx = () => {\n const c = useContext(Ctx);\n if (!c) throw new Error(\"Cockpit: wrap your app in <CockpitProvider apiKey=…>.\");\n return c;\n};\n"],"mappings":";;;AAEA,SAAS,eAAe,kBAAkB;AAqCnC,IAAM,MAAM,cAAqC,IAAI;AAErD,IAAM,SAAS,MAAM;AACxB,QAAM,IAAI,WAAW,GAAG;AACxB,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,4DAAuD;AAC/E,SAAO;AACX;","names":[]}
|
package/dist/core.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* No React, no browser APIs, so the server helper, the proxy helper and the client share it.
|
|
4
4
|
*/
|
|
5
5
|
export type VariantId = string;
|
|
6
|
+
/** This build of the SDK. Sent with the config request so MRRocket knows what the site runs (error tracking, flags). */
|
|
7
|
+
export declare const SDK_VERSION = "0.3.0";
|
|
6
8
|
/** One variant of an experiment. `changes` replaces texts that the page marks with `<Text id>`. */
|
|
7
9
|
export interface VariantConfig {
|
|
8
10
|
id: VariantId;
|
|
@@ -29,6 +31,66 @@ export interface CockpitConfig {
|
|
|
29
31
|
};
|
|
30
32
|
/** Session recordings: present only while the product records (Cockpit › Sessions). See `ReplayConfig`. */
|
|
31
33
|
replay?: ReplayConfig;
|
|
34
|
+
/** Feature flags of the product in Production (MRRocket › Build › Feature flags), evaluated by `useFlag` and `getFlag`. */
|
|
35
|
+
flags?: FlagConfig[];
|
|
36
|
+
}
|
|
37
|
+
/** One condition of a flag's audience, on a property of the person (`identify(id, props)`), the id or the page. */
|
|
38
|
+
export interface FlagCondition {
|
|
39
|
+
/** "Email", "Plan", "Country", "id", "path" … Matched against the person's properties without regard to case. */
|
|
40
|
+
property: string;
|
|
41
|
+
op: "is" | "is not" | "in" | "not in" | "≥" | "in cohort";
|
|
42
|
+
values: string[];
|
|
43
|
+
}
|
|
44
|
+
/** Users who match every condition, of whom `rollout` percent get the flag. No conditions: everyone. */
|
|
45
|
+
export interface FlagConditionSet {
|
|
46
|
+
conditions: FlagCondition[];
|
|
47
|
+
/** 0 to 100. */
|
|
48
|
+
rollout: number;
|
|
49
|
+
}
|
|
50
|
+
export interface FlagConfig {
|
|
51
|
+
key: string;
|
|
52
|
+
/** Off: everyone gets the off side. */
|
|
53
|
+
on: boolean;
|
|
54
|
+
/** The first set a person matches decides; none matched: off. */
|
|
55
|
+
sets: FlagConditionSet[];
|
|
56
|
+
/** Multivariate flags: the variants and their split in percent (control included). Missing for an on/off flag. */
|
|
57
|
+
variants?: {
|
|
58
|
+
key: string;
|
|
59
|
+
split: number;
|
|
60
|
+
payload?: string;
|
|
61
|
+
}[];
|
|
62
|
+
}
|
|
63
|
+
/** What a person is for flag conditions: their properties from identify(), their id and the page they are on. */
|
|
64
|
+
export type FlagPerson = Record<string, unknown>;
|
|
65
|
+
/**
|
|
66
|
+
* The flag for one person: `false` (off), `true` (on, an on/off flag) or the variant's key (a multivariate flag). The
|
|
67
|
+
* bucket is a stable hash of flag and person, so a person keeps their side as the rollout grows.
|
|
68
|
+
*/
|
|
69
|
+
export declare const evaluateFlag: (flag: FlagConfig, distinctId: string, person?: FlagPerson) => boolean | string;
|
|
70
|
+
/** A flag of the config for this person; an unknown flag (or no config) is off. */
|
|
71
|
+
export declare const flagFor: (key: string, config: CockpitConfig | null, distinctId: string, person?: FlagPerson) => string | boolean;
|
|
72
|
+
/** One JavaScript error as it goes over the wire to `POST /api/sdk/errors`. */
|
|
73
|
+
export interface ErrorReport {
|
|
74
|
+
/** "TypeError", "Error" … */
|
|
75
|
+
type: string;
|
|
76
|
+
message: string;
|
|
77
|
+
/** The raw stack, at most 8 KB. MRRocket parses it into frames. */
|
|
78
|
+
stack?: string;
|
|
79
|
+
/** "onerror" (uncaught), "onunhandledrejection" (a rejected promise nobody caught) or "manual" (captureError). */
|
|
80
|
+
mechanism: "onerror" | "onunhandledrejection" | "manual";
|
|
81
|
+
handled: boolean;
|
|
82
|
+
url: string;
|
|
83
|
+
ts: number;
|
|
84
|
+
/** What happened just before: pages and clicks, newest last, at most 20. */
|
|
85
|
+
crumbs?: {
|
|
86
|
+
ts: number;
|
|
87
|
+
type: "navigation" | "click" | "lifecycle";
|
|
88
|
+
message: string;
|
|
89
|
+
}[];
|
|
90
|
+
/** Your release (a version or commit), from the provider's `release` prop. */
|
|
91
|
+
release?: string;
|
|
92
|
+
distinctId?: string;
|
|
93
|
+
sessionId?: string;
|
|
32
94
|
}
|
|
33
95
|
/**
|
|
34
96
|
* Session recordings of a product, from the config. Recording also needs the visitor's own replay consent
|
|
@@ -102,6 +164,11 @@ export declare const SESSION_TIMEOUT_MS: number;
|
|
|
102
164
|
export declare const readUtm: (search: string) => Partial<Record<UtmKey, string>>;
|
|
103
165
|
/** FNV-1a, 32 bit, mapped to [0, 1). Stable across server and browser. */
|
|
104
166
|
export declare const hashUnit: (input: string) => number;
|
|
167
|
+
/**
|
|
168
|
+
* FNV-1a with murmur3's finalizer, mapped to [0, 1): ids that differ only at the end ("user-1", "user-2") still spread
|
|
169
|
+
* evenly, so a 30 % rollout reaches about 30 % of people. Flags only; experiments keep `hashUnit` so nobody switches.
|
|
170
|
+
*/
|
|
171
|
+
export declare const flagUnit: (input: string) => number;
|
|
105
172
|
/** The variant a visitor sees: a shipped winner, control while the experiment does not run, else a stable bucket by visitor. */
|
|
106
173
|
export declare const assign: (experiment: ExperimentConfig, distinctId: string) => VariantId;
|
|
107
174
|
/** `"a:b,c:d"` → `{ a: "b", c: "d" }`. Ignores anything malformed. */
|
package/dist/core.js
CHANGED
|
@@ -1,4 +1,46 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
|
+
var SDK_VERSION = "0.3.0";
|
|
3
|
+
var propOf = (person, name) => {
|
|
4
|
+
const want = name.trim().toLowerCase();
|
|
5
|
+
for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;
|
|
6
|
+
return void 0;
|
|
7
|
+
};
|
|
8
|
+
var same = (actual, value) => {
|
|
9
|
+
const a = actual.trim().toLowerCase();
|
|
10
|
+
const v = value.trim().toLowerCase();
|
|
11
|
+
return v.startsWith("@") ? a.endsWith(v) : a === v;
|
|
12
|
+
};
|
|
13
|
+
var matchesCondition = (c, person) => {
|
|
14
|
+
if (c.op === "in cohort") return false;
|
|
15
|
+
const raw = propOf(person, c.property);
|
|
16
|
+
const actual = raw === void 0 || raw === null ? "" : String(raw);
|
|
17
|
+
if (c.op === "\u2265") {
|
|
18
|
+
const a = Number(actual);
|
|
19
|
+
const b = Number(c.values[0]);
|
|
20
|
+
return actual !== "" && Number.isFinite(a) && Number.isFinite(b) && a >= b;
|
|
21
|
+
}
|
|
22
|
+
const hit = actual !== "" && c.values.some((v) => same(actual, v));
|
|
23
|
+
return c.op === "is" || c.op === "in" ? hit : !hit;
|
|
24
|
+
};
|
|
25
|
+
var evaluateFlag = (flag, distinctId, person = {}) => {
|
|
26
|
+
if (!flag.on) return false;
|
|
27
|
+
const subject = { id: distinctId, ...person };
|
|
28
|
+
const set = flag.sets.length ? flag.sets.find((s) => s.conditions.every((c) => matchesCondition(c, subject))) : { conditions: [], rollout: 100 };
|
|
29
|
+
if (!set) return false;
|
|
30
|
+
if (flagUnit(`${flag.key}:rollout:${distinctId}`) * 100 >= set.rollout) return false;
|
|
31
|
+
if (!flag.variants?.length) return true;
|
|
32
|
+
const u = flagUnit(`${flag.key}:variant:${distinctId}`) * 100;
|
|
33
|
+
let acc = 0;
|
|
34
|
+
for (const v of flag.variants) {
|
|
35
|
+
acc += v.split;
|
|
36
|
+
if (u < acc) return v.key;
|
|
37
|
+
}
|
|
38
|
+
return flag.variants[flag.variants.length - 1].key;
|
|
39
|
+
};
|
|
40
|
+
var flagFor = (key, config, distinctId, person = {}) => {
|
|
41
|
+
const flag = config?.flags?.find((f) => f.key === key);
|
|
42
|
+
return flag ? evaluateFlag(flag, distinctId, person) : false;
|
|
43
|
+
};
|
|
2
44
|
var UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
|
3
45
|
var PREVIEW_PARAM = "ck_preview";
|
|
4
46
|
var EMBED_PARAM = "ck_embed";
|
|
@@ -22,6 +64,19 @@ var hashUnit = (input) => {
|
|
|
22
64
|
}
|
|
23
65
|
return (h >>> 0) / 4294967296;
|
|
24
66
|
};
|
|
67
|
+
var flagUnit = (input) => {
|
|
68
|
+
let h = 2166136261;
|
|
69
|
+
for (let i = 0; i < input.length; i++) {
|
|
70
|
+
h ^= input.charCodeAt(i);
|
|
71
|
+
h = Math.imul(h, 16777619);
|
|
72
|
+
}
|
|
73
|
+
h ^= h >>> 16;
|
|
74
|
+
h = Math.imul(h, 2246822507);
|
|
75
|
+
h ^= h >>> 13;
|
|
76
|
+
h = Math.imul(h, 3266489909);
|
|
77
|
+
h ^= h >>> 16;
|
|
78
|
+
return (h >>> 0) / 4294967296;
|
|
79
|
+
};
|
|
25
80
|
var assign = (experiment, distinctId) => {
|
|
26
81
|
const control = experiment.variants[0]?.id ?? "control";
|
|
27
82
|
if (experiment.winner) return experiment.winner;
|
|
@@ -86,12 +141,16 @@ export {
|
|
|
86
141
|
EMBED_PARAM,
|
|
87
142
|
ID_COOKIE,
|
|
88
143
|
PREVIEW_PARAM,
|
|
144
|
+
SDK_VERSION,
|
|
89
145
|
SESSION_COOKIE,
|
|
90
146
|
SESSION_TIMEOUT_MS,
|
|
91
147
|
UTM_KEYS,
|
|
92
148
|
assign,
|
|
93
149
|
envHost,
|
|
94
150
|
envKey,
|
|
151
|
+
evaluateFlag,
|
|
152
|
+
flagFor,
|
|
153
|
+
flagUnit,
|
|
95
154
|
hashUnit,
|
|
96
155
|
newDistinctId,
|
|
97
156
|
parsePreview,
|
package/dist/core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core.ts"],"sourcesContent":["/**\n * The part of the SDK that runs anywhere: experiment config, variant assignment, preview links and event shapes.\n * No React, no browser APIs, so the server helper, the proxy helper and the client share it.\n */\n\nexport type VariantId = string;\n\n/** One variant of an experiment. `changes` replaces texts that the page marks with `<Text id>`. */\nexport interface VariantConfig {\n id: VariantId;\n /** Share of traffic, 0 to 1. The weights of an experiment add up to 1. */\n weight: number;\n changes?: Record<string, string>;\n}\n\nexport interface ExperimentConfig {\n /** The experiment's key, the same as in Cockpit. */\n key: string;\n status: \"running\" | \"draft\" | \"stopped\";\n /** Control first. */\n variants: VariantConfig[];\n /** A winner that shipped: everyone gets it, and nobody is assigned anymore. */\n winner?: VariantId;\n}\n\nexport interface CockpitConfig {\n /** The product the public key belongs to. */\n project: string;\n experiments: ExperimentConfig[];\n /** The product's affiliate program: how many days a partner's referral is kept. Missing without a program. */\n referral?: { cookieDays: number };\n /** Session recordings: present only while the product records (Cockpit › Sessions). See `ReplayConfig`. */\n replay?: ReplayConfig;\n}\n\n/**\n * Session recordings of a product, from the config. Recording also needs the visitor's own replay consent\n * (`<CockpitProvider replay>` or `setConsent({ replay: true })`); without it nothing is recorded or buffered.\n */\nexport interface ReplayConfig {\n /**\n * Share of sessions uploaded from their start, 0 to 1 (default 0.1). The others keep only the last minute in\n * memory and upload it when something goes wrong: a JS error, a rage click, a `purchase…` or `checkout…` event.\n */\n sampleRate: number;\n}\n\n/**\n * What the SDK may send and store.\n * - \"cookieless\" (default): no cookie, no localStorage. Events go without a visitor id; the server counts visitors\n * with a hash of key, date, IP and user agent that changes every day.\n * - \"cookies\": a persistent `ck_id` cookie identifies the visitor across visits, `ck_sid` holds the session.\n * - \"off\": nothing is sent and nothing is stored.\n */\nexport type ConsentMode = \"cookieless\" | \"cookies\" | \"off\";\n\n/**\n * What `setConsent` takes: a mode as before, or the parts to change. `replay` is the separate consent for session\n * recordings (default false); it applies only while analytics is not \"off\".\n *\n * setConsent(\"cookies\");\n * setConsent({ replay: true });\n * setConsent({ analytics: \"cookieless\", replay: false });\n */\nexport type ConsentChoice = ConsentMode | { analytics?: ConsentMode; replay?: boolean };\n\n/** The campaign parameters the SDK reads from the landing URL. */\nexport const UTM_KEYS = [\"utm_source\", \"utm_medium\", \"utm_campaign\", \"utm_term\", \"utm_content\"] as const;\nexport type UtmKey = (typeof UTM_KEYS)[number];\n\n/** One event as it goes over the wire to `POST /api/sdk/events`. */\nexport interface CockpitEvent {\n event: string;\n /** The visitor. Missing in \"cookieless\" mode until identify(): the server then uses its daily visitor hash. */\n distinctId?: string;\n /** Ends after 30 minutes without an event. */\n sessionId?: string;\n props?: Record<string, unknown>;\n /** Milliseconds since the epoch. */\n ts: number;\n url?: string;\n /** `document.referrer` on the first page of a page load, the previous in-app URL after client-side navigation. */\n referrer?: string;\n utm_source?: string;\n utm_medium?: string;\n utm_campaign?: string;\n utm_term?: string;\n utm_content?: string;\n /** The screen size in CSS pixels. */\n screen?: { w: number; h: number };\n}\n\n/** The query parameter that forces variants, for previews in Cockpit: `?ck_preview=pricing-annual:a,home-hero:control`. */\nexport const PREVIEW_PARAM = \"ck_preview\";\n/** Set when Cockpit embeds the page: nothing is tracked, so previews never count as visits. */\nexport const EMBED_PARAM = \"ck_embed\";\n/** The cookie that keeps a visitor in the same variant across visits. Only written in \"cookies\" mode. */\nexport const ID_COOKIE = \"ck_id\";\n/** The cookie that holds the session id in \"cookies\" mode. */\nexport const SESSION_COOKIE = \"ck_sid\";\n/** A session ends after this long without an event. */\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n\n/** The UTM parameters of a query string, like `\"?utm_source=x\"`. Empty values are left out. */\nexport const readUtm = (search: string): Partial<Record<UtmKey, string>> => {\n const params = new URLSearchParams(search);\n const out: Partial<Record<UtmKey, string>> = {};\n for (const key of UTM_KEYS) {\n const value = params.get(key)?.trim();\n if (value) out[key] = value.slice(0, 150);\n }\n return out;\n};\n\n/** FNV-1a, 32 bit, mapped to [0, 1). Stable across server and browser. */\nexport const hashUnit = (input: string) => {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0) / 4294967296;\n};\n\n/** The variant a visitor sees: a shipped winner, control while the experiment does not run, else a stable bucket by visitor. */\nexport const assign = (experiment: ExperimentConfig, distinctId: string): VariantId => {\n const control = experiment.variants[0]?.id ?? \"control\";\n if (experiment.winner) return experiment.winner;\n if (experiment.status !== \"running\") return control;\n const u = hashUnit(`${experiment.key}:${distinctId}`);\n let acc = 0;\n for (const v of experiment.variants) {\n acc += v.weight;\n if (u < acc) return v.id;\n }\n return experiment.variants[experiment.variants.length - 1]?.id ?? control;\n};\n\n/** `\"a:b,c:d\"` → `{ a: \"b\", c: \"d\" }`. Ignores anything malformed. */\nexport const parsePreview = (value: string | string[] | null | undefined): Record<string, VariantId> => {\n const raw = Array.isArray(value) ? value[0] : value;\n if (!raw) return {};\n const out: Record<string, VariantId> = {};\n for (const pair of raw.split(\",\")) {\n const [key, variant] = pair.split(\":\");\n if (key && variant) out[key.trim()] = variant.trim();\n }\n return out;\n};\n\n/** A link that shows one variant of a page, for example in Cockpit's preview frames. */\nexport const previewHref = (url: string, forced: Record<string, VariantId>, embed = true) => {\n const [path, query = \"\"] = url.split(\"?\");\n const params = new URLSearchParams(query);\n const value = Object.entries(forced)\n .map(([k, v]) => `${k}:${v}`)\n .join(\",\");\n if (value) params.set(PREVIEW_PARAM, value);\n if (embed) params.set(EMBED_PARAM, \"1\");\n return `${path}?${params.toString()}`;\n};\n\n/** The variant for one experiment, from a forced preview first, then the config. Unknown experiments get control. */\nexport const resolveVariant = (key: string, config: CockpitConfig | null, distinctId: string, preview: Record<string, VariantId>): VariantId => {\n if (preview[key]) return preview[key];\n const experiment = config?.experiments.find((e) => e.key === key);\n return experiment ? assign(experiment, distinctId) : \"control\";\n};\n\n/**\n * Every text change that applies to this visitor, merged in config order. Control keeps the page's own words,\n * so only a non-control variant (assigned, forced in a preview, or a shipped winner) changes text.\n */\nexport const textChanges = (config: CockpitConfig | null, distinctId: string, preview: Record<string, VariantId>) => {\n const out: Record<string, string> = {};\n for (const experiment of config?.experiments ?? []) {\n const id = resolveVariant(experiment.key, config, distinctId, preview);\n if (id === (experiment.variants[0]?.id ?? \"control\")) continue;\n Object.assign(out, experiment.variants.find((v) => v.id === id)?.changes ?? {});\n }\n return out;\n};\n\n/**\n * The MRRocket API host from `NEXT_PUBLIC_MRROCKET_HOST`. Written as a literal property access on purpose: Next.js\n * inlines `process.env.NEXT_PUBLIC_*` at build time, in the browser bundle and in the server bundle (also on\n * Cloudflare Workers), and only replaces the literal form. Without a bundler (or a `process`) it is undefined.\n */\nexport const envHost = (): string | undefined => {\n try {\n return process.env.NEXT_PUBLIC_MRROCKET_HOST || undefined;\n } catch {\n return undefined;\n }\n};\n\n/** The public key from `NEXT_PUBLIC_MRROCKET_KEY`, the default for the `apiKey` prop. Same literal-access rule as `envHost`. */\nexport const envKey = (): string | undefined => {\n try {\n return process.env.NEXT_PUBLIC_MRROCKET_KEY || undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Where the API lives: the `host` prop, else `NEXT_PUBLIC_MRROCKET_HOST`, else \"\" (the page's own origin, which is how\n * MRRocket's own demo pages call it). Without a trailing slash.\n */\nexport const resolveHost = (host?: string) => (host ?? envHost() ?? \"\").trim().replace(/\\/+$/, \"\");\n\n/** A random visitor id. */\nexport const newDistinctId = () =>\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto ? crypto.randomUUID() : `ck_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core.ts"],"sourcesContent":["/**\n * The part of the SDK that runs anywhere: experiment config, variant assignment, preview links and event shapes.\n * No React, no browser APIs, so the server helper, the proxy helper and the client share it.\n */\n\nexport type VariantId = string;\n\n/** This build of the SDK. Sent with the config request so MRRocket knows what the site runs (error tracking, flags). */\nexport const SDK_VERSION = \"0.3.0\";\n\n/** One variant of an experiment. `changes` replaces texts that the page marks with `<Text id>`. */\nexport interface VariantConfig {\n id: VariantId;\n /** Share of traffic, 0 to 1. The weights of an experiment add up to 1. */\n weight: number;\n changes?: Record<string, string>;\n}\n\nexport interface ExperimentConfig {\n /** The experiment's key, the same as in Cockpit. */\n key: string;\n status: \"running\" | \"draft\" | \"stopped\";\n /** Control first. */\n variants: VariantConfig[];\n /** A winner that shipped: everyone gets it, and nobody is assigned anymore. */\n winner?: VariantId;\n}\n\nexport interface CockpitConfig {\n /** The product the public key belongs to. */\n project: string;\n experiments: ExperimentConfig[];\n /** The product's affiliate program: how many days a partner's referral is kept. Missing without a program. */\n referral?: { cookieDays: number };\n /** Session recordings: present only while the product records (Cockpit › Sessions). See `ReplayConfig`. */\n replay?: ReplayConfig;\n /** Feature flags of the product in Production (MRRocket › Build › Feature flags), evaluated by `useFlag` and `getFlag`. */\n flags?: FlagConfig[];\n}\n\n/** One condition of a flag's audience, on a property of the person (`identify(id, props)`), the id or the page. */\nexport interface FlagCondition {\n /** \"Email\", \"Plan\", \"Country\", \"id\", \"path\" … Matched against the person's properties without regard to case. */\n property: string;\n op: \"is\" | \"is not\" | \"in\" | \"not in\" | \"≥\" | \"in cohort\";\n values: string[];\n}\n\n/** Users who match every condition, of whom `rollout` percent get the flag. No conditions: everyone. */\nexport interface FlagConditionSet {\n conditions: FlagCondition[];\n /** 0 to 100. */\n rollout: number;\n}\n\nexport interface FlagConfig {\n key: string;\n /** Off: everyone gets the off side. */\n on: boolean;\n /** The first set a person matches decides; none matched: off. */\n sets: FlagConditionSet[];\n /** Multivariate flags: the variants and their split in percent (control included). Missing for an on/off flag. */\n variants?: { key: string; split: number; payload?: string }[];\n}\n\n/** What a person is for flag conditions: their properties from identify(), their id and the page they are on. */\nexport type FlagPerson = Record<string, unknown>;\n\nconst propOf = (person: FlagPerson, name: string) => {\n const want = name.trim().toLowerCase();\n for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;\n return undefined;\n};\n\n/** `\"@acme.com\"` matches every address at that domain; anything else matches the whole value, without regard to case. */\nconst same = (actual: string, value: string) => {\n const a = actual.trim().toLowerCase();\n const v = value.trim().toLowerCase();\n return v.startsWith(\"@\") ? a.endsWith(v) : a === v;\n};\n\nconst matchesCondition = (c: FlagCondition, person: FlagPerson) => {\n // Cohorts live in MRRocket; a browser cannot know them, so a cohort condition never matches here.\n if (c.op === \"in cohort\") return false;\n const raw = propOf(person, c.property);\n const actual = raw === undefined || raw === null ? \"\" : String(raw);\n if (c.op === \"≥\") {\n const a = Number(actual);\n const b = Number(c.values[0]);\n return actual !== \"\" && Number.isFinite(a) && Number.isFinite(b) && a >= b;\n }\n const hit = actual !== \"\" && c.values.some((v) => same(actual, v));\n return c.op === \"is\" || c.op === \"in\" ? hit : !hit;\n};\n\n/**\n * The flag for one person: `false` (off), `true` (on, an on/off flag) or the variant's key (a multivariate flag). The\n * bucket is a stable hash of flag and person, so a person keeps their side as the rollout grows.\n */\nexport const evaluateFlag = (flag: FlagConfig, distinctId: string, person: FlagPerson = {}): boolean | string => {\n if (!flag.on) return false;\n const subject = { id: distinctId, ...person };\n const set = flag.sets.length ? flag.sets.find((s) => s.conditions.every((c) => matchesCondition(c, subject))) : { conditions: [], rollout: 100 };\n if (!set) return false;\n if (flagUnit(`${flag.key}:rollout:${distinctId}`) * 100 >= set.rollout) return false;\n if (!flag.variants?.length) return true;\n const u = flagUnit(`${flag.key}:variant:${distinctId}`) * 100;\n let acc = 0;\n for (const v of flag.variants) {\n acc += v.split;\n if (u < acc) return v.key;\n }\n return flag.variants[flag.variants.length - 1].key;\n};\n\n/** A flag of the config for this person; an unknown flag (or no config) is off. */\nexport const flagFor = (key: string, config: CockpitConfig | null, distinctId: string, person: FlagPerson = {}) => {\n const flag = config?.flags?.find((f) => f.key === key);\n return flag ? evaluateFlag(flag, distinctId, person) : false;\n};\n\n/** One JavaScript error as it goes over the wire to `POST /api/sdk/errors`. */\nexport interface ErrorReport {\n /** \"TypeError\", \"Error\" … */\n type: string;\n message: string;\n /** The raw stack, at most 8 KB. MRRocket parses it into frames. */\n stack?: string;\n /** \"onerror\" (uncaught), \"onunhandledrejection\" (a rejected promise nobody caught) or \"manual\" (captureError). */\n mechanism: \"onerror\" | \"onunhandledrejection\" | \"manual\";\n handled: boolean;\n url: string;\n ts: number;\n /** What happened just before: pages and clicks, newest last, at most 20. */\n crumbs?: { ts: number; type: \"navigation\" | \"click\" | \"lifecycle\"; message: string }[];\n /** Your release (a version or commit), from the provider's `release` prop. */\n release?: string;\n distinctId?: string;\n sessionId?: string;\n}\n\n/**\n * Session recordings of a product, from the config. Recording also needs the visitor's own replay consent\n * (`<CockpitProvider replay>` or `setConsent({ replay: true })`); without it nothing is recorded or buffered.\n */\nexport interface ReplayConfig {\n /**\n * Share of sessions uploaded from their start, 0 to 1 (default 0.1). The others keep only the last minute in\n * memory and upload it when something goes wrong: a JS error, a rage click, a `purchase…` or `checkout…` event.\n */\n sampleRate: number;\n}\n\n/**\n * What the SDK may send and store.\n * - \"cookieless\" (default): no cookie, no localStorage. Events go without a visitor id; the server counts visitors\n * with a hash of key, date, IP and user agent that changes every day.\n * - \"cookies\": a persistent `ck_id` cookie identifies the visitor across visits, `ck_sid` holds the session.\n * - \"off\": nothing is sent and nothing is stored.\n */\nexport type ConsentMode = \"cookieless\" | \"cookies\" | \"off\";\n\n/**\n * What `setConsent` takes: a mode as before, or the parts to change. `replay` is the separate consent for session\n * recordings (default false); it applies only while analytics is not \"off\".\n *\n * setConsent(\"cookies\");\n * setConsent({ replay: true });\n * setConsent({ analytics: \"cookieless\", replay: false });\n */\nexport type ConsentChoice = ConsentMode | { analytics?: ConsentMode; replay?: boolean };\n\n/** The campaign parameters the SDK reads from the landing URL. */\nexport const UTM_KEYS = [\"utm_source\", \"utm_medium\", \"utm_campaign\", \"utm_term\", \"utm_content\"] as const;\nexport type UtmKey = (typeof UTM_KEYS)[number];\n\n/** One event as it goes over the wire to `POST /api/sdk/events`. */\nexport interface CockpitEvent {\n event: string;\n /** The visitor. Missing in \"cookieless\" mode until identify(): the server then uses its daily visitor hash. */\n distinctId?: string;\n /** Ends after 30 minutes without an event. */\n sessionId?: string;\n props?: Record<string, unknown>;\n /** Milliseconds since the epoch. */\n ts: number;\n url?: string;\n /** `document.referrer` on the first page of a page load, the previous in-app URL after client-side navigation. */\n referrer?: string;\n utm_source?: string;\n utm_medium?: string;\n utm_campaign?: string;\n utm_term?: string;\n utm_content?: string;\n /** The screen size in CSS pixels. */\n screen?: { w: number; h: number };\n}\n\n/** The query parameter that forces variants, for previews in Cockpit: `?ck_preview=pricing-annual:a,home-hero:control`. */\nexport const PREVIEW_PARAM = \"ck_preview\";\n/** Set when Cockpit embeds the page: nothing is tracked, so previews never count as visits. */\nexport const EMBED_PARAM = \"ck_embed\";\n/** The cookie that keeps a visitor in the same variant across visits. Only written in \"cookies\" mode. */\nexport const ID_COOKIE = \"ck_id\";\n/** The cookie that holds the session id in \"cookies\" mode. */\nexport const SESSION_COOKIE = \"ck_sid\";\n/** A session ends after this long without an event. */\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n\n/** The UTM parameters of a query string, like `\"?utm_source=x\"`. Empty values are left out. */\nexport const readUtm = (search: string): Partial<Record<UtmKey, string>> => {\n const params = new URLSearchParams(search);\n const out: Partial<Record<UtmKey, string>> = {};\n for (const key of UTM_KEYS) {\n const value = params.get(key)?.trim();\n if (value) out[key] = value.slice(0, 150);\n }\n return out;\n};\n\n/** FNV-1a, 32 bit, mapped to [0, 1). Stable across server and browser. */\nexport const hashUnit = (input: string) => {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0) / 4294967296;\n};\n\n/**\n * FNV-1a with murmur3's finalizer, mapped to [0, 1): ids that differ only at the end (\"user-1\", \"user-2\") still spread\n * evenly, so a 30 % rollout reaches about 30 % of people. Flags only; experiments keep `hashUnit` so nobody switches.\n */\nexport const flagUnit = (input: string) => {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n h ^= h >>> 16;\n h = Math.imul(h, 0x85ebca6b);\n h ^= h >>> 13;\n h = Math.imul(h, 0xc2b2ae35);\n h ^= h >>> 16;\n return (h >>> 0) / 4294967296;\n};\n\n/** The variant a visitor sees: a shipped winner, control while the experiment does not run, else a stable bucket by visitor. */\nexport const assign = (experiment: ExperimentConfig, distinctId: string): VariantId => {\n const control = experiment.variants[0]?.id ?? \"control\";\n if (experiment.winner) return experiment.winner;\n if (experiment.status !== \"running\") return control;\n const u = hashUnit(`${experiment.key}:${distinctId}`);\n let acc = 0;\n for (const v of experiment.variants) {\n acc += v.weight;\n if (u < acc) return v.id;\n }\n return experiment.variants[experiment.variants.length - 1]?.id ?? control;\n};\n\n/** `\"a:b,c:d\"` → `{ a: \"b\", c: \"d\" }`. Ignores anything malformed. */\nexport const parsePreview = (value: string | string[] | null | undefined): Record<string, VariantId> => {\n const raw = Array.isArray(value) ? value[0] : value;\n if (!raw) return {};\n const out: Record<string, VariantId> = {};\n for (const pair of raw.split(\",\")) {\n const [key, variant] = pair.split(\":\");\n if (key && variant) out[key.trim()] = variant.trim();\n }\n return out;\n};\n\n/** A link that shows one variant of a page, for example in Cockpit's preview frames. */\nexport const previewHref = (url: string, forced: Record<string, VariantId>, embed = true) => {\n const [path, query = \"\"] = url.split(\"?\");\n const params = new URLSearchParams(query);\n const value = Object.entries(forced)\n .map(([k, v]) => `${k}:${v}`)\n .join(\",\");\n if (value) params.set(PREVIEW_PARAM, value);\n if (embed) params.set(EMBED_PARAM, \"1\");\n return `${path}?${params.toString()}`;\n};\n\n/** The variant for one experiment, from a forced preview first, then the config. Unknown experiments get control. */\nexport const resolveVariant = (key: string, config: CockpitConfig | null, distinctId: string, preview: Record<string, VariantId>): VariantId => {\n if (preview[key]) return preview[key];\n const experiment = config?.experiments.find((e) => e.key === key);\n return experiment ? assign(experiment, distinctId) : \"control\";\n};\n\n/**\n * Every text change that applies to this visitor, merged in config order. Control keeps the page's own words,\n * so only a non-control variant (assigned, forced in a preview, or a shipped winner) changes text.\n */\nexport const textChanges = (config: CockpitConfig | null, distinctId: string, preview: Record<string, VariantId>) => {\n const out: Record<string, string> = {};\n for (const experiment of config?.experiments ?? []) {\n const id = resolveVariant(experiment.key, config, distinctId, preview);\n if (id === (experiment.variants[0]?.id ?? \"control\")) continue;\n Object.assign(out, experiment.variants.find((v) => v.id === id)?.changes ?? {});\n }\n return out;\n};\n\n/**\n * The MRRocket API host from `NEXT_PUBLIC_MRROCKET_HOST`. Written as a literal property access on purpose: Next.js\n * inlines `process.env.NEXT_PUBLIC_*` at build time, in the browser bundle and in the server bundle (also on\n * Cloudflare Workers), and only replaces the literal form. Without a bundler (or a `process`) it is undefined.\n */\nexport const envHost = (): string | undefined => {\n try {\n return process.env.NEXT_PUBLIC_MRROCKET_HOST || undefined;\n } catch {\n return undefined;\n }\n};\n\n/** The public key from `NEXT_PUBLIC_MRROCKET_KEY`, the default for the `apiKey` prop. Same literal-access rule as `envHost`. */\nexport const envKey = (): string | undefined => {\n try {\n return process.env.NEXT_PUBLIC_MRROCKET_KEY || undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Where the API lives: the `host` prop, else `NEXT_PUBLIC_MRROCKET_HOST`, else \"\" (the page's own origin, which is how\n * MRRocket's own demo pages call it). Without a trailing slash.\n */\nexport const resolveHost = (host?: string) => (host ?? envHost() ?? \"\").trim().replace(/\\/+$/, \"\");\n\n/** A random visitor id. */\nexport const newDistinctId = () =>\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto ? crypto.randomUUID() : `ck_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n"],"mappings":"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","names":[]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error tracking: uncaught errors and unhandled promise rejections of the page (and `captureError` for ones you
|
|
3
|
+
* catch), sent to `POST /api/sdk/errors`, where MRRocket groups them into issues for Build › Errors. Independent of
|
|
4
|
+
* session recordings. Only the error itself, the page URL and the last pages and clicks go along (no typed text, no
|
|
5
|
+
* network bodies), under the same consent as analytics.
|
|
6
|
+
*
|
|
7
|
+
* Limits on the page: the same error is sent once per page load, at most 20 errors per page load, in batches every
|
|
8
|
+
* 2 seconds and with `sendBeacon` when the page hides.
|
|
9
|
+
*/
|
|
10
|
+
import type { ErrorReport } from "./core.js";
|
|
11
|
+
export interface ErrorCapture {
|
|
12
|
+
/** An error you caught yourself; `handled` is true. */
|
|
13
|
+
capture: (error: unknown, mechanism?: ErrorReport["mechanism"]) => void;
|
|
14
|
+
/** A page view or a click for the breadcrumbs. */
|
|
15
|
+
crumb: (type: "navigation" | "click" | "lifecycle", message: string) => void;
|
|
16
|
+
stop: () => void;
|
|
17
|
+
}
|
|
18
|
+
/** Type, message and stack of anything thrown. */
|
|
19
|
+
export declare const describeError: (error: unknown) => {
|
|
20
|
+
type: string;
|
|
21
|
+
message: string;
|
|
22
|
+
stack?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare const startErrorCapture: (opts: {
|
|
25
|
+
host: string;
|
|
26
|
+
apiKey: string;
|
|
27
|
+
release?: string;
|
|
28
|
+
/** Read when an error happens: whether to send at all (consent), and who and which session. */
|
|
29
|
+
context: () => {
|
|
30
|
+
send: boolean;
|
|
31
|
+
distinctId?: string;
|
|
32
|
+
sessionId?: string;
|
|
33
|
+
};
|
|
34
|
+
warn: (message: string) => void;
|
|
35
|
+
}) => ErrorCapture;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var MAX_STACK = 8 * 1024;
|
|
3
|
+
var MAX_PER_PAGE = 20;
|
|
4
|
+
var MAX_CRUMBS = 20;
|
|
5
|
+
var FLUSH_MS = 2e3;
|
|
6
|
+
var clip = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
7
|
+
var describeError = (error) => {
|
|
8
|
+
if (error instanceof Error) return { type: error.name || "Error", message: clip(error.message || String(error), 1e3), stack: error.stack ? clip(error.stack, MAX_STACK) : void 0 };
|
|
9
|
+
if (typeof error === "string") return { type: "Error", message: clip(error, 1e3) };
|
|
10
|
+
try {
|
|
11
|
+
return { type: "Error", message: clip(JSON.stringify(error) ?? String(error), 1e3) };
|
|
12
|
+
} catch {
|
|
13
|
+
return { type: "Error", message: String(error) };
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var startErrorCapture = (opts) => {
|
|
17
|
+
const queue = [];
|
|
18
|
+
const crumbs = [];
|
|
19
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20
|
+
let sent = 0;
|
|
21
|
+
const url = `${opts.host}/api/sdk/errors`;
|
|
22
|
+
const flush = (beacon = false) => {
|
|
23
|
+
while (queue.length) {
|
|
24
|
+
const body = JSON.stringify({ key: opts.apiKey, errors: queue.splice(0, 4) });
|
|
25
|
+
const ok = beacon && typeof navigator !== "undefined" && "sendBeacon" in navigator && navigator.sendBeacon(url, body);
|
|
26
|
+
if (!ok)
|
|
27
|
+
fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: true }).then((r) => {
|
|
28
|
+
if (!r.ok && r.status !== 429) opts.warn(`Error reports were rejected with ${r.status}. Check the public key and NEXT_PUBLIC_MRROCKET_HOST.`);
|
|
29
|
+
}).catch(() => {
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const capture = (error, mechanism = "manual") => {
|
|
34
|
+
const ctx = opts.context();
|
|
35
|
+
if (!ctx.send || sent >= MAX_PER_PAGE) return;
|
|
36
|
+
const d = describeError(error);
|
|
37
|
+
if (/\/api\/sdk\/errors/.test(d.message)) return;
|
|
38
|
+
const mark = `${d.type}:${d.message}`;
|
|
39
|
+
if (seen.has(mark)) return;
|
|
40
|
+
seen.add(mark);
|
|
41
|
+
sent++;
|
|
42
|
+
queue.push({
|
|
43
|
+
...d,
|
|
44
|
+
mechanism,
|
|
45
|
+
handled: mechanism === "manual",
|
|
46
|
+
url: window.location.href,
|
|
47
|
+
ts: Date.now(),
|
|
48
|
+
crumbs: crumbs.slice(),
|
|
49
|
+
...opts.release ? { release: opts.release } : {},
|
|
50
|
+
...ctx.distinctId ? { distinctId: ctx.distinctId } : {},
|
|
51
|
+
...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
const onError = (e) => {
|
|
55
|
+
if (!e.error && (!e.message || e.message === "Script error.")) return;
|
|
56
|
+
capture(e.error ?? new Error(e.message), "onerror");
|
|
57
|
+
};
|
|
58
|
+
const onRejection = (e) => capture(e.reason ?? new Error("Unhandled promise rejection"), "onunhandledrejection");
|
|
59
|
+
const onHide = () => flush(true);
|
|
60
|
+
window.addEventListener("error", onError);
|
|
61
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
62
|
+
window.addEventListener("pagehide", onHide);
|
|
63
|
+
const timer = window.setInterval(() => flush(), FLUSH_MS);
|
|
64
|
+
return {
|
|
65
|
+
capture,
|
|
66
|
+
crumb: (type, message) => {
|
|
67
|
+
crumbs.push({ ts: Date.now(), type, message: clip(message, 120) });
|
|
68
|
+
if (crumbs.length > MAX_CRUMBS) crumbs.shift();
|
|
69
|
+
},
|
|
70
|
+
stop: () => {
|
|
71
|
+
window.removeEventListener("error", onError);
|
|
72
|
+
window.removeEventListener("unhandledrejection", onRejection);
|
|
73
|
+
window.removeEventListener("pagehide", onHide);
|
|
74
|
+
window.clearInterval(timer);
|
|
75
|
+
flush(true);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
export {
|
|
80
|
+
describeError,
|
|
81
|
+
startErrorCapture
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Error tracking: uncaught errors and unhandled promise rejections of the page (and `captureError` for ones you\n * catch), sent to `POST /api/sdk/errors`, where MRRocket groups them into issues for Build › Errors. Independent of\n * session recordings. Only the error itself, the page URL and the last pages and clicks go along (no typed text, no\n * network bodies), under the same consent as analytics.\n *\n * Limits on the page: the same error is sent once per page load, at most 20 errors per page load, in batches every\n * 2 seconds and with `sendBeacon` when the page hides.\n */\nimport type { ErrorReport } from \"./core\";\n\nconst MAX_STACK = 8 * 1024;\nconst MAX_PER_PAGE = 20;\nconst MAX_CRUMBS = 20;\nconst FLUSH_MS = 2000;\n\nexport interface ErrorCapture {\n /** An error you caught yourself; `handled` is true. */\n capture: (error: unknown, mechanism?: ErrorReport[\"mechanism\"]) => void;\n /** A page view or a click for the breadcrumbs. */\n crumb: (type: \"navigation\" | \"click\" | \"lifecycle\", message: string) => void;\n stop: () => void;\n}\n\nconst clip = (s: string, n: number) => (s.length > n ? `${s.slice(0, n - 1)}…` : s);\n\n/** Type, message and stack of anything thrown. */\nexport const describeError = (error: unknown): { type: string; message: string; stack?: string } => {\n if (error instanceof Error) return { type: error.name || \"Error\", message: clip(error.message || String(error), 1000), stack: error.stack ? clip(error.stack, MAX_STACK) : undefined };\n if (typeof error === \"string\") return { type: \"Error\", message: clip(error, 1000) };\n try {\n return { type: \"Error\", message: clip(JSON.stringify(error) ?? String(error), 1000) };\n } catch {\n return { type: \"Error\", message: String(error) };\n }\n};\n\nexport const startErrorCapture = (opts: {\n host: string;\n apiKey: string;\n release?: string;\n /** Read when an error happens: whether to send at all (consent), and who and which session. */\n context: () => { send: boolean; distinctId?: string; sessionId?: string };\n warn: (message: string) => void;\n}): ErrorCapture => {\n const queue: ErrorReport[] = [];\n const crumbs: NonNullable<ErrorReport[\"crumbs\"]> = [];\n const seen = new Set<string>();\n let sent = 0;\n const url = `${opts.host}/api/sdk/errors`;\n\n const flush = (beacon = false) => {\n // At most four reports per request: four full stacks stay under the server's 64 KB.\n while (queue.length) {\n const body = JSON.stringify({ key: opts.apiKey, errors: queue.splice(0, 4) });\n const ok = beacon && typeof navigator !== \"undefined\" && \"sendBeacon\" in navigator && navigator.sendBeacon(url, body);\n if (!ok)\n fetch(url, { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body, keepalive: true })\n .then((r) => {\n if (!r.ok && r.status !== 429) opts.warn(`Error reports were rejected with ${r.status}. Check the public key and NEXT_PUBLIC_MRROCKET_HOST.`);\n })\n .catch(() => {});\n }\n };\n\n const capture = (error: unknown, mechanism: ErrorReport[\"mechanism\"] = \"manual\") => {\n const ctx = opts.context();\n if (!ctx.send || sent >= MAX_PER_PAGE) return;\n const d = describeError(error);\n // Our own reports failing must never report themselves.\n if (/\\/api\\/sdk\\/errors/.test(d.message)) return;\n const mark = `${d.type}:${d.message}`;\n if (seen.has(mark)) return;\n seen.add(mark);\n sent++;\n queue.push({\n ...d,\n mechanism,\n handled: mechanism === \"manual\",\n url: window.location.href,\n ts: Date.now(),\n crumbs: crumbs.slice(),\n ...(opts.release ? { release: opts.release } : {}),\n ...(ctx.distinctId ? { distinctId: ctx.distinctId } : {}),\n ...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),\n });\n };\n\n const onError = (e: ErrorEvent) => {\n // \"Script error.\" from a cross-origin script carries nothing useful.\n if (!e.error && (!e.message || e.message === \"Script error.\")) return;\n capture(e.error ?? new Error(e.message), \"onerror\");\n };\n const onRejection = (e: PromiseRejectionEvent) => capture(e.reason ?? new Error(\"Unhandled promise rejection\"), \"onunhandledrejection\");\n const onHide = () => flush(true);\n window.addEventListener(\"error\", onError);\n window.addEventListener(\"unhandledrejection\", onRejection);\n window.addEventListener(\"pagehide\", onHide);\n const timer = window.setInterval(() => flush(), FLUSH_MS);\n\n return {\n capture,\n crumb: (type, message) => {\n crumbs.push({ ts: Date.now(), type, message: clip(message, 120) });\n if (crumbs.length > MAX_CRUMBS) crumbs.shift();\n },\n stop: () => {\n window.removeEventListener(\"error\", onError);\n window.removeEventListener(\"unhandledrejection\", onRejection);\n window.removeEventListener(\"pagehide\", onHide);\n window.clearInterval(timer);\n flush(true);\n },\n };\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { type ChangelogEntry, type CockpitProviderProps, type CockpitUser, type FeedbackBoardInfo, type FeedbackBoardProps, type FeedbackComment, type FeedbackList, type FeedbackPost, type FeedbackRoadmap, type FeedbackWidgetProps, type PostStatus, type SurveyAnswer, type SurveyDefinition, type SurveyQuestion, type SurveyQuestionKind, CockpitProvider, Experiment, FeedbackBoard, FeedbackWidget, Survey, Text, WIDGET_CSS, createApi, useCockpit, useFeedback, useStyles, useVariant, } from "./browser.js";
|
|
2
|
-
export { type CockpitConfig, type CockpitEvent, type ConsentChoice, type ConsentMode, type ReplayConfig, type ExperimentConfig, type VariantConfig, type VariantId, EMBED_PARAM, ID_COOKIE, PREVIEW_PARAM, SESSION_COOKIE, assign, parsePreview, previewHref, } from "./core.js";
|
|
1
|
+
export { type ChangelogEntry, type CockpitProviderProps, type CockpitUser, type FeedbackBoardInfo, type FeedbackBoardProps, type FeedbackComment, type FeedbackList, type FeedbackPost, type FeedbackRoadmap, type FeedbackWidgetProps, type PostStatus, type SurveyAnswer, type SurveyDefinition, type SurveyQuestion, type SurveyQuestionKind, CockpitProvider, Experiment, FeedbackBoard, FeedbackWidget, Survey, Text, WIDGET_CSS, createApi, useCockpit, useFeedback, useFlag, useFlagVariant, useStyles, useVariant, } from "./browser.js";
|
|
2
|
+
export { type CockpitConfig, type CockpitEvent, type ConsentChoice, type ConsentMode, type ReplayConfig, type ErrorReport, type ExperimentConfig, type FlagCondition, type FlagConditionSet, type FlagConfig, type FlagPerson, type VariantConfig, type VariantId, EMBED_PARAM, SDK_VERSION, ID_COOKIE, PREVIEW_PARAM, SESSION_COOKIE, assign, evaluateFlag, parsePreview, previewHref, } from "./core.js";
|
|
3
3
|
export { type AttributionMode, type CheckoutReferral, type Referral, REFERRAL_COOKIE, getReferral, readReferralCookie, referralForCheckout, referralLink, } from "./referral.js";
|
package/dist/index.js
CHANGED
|
@@ -10,15 +10,19 @@ import {
|
|
|
10
10
|
createApi,
|
|
11
11
|
useCockpit,
|
|
12
12
|
useFeedback,
|
|
13
|
+
useFlag,
|
|
14
|
+
useFlagVariant,
|
|
13
15
|
useStyles,
|
|
14
16
|
useVariant
|
|
15
17
|
} from "./browser.js";
|
|
16
18
|
import {
|
|
17
19
|
EMBED_PARAM,
|
|
20
|
+
SDK_VERSION,
|
|
18
21
|
ID_COOKIE,
|
|
19
22
|
PREVIEW_PARAM,
|
|
20
23
|
SESSION_COOKIE,
|
|
21
24
|
assign,
|
|
25
|
+
evaluateFlag,
|
|
22
26
|
parsePreview,
|
|
23
27
|
previewHref
|
|
24
28
|
} from "./core.js";
|
|
@@ -38,12 +42,14 @@ export {
|
|
|
38
42
|
ID_COOKIE,
|
|
39
43
|
PREVIEW_PARAM,
|
|
40
44
|
REFERRAL_COOKIE,
|
|
45
|
+
SDK_VERSION,
|
|
41
46
|
SESSION_COOKIE,
|
|
42
47
|
Survey,
|
|
43
48
|
Text,
|
|
44
49
|
WIDGET_CSS,
|
|
45
50
|
assign,
|
|
46
51
|
createApi,
|
|
52
|
+
evaluateFlag,
|
|
47
53
|
getReferral,
|
|
48
54
|
parsePreview,
|
|
49
55
|
previewHref,
|
|
@@ -52,6 +58,8 @@ export {
|
|
|
52
58
|
referralLink,
|
|
53
59
|
useCockpit,
|
|
54
60
|
useFeedback,
|
|
61
|
+
useFlag,
|
|
62
|
+
useFlagVariant,
|
|
55
63
|
useStyles,
|
|
56
64
|
useVariant
|
|
57
65
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n type ChangelogEntry,\n type CockpitProviderProps,\n type CockpitUser,\n type FeedbackBoardInfo,\n type FeedbackBoardProps,\n type FeedbackComment,\n type FeedbackList,\n type FeedbackPost,\n type FeedbackRoadmap,\n type FeedbackWidgetProps,\n type PostStatus,\n type SurveyAnswer,\n type SurveyDefinition,\n type SurveyQuestion,\n type SurveyQuestionKind,\n CockpitProvider,\n Experiment,\n FeedbackBoard,\n FeedbackWidget,\n Survey,\n Text,\n WIDGET_CSS,\n createApi,\n useCockpit,\n useFeedback,\n useStyles,\n useVariant,\n} from \"./browser\";\nexport {\n type CockpitConfig,\n type CockpitEvent,\n type ConsentChoice,\n type ConsentMode,\n type ReplayConfig,\n type ExperimentConfig,\n type VariantConfig,\n type VariantId,\n EMBED_PARAM,\n ID_COOKIE,\n PREVIEW_PARAM,\n SESSION_COOKIE,\n assign,\n parsePreview,\n previewHref,\n} from \"./core\";\nexport {\n type AttributionMode,\n type CheckoutReferral,\n type Referral,\n REFERRAL_COOKIE,\n getReferral,\n readReferralCookie,\n referralForCheckout,\n referralLink,\n} from \"./referral\";\n"],"mappings":";AAAA;AAAA,EAgBI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP;AAAA,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n type ChangelogEntry,\n type CockpitProviderProps,\n type CockpitUser,\n type FeedbackBoardInfo,\n type FeedbackBoardProps,\n type FeedbackComment,\n type FeedbackList,\n type FeedbackPost,\n type FeedbackRoadmap,\n type FeedbackWidgetProps,\n type PostStatus,\n type SurveyAnswer,\n type SurveyDefinition,\n type SurveyQuestion,\n type SurveyQuestionKind,\n CockpitProvider,\n Experiment,\n FeedbackBoard,\n FeedbackWidget,\n Survey,\n Text,\n WIDGET_CSS,\n createApi,\n useCockpit,\n useFeedback,\n useFlag,\n useFlagVariant,\n useStyles,\n useVariant,\n} from \"./browser\";\nexport {\n type CockpitConfig,\n type CockpitEvent,\n type ConsentChoice,\n type ConsentMode,\n type ReplayConfig,\n type ErrorReport,\n type ExperimentConfig,\n type FlagCondition,\n type FlagConditionSet,\n type FlagConfig,\n type FlagPerson,\n type VariantConfig,\n type VariantId,\n EMBED_PARAM,\n SDK_VERSION,\n ID_COOKIE,\n PREVIEW_PARAM,\n SESSION_COOKIE,\n assign,\n evaluateFlag,\n parsePreview,\n previewHref,\n} from \"./core\";\nexport {\n type AttributionMode,\n type CheckoutReferral,\n type Referral,\n REFERRAL_COOKIE,\n getReferral,\n readReferralCookie,\n referralForCheckout,\n referralLink,\n} from \"./referral\";\n"],"mappings":";AAAA;AAAA,EAgBI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP;AAAA,EAcI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP;AAAA,EAII;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;","names":[]}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CockpitConfig, type VariantId } from "./core.js";
|
|
1
|
+
import { type CockpitConfig, type FlagPerson, type VariantId } from "./core.js";
|
|
2
2
|
/**
|
|
3
3
|
* Server helpers for the App Router: load the experiments once per request, then render the right variant on the
|
|
4
4
|
* first paint (no flicker) and hand the same config to `<CockpitProvider config>` so the browser agrees.
|
|
@@ -40,6 +40,18 @@ interface RequestInfo {
|
|
|
40
40
|
}
|
|
41
41
|
/** The variant of one experiment for this request. */
|
|
42
42
|
export declare const getVariant: (key: string, { config, searchParams, cookies, distinctId }: RequestInfo) => VariantId;
|
|
43
|
+
/**
|
|
44
|
+
* A feature flag for this request: true while it is on for the visitor, false while it is off or unknown (or MRRocket
|
|
45
|
+
* could not be reached). `person` takes the properties the flag's audience reads ("Email", "Plan" …), as you pass them
|
|
46
|
+
* to identify(). For a multivariate flag use `getFlagVariant`.
|
|
47
|
+
*/
|
|
48
|
+
export declare const getFlag: (key: string, { config, cookies, distinctId, person }: RequestInfo & {
|
|
49
|
+
person?: FlagPerson;
|
|
50
|
+
}) => boolean;
|
|
51
|
+
/** The variant of a multivariate flag for this request, or null while it is off. */
|
|
52
|
+
export declare const getFlagVariant: (key: string, { config, cookies, distinctId, person }: RequestInfo & {
|
|
53
|
+
person?: FlagPerson;
|
|
54
|
+
}) => string | null;
|
|
43
55
|
/** Every text change for this request, to render `<Text>` defaults server-side. */
|
|
44
56
|
export declare const getTexts: ({ config, searchParams, cookies, distinctId }: RequestInfo) => Record<string, string>;
|
|
45
57
|
export * from "./core.js";
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
|
-
import { EMBED_PARAM, ID_COOKIE, PREVIEW_PARAM, envKey, parsePreview, resolveHost, resolveVariant, textChanges } from "./core.js";
|
|
2
|
+
import { EMBED_PARAM, ID_COOKIE, PREVIEW_PARAM, envKey, flagFor, parsePreview, resolveHost, resolveVariant, textChanges } from "./core.js";
|
|
3
3
|
export * from "./core.js";
|
|
4
4
|
import { REFERRAL_COOKIE, readReferralCookie, referralForCheckout, referralLink } from "./referral.js";
|
|
5
5
|
var CONFIG_TIMEOUT_MS = 2500;
|
|
@@ -28,11 +28,18 @@ var previewFrom = (searchParams) => {
|
|
|
28
28
|
};
|
|
29
29
|
var distinctIdFrom = (cookies) => cookies?.get(ID_COOKIE)?.value ?? "anonymous";
|
|
30
30
|
var getVariant = (key, { config, searchParams, cookies, distinctId }) => resolveVariant(key, config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);
|
|
31
|
+
var getFlag = (key, { config, cookies, distinctId, person }) => flagFor(key, config, distinctId ?? distinctIdFrom(cookies), person) !== false;
|
|
32
|
+
var getFlagVariant = (key, { config, cookies, distinctId, person }) => {
|
|
33
|
+
const v = flagFor(key, config, distinctId ?? distinctIdFrom(cookies), person);
|
|
34
|
+
return typeof v === "string" ? v : null;
|
|
35
|
+
};
|
|
31
36
|
var getTexts = ({ config, searchParams, cookies, distinctId }) => textChanges(config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);
|
|
32
37
|
export {
|
|
33
38
|
REFERRAL_COOKIE,
|
|
34
39
|
distinctIdFrom,
|
|
35
40
|
getCockpitConfig,
|
|
41
|
+
getFlag,
|
|
42
|
+
getFlagVariant,
|
|
36
43
|
getTexts,
|
|
37
44
|
getVariant,
|
|
38
45
|
previewFrom,
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { type CockpitConfig, EMBED_PARAM, ID_COOKIE, PREVIEW_PARAM, type VariantId, envKey, parsePreview, resolveHost, resolveVariant, textChanges } from \"./core\";\n\n/**\n * Server helpers for the App Router: load the experiments once per request, then render the right variant on the\n * first paint (no flicker) and hand the same config to `<CockpitProvider config>` so the browser agrees.\n */\n\ntype SearchParams = Record<string, string | string[] | undefined>;\ninterface CookieReader {\n get(name: string): { value: string } | undefined;\n}\n\n/** How long the server waits for MRRocket before it renders with control. */\nconst CONFIG_TIMEOUT_MS = 2500;\n\n/**\n * The experiments for a public key. `null` when MRRocket cannot be reached, answers with an error or is slower than\n * `timeoutMs`: everyone then sees control and the page still renders. Runs on Node, the Edge runtime and Cloudflare\n * Workers (only `fetch` and `AbortController`).\n *\n * `apiKey` and `host` default to `NEXT_PUBLIC_MRROCKET_KEY` and `NEXT_PUBLIC_MRROCKET_HOST`.\n */\nexport const getCockpitConfig = async ({ apiKey, host, timeoutMs = CONFIG_TIMEOUT_MS }: { apiKey?: string; host?: string; timeoutMs?: number } = {}): Promise<CockpitConfig | null> => {\n const key = apiKey ?? envKey();\n if (!key) return null;\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), timeoutMs);\n try {\n // `next.revalidate: 0` keeps this request uncached in Next.js. Not `cache: \"no-store\"`: Cloudflare Workers\n // throws on the `cache` field of a fetch unless a compatibility flag is on, and every config would be null.\n const res = await fetch(`${resolveHost(host)}/api/sdk/config?key=${encodeURIComponent(key)}`, {\n signal: ctl.signal,\n next: { revalidate: 0 },\n } as RequestInit);\n if (!res.ok) return null;\n const config = (await res.json()) as CockpitConfig | null;\n return config && Array.isArray(config.experiments) ? config : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n};\n\n/** Forced variants and the embed flag from a page's search params, for previews in Cockpit. */\nexport const previewFrom = (searchParams: SearchParams | undefined) => {\n const embed = searchParams?.[EMBED_PARAM];\n return { preview: parsePreview(searchParams?.[PREVIEW_PARAM]), embedded: (Array.isArray(embed) ? embed[0] : embed) === \"1\" };\n};\n\n/** The visitor's id from the `ck_id` cookie, if the browser has one yet (only in consent \"cookies\" mode). */\nexport const distinctIdFrom = (cookies: CookieReader | undefined) => cookies?.get(ID_COOKIE)?.value ?? \"anonymous\";\n\n/**\n * Who the visitor is for this request. Pass `distinctId` (your signed-in user id, the same you give\n * `<CockpitProvider distinctId>`) when there is no `ck_id` cookie, as in the default cookieless mode.\n */\ninterface RequestInfo {\n config: CockpitConfig | null;\n searchParams?: SearchParams;\n cookies?: CookieReader;\n distinctId?: string;\n}\n\n/** The variant of one experiment for this request. */\nexport const getVariant = (key: string, { config, searchParams, cookies, distinctId }: RequestInfo): VariantId =>\n resolveVariant(key, config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);\n\n/** Every text change for this request, to render `<Text>` defaults server-side. */\nexport const getTexts = ({ config, searchParams, cookies, distinctId }: RequestInfo) =>\n textChanges(config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);\n\nexport * from \"./core\";\n/** Affiliate referrals on the server: read the `mrr_ref` cookie in the route that creates a Stripe Checkout Session. */\nexport { type CheckoutReferral, type Referral, REFERRAL_COOKIE, readReferralCookie, referralForCheckout, referralLink } from \"./referral\";\n"],"mappings":";AAAA,SAA6B,
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { type CockpitConfig, EMBED_PARAM, type FlagPerson, ID_COOKIE, PREVIEW_PARAM, type VariantId, envKey, flagFor, parsePreview, resolveHost, resolveVariant, textChanges } from \"./core\";\n\n/**\n * Server helpers for the App Router: load the experiments once per request, then render the right variant on the\n * first paint (no flicker) and hand the same config to `<CockpitProvider config>` so the browser agrees.\n */\n\ntype SearchParams = Record<string, string | string[] | undefined>;\ninterface CookieReader {\n get(name: string): { value: string } | undefined;\n}\n\n/** How long the server waits for MRRocket before it renders with control. */\nconst CONFIG_TIMEOUT_MS = 2500;\n\n/**\n * The experiments for a public key. `null` when MRRocket cannot be reached, answers with an error or is slower than\n * `timeoutMs`: everyone then sees control and the page still renders. Runs on Node, the Edge runtime and Cloudflare\n * Workers (only `fetch` and `AbortController`).\n *\n * `apiKey` and `host` default to `NEXT_PUBLIC_MRROCKET_KEY` and `NEXT_PUBLIC_MRROCKET_HOST`.\n */\nexport const getCockpitConfig = async ({ apiKey, host, timeoutMs = CONFIG_TIMEOUT_MS }: { apiKey?: string; host?: string; timeoutMs?: number } = {}): Promise<CockpitConfig | null> => {\n const key = apiKey ?? envKey();\n if (!key) return null;\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), timeoutMs);\n try {\n // `next.revalidate: 0` keeps this request uncached in Next.js. Not `cache: \"no-store\"`: Cloudflare Workers\n // throws on the `cache` field of a fetch unless a compatibility flag is on, and every config would be null.\n const res = await fetch(`${resolveHost(host)}/api/sdk/config?key=${encodeURIComponent(key)}`, {\n signal: ctl.signal,\n next: { revalidate: 0 },\n } as RequestInit);\n if (!res.ok) return null;\n const config = (await res.json()) as CockpitConfig | null;\n return config && Array.isArray(config.experiments) ? config : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n};\n\n/** Forced variants and the embed flag from a page's search params, for previews in Cockpit. */\nexport const previewFrom = (searchParams: SearchParams | undefined) => {\n const embed = searchParams?.[EMBED_PARAM];\n return { preview: parsePreview(searchParams?.[PREVIEW_PARAM]), embedded: (Array.isArray(embed) ? embed[0] : embed) === \"1\" };\n};\n\n/** The visitor's id from the `ck_id` cookie, if the browser has one yet (only in consent \"cookies\" mode). */\nexport const distinctIdFrom = (cookies: CookieReader | undefined) => cookies?.get(ID_COOKIE)?.value ?? \"anonymous\";\n\n/**\n * Who the visitor is for this request. Pass `distinctId` (your signed-in user id, the same you give\n * `<CockpitProvider distinctId>`) when there is no `ck_id` cookie, as in the default cookieless mode.\n */\ninterface RequestInfo {\n config: CockpitConfig | null;\n searchParams?: SearchParams;\n cookies?: CookieReader;\n distinctId?: string;\n}\n\n/** The variant of one experiment for this request. */\nexport const getVariant = (key: string, { config, searchParams, cookies, distinctId }: RequestInfo): VariantId =>\n resolveVariant(key, config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);\n\n/**\n * A feature flag for this request: true while it is on for the visitor, false while it is off or unknown (or MRRocket\n * could not be reached). `person` takes the properties the flag's audience reads (\"Email\", \"Plan\" …), as you pass them\n * to identify(). For a multivariate flag use `getFlagVariant`.\n */\nexport const getFlag = (key: string, { config, cookies, distinctId, person }: RequestInfo & { person?: FlagPerson }): boolean =>\n flagFor(key, config, distinctId ?? distinctIdFrom(cookies), person) !== false;\n\n/** The variant of a multivariate flag for this request, or null while it is off. */\nexport const getFlagVariant = (key: string, { config, cookies, distinctId, person }: RequestInfo & { person?: FlagPerson }): string | null => {\n const v = flagFor(key, config, distinctId ?? distinctIdFrom(cookies), person);\n return typeof v === \"string\" ? v : null;\n};\n\n/** Every text change for this request, to render `<Text>` defaults server-side. */\nexport const getTexts = ({ config, searchParams, cookies, distinctId }: RequestInfo) =>\n textChanges(config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);\n\nexport * from \"./core\";\n/** Affiliate referrals on the server: read the `mrr_ref` cookie in the route that creates a Stripe Checkout Session. */\nexport { type CheckoutReferral, type Referral, REFERRAL_COOKIE, readReferralCookie, referralForCheckout, referralLink } from \"./referral\";\n"],"mappings":";AAAA,SAA6B,aAA8B,WAAW,eAA+B,QAAQ,SAAS,cAAc,aAAa,gBAAgB,mBAAmB;AAsFpL,cAAc;AAEd,SAA+C,iBAAiB,oBAAoB,qBAAqB,oBAAoB;AA3E7H,IAAM,oBAAoB;AASnB,IAAM,mBAAmB,OAAO,EAAE,QAAQ,MAAM,YAAY,kBAAkB,IAA4D,CAAC,MAAqC;AACnL,QAAM,MAAM,UAAU,OAAO;AAC7B,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,MAAM,IAAI,gBAAgB;AAChC,QAAM,QAAQ,WAAW,MAAM,IAAI,MAAM,GAAG,SAAS;AACrD,MAAI;AAGA,UAAM,MAAM,MAAM,MAAM,GAAG,YAAY,IAAI,CAAC,uBAAuB,mBAAmB,GAAG,CAAC,IAAI;AAAA,MAC1F,QAAQ,IAAI;AAAA,MACZ,MAAM,EAAE,YAAY,EAAE;AAAA,IAC1B,CAAgB;AAChB,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,SAAU,MAAM,IAAI,KAAK;AAC/B,WAAO,UAAU,MAAM,QAAQ,OAAO,WAAW,IAAI,SAAS;AAAA,EAClE,QAAQ;AACJ,WAAO;AAAA,EACX,UAAE;AACE,iBAAa,KAAK;AAAA,EACtB;AACJ;AAGO,IAAM,cAAc,CAAC,iBAA2C;AACnE,QAAM,QAAQ,eAAe,WAAW;AACxC,SAAO,EAAE,SAAS,aAAa,eAAe,aAAa,CAAC,GAAG,WAAW,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI,WAAW,IAAI;AAC/H;AAGO,IAAM,iBAAiB,CAAC,YAAsC,SAAS,IAAI,SAAS,GAAG,SAAS;AAchG,IAAM,aAAa,CAAC,KAAa,EAAE,QAAQ,cAAc,SAAS,WAAW,MAChF,eAAe,KAAK,QAAQ,cAAc,eAAe,OAAO,GAAG,YAAY,YAAY,EAAE,OAAO;AAOjG,IAAM,UAAU,CAAC,KAAa,EAAE,QAAQ,SAAS,YAAY,OAAO,MACvE,QAAQ,KAAK,QAAQ,cAAc,eAAe,OAAO,GAAG,MAAM,MAAM;AAGrE,IAAM,iBAAiB,CAAC,KAAa,EAAE,QAAQ,SAAS,YAAY,OAAO,MAA4D;AAC1I,QAAM,IAAI,QAAQ,KAAK,QAAQ,cAAc,eAAe,OAAO,GAAG,MAAM;AAC5E,SAAO,OAAO,MAAM,WAAW,IAAI;AACvC;AAGO,IAAM,WAAW,CAAC,EAAE,QAAQ,cAAc,SAAS,WAAW,MACjE,YAAY,QAAQ,cAAc,eAAe,OAAO,GAAG,YAAY,YAAY,EAAE,OAAO;","names":[]}
|
package/dist/styles.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** The one stylesheet for every Cockpit widget. Everything is scoped under `.ckw-root` and prefixed `ckw-`. */
|
|
2
|
-
export declare const CSS = "\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n";
|
|
2
|
+
export declare const CSS = "\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-root .ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-root .ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n";
|
|
3
3
|
/** Puts the stylesheet into the page once, however many widgets mount. */
|
|
4
4
|
export declare const useStyles: () => void;
|
package/dist/styles.js
CHANGED
|
@@ -119,8 +119,8 @@ font-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizi
|
|
|
119
119
|
.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}
|
|
120
120
|
.ckw-num:hover{border-color:var(--ckw-accent)}
|
|
121
121
|
.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}
|
|
122
|
-
.ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}
|
|
123
|
-
.ckw-star[data-on=true]{color:#f5a524}
|
|
122
|
+
.ckw-root .ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}
|
|
123
|
+
.ckw-root .ckw-star[data-on=true]{color:#f5a524}
|
|
124
124
|
.ckw-star svg{width:28px;height:28px}
|
|
125
125
|
.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}
|
|
126
126
|
.ckw-opts{display:flex;flex-direction:column;gap:6px}
|
package/dist/styles.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/styles.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\nconst STYLE_ID = \"ckw-styles\";\n\n/** The one stylesheet for every Cockpit widget. Everything is scoped under `.ckw-root` and prefixed `ckw-`. */\nexport const CSS = `\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n`;\n\n/** Puts the stylesheet into the page once, however many widgets mount. */\nexport const useStyles = () => {\n useEffect(() => {\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n};\n"],"mappings":";;;AAEA,SAAS,iBAAiB;AAE1B,IAAM,WAAW;AAGV,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkIZ,IAAM,YAAY,MAAM;AAC3B,YAAU,MAAM;AACZ,QAAI,SAAS,eAAe,QAAQ,EAAG;AACvC,UAAM,KAAK,SAAS,cAAc,OAAO;AACzC,OAAG,KAAK;AACR,OAAG,cAAc;AACjB,aAAS,KAAK,YAAY,EAAE;AAAA,EAChC,GAAG,CAAC,CAAC;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/styles.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\nconst STYLE_ID = \"ckw-styles\";\n\n/** The one stylesheet for every Cockpit widget. Everything is scoped under `.ckw-root` and prefixed `ckw-`. */\nexport const CSS = `\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-root .ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-root .ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n`;\n\n/** Puts the stylesheet into the page once, however many widgets mount. */\nexport const useStyles = () => {\n useEffect(() => {\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n};\n"],"mappings":";;;AAEA,SAAS,iBAAiB;AAE1B,IAAM,WAAW;AAGV,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkIZ,IAAM,YAAY,MAAM;AAC3B,YAAU,MAAM;AACZ,QAAI,SAAS,eAAe,QAAQ,EAAG;AACvC,UAAM,KAAK,SAAS,cAAc,OAAO;AACzC,OAAG,KAAK;AACR,OAAG,cAAc;AACjB,aAAS,KAAK,YAAY,EAAE;AAAA,EAChC,GAAG,CAAC,CAAC;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrocket/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MRRocket SDK: page views, events, heatmap clicks, session recordings, A/B tests, feedback board and surveys that your MRRocket agents can run and read. Next.js today (@mrrocket/sdk/next), more frameworks next.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|