@mrrocket/sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +250 -2
- package/dist/api.d.ts +113 -0
- package/dist/api.js +71 -0
- package/dist/api.js.map +1 -0
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +25 -0
- package/dist/browser.js.map +1 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.js +373 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +31 -0
- package/dist/context.js +15 -0
- package/dist/context.js.map +1 -0
- package/dist/core.d.ts +107 -0
- package/dist/core.js +104 -0
- package/dist/core.js.map +1 -0
- package/dist/feedback.d.ts +28 -0
- package/dist/feedback.js +459 -0
- package/dist/feedback.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +28 -0
- package/dist/proxy.js +12 -0
- package/dist/proxy.js.map +1 -0
- package/dist/referral.d.ts +86 -0
- package/dist/referral.js +128 -0
- package/dist/referral.js.map +1 -0
- package/dist/server.d.ts +47 -0
- package/dist/server.js +43 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.d.ts +4 -0
- package/dist/styles.js +147 -0
- package/dist/styles.js.map +1 -0
- package/dist/survey.d.ts +4 -0
- package/dist/survey.js +201 -0
- package/dist/survey.js.map +1 -0
- package/dist/ui.d.ts +29 -0
- package/dist/ui.js +77 -0
- package/dist/ui.js.map +1 -0
- package/package.json +84 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package. It follows semantic versioning; before 1.0 a minor version may change an API.
|
|
4
|
+
|
|
5
|
+
## 0.1.0
|
|
6
|
+
|
|
7
|
+
First public release on npm as `@mrrocket/sdk`, MIT licensed. Next.js lives under `@mrrocket/sdk/next`
|
|
8
|
+
(`/next/server`, `/next/proxy`); the package root is kept for a framework-free core.
|
|
9
|
+
|
|
10
|
+
- Affiliate referrals: `?mrr_ref`/`mrr_click` (tracked links) and `?ref` (direct links) are kept in a first-party `mrr_ref` cookie following consent (`attribution` prop), a click beacon goes to `/api/sdk/affiliate/click`, `$referral` rides on `identify()` and own events. New exports `getReferral`, `referralForCheckout`, `referralLink`, `readReferralCookie`. Tests: `npm test`.
|
|
11
|
+
|
|
12
|
+
## 0.0.1 (never published)
|
|
13
|
+
|
|
14
|
+
First installable build, compiled to JavaScript (ESM) with type declarations: no `transpilePackages` needed.
|
|
15
|
+
|
|
16
|
+
- Page views, `data-ck-goal` clicks, `track`, `identify`, UTM and referrer capture, batched events.
|
|
17
|
+
- Consent modes `cookieless` (default), `cookies`, `off`, `respectDnt`.
|
|
18
|
+
- Experiments: `useVariant`, `<Experiment>`, `<Text>`, server helpers `getCockpitConfig`, `getVariant`, `getTexts`,
|
|
19
|
+
`previewFrom` (`/next/server`), `withCockpitId` (`/next/proxy`), preview links for MRRocket.
|
|
20
|
+
- `<FeedbackBoard>`, `<FeedbackWidget>`, `useFeedback`, `<Survey>` (automatic via the provider).
|
|
21
|
+
- `host` and `apiKey` default to `NEXT_PUBLIC_MRROCKET_HOST` and `NEXT_PUBLIC_MRROCKET_KEY`.
|
|
22
|
+
- Server helpers run on Node, Edge and Cloudflare Workers; `getCockpitConfig` times out after 2.5 s and returns `null`
|
|
23
|
+
(control variants) when MRRocket cannot be reached.
|
|
24
|
+
- Console warnings (once) when the host or key is wrong.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brombach Technologies GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,251 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mrrocket/sdk
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
The MRRocket SDK. This release covers Next.js (App Router), imported from `@mrrocket/sdk/next`; other frameworks get their own entry in the same package (`@mrrocket/sdk/react` is next). One provider in your layout gives MRRocket page views, your events and A/B tests that the agents can run. MRRocket shows every variant of your real pages, because the SDK can render any variant on request.
|
|
4
|
+
|
|
5
|
+
Status: 0.1.0, MIT licensed, compiled to ESM JavaScript with type declarations (no `transpilePackages`). Works with Next.js 15 and 16, React 19, on Node and on Cloudflare Workers (OpenNext). The source lives in the MRRocket repo (`prototype/web/packages/cockpit-next`).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @mrrocket/sdk # or npm install / yarn add
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Your public key is in MRRocket › Settings › Products, one per product (`mrr_pub_…`, or `ck_pub_…` for a product without a stored key). It is safe in the browser, like PostHog's project key. Never put a secret key in client code.
|
|
14
|
+
|
|
15
|
+
Two environment variables, both public (Next.js inlines `NEXT_PUBLIC_*` at build time, so they must exist when you run `next build`):
|
|
16
|
+
|
|
17
|
+
| Variable | What | Default |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `NEXT_PUBLIC_MRROCKET_HOST` | Where MRRocket runs, for example `https://app.example.com` (no path) | the page's own origin |
|
|
20
|
+
| `NEXT_PUBLIC_MRROCKET_KEY` | The product's public key | none: the SDK warns in the console and sends nothing |
|
|
21
|
+
|
|
22
|
+
`host` and `apiKey` props override them. The server answers every `/api/sdk` request with open CORS headers, so any website origin may call it.
|
|
23
|
+
|
|
24
|
+
## 1. Provider in the root layout
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// app/layout.tsx
|
|
28
|
+
import { CockpitProvider } from "@mrrocket/sdk/next";
|
|
29
|
+
|
|
30
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
31
|
+
return (
|
|
32
|
+
<html lang="en">
|
|
33
|
+
<body>
|
|
34
|
+
<CockpitProvider>
|
|
35
|
+
{children}
|
|
36
|
+
</CockpitProvider>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
From then on every route change is a `$pageview`, and clicks on elements with `data-ck-goal` count as that goal. By default the SDK is cookieless: it stores nothing in the browser (see [Privacy and consent](#privacy-and-consent)).
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<button data-ck-goal="signup_started">Start free trial</button>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 2. Your own events
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
"use client";
|
|
53
|
+
import { useCockpit } from "@mrrocket/sdk/next";
|
|
54
|
+
|
|
55
|
+
const { track, identify } = useCockpit();
|
|
56
|
+
track("checkout_started", { plan: "team" });
|
|
57
|
+
identify(user.id, { plan: user.plan });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 3. A/B tests
|
|
61
|
+
|
|
62
|
+
MRRocket's agents create experiments; the key is shown on each test in MRRocket.
|
|
63
|
+
|
|
64
|
+
Swap a whole block:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { Experiment } from "@mrrocket/sdk/next";
|
|
68
|
+
|
|
69
|
+
<Experiment name="rr-home-logos" variants={{ control: <Hero />, a: <HeroWithLogos /> }} />;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or ask for the variant:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
const variant = useVariant("rr-pricing-annual-first"); // "control" | "a"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Let tests change copy without a deploy: wrap the text once, and MRRocket can send new words for it.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<h1><Text id="home.headline">Sales training that sticks</Text></h1>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Give each text an id that is unique across your site (`home.headline`, `pricing.cta`), because a running test changes that id wherever it appears. Control never changes text: your defaults stay.
|
|
85
|
+
|
|
86
|
+
With `consent="cookies"` every visitor stays in one variant across visits (the `ck_id` cookie). Cookieless, the variant holds for client-side navigation but can change on a reload, unless you pass your own `distinctId` or call `identify()`. Exposures are recorded as `$exposure` with the experiment and the variant.
|
|
87
|
+
|
|
88
|
+
## 4. No flicker: render the variant on the server
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// app/pricing/page.tsx
|
|
92
|
+
import { cookies } from "next/headers";
|
|
93
|
+
import { CockpitProvider } from "@mrrocket/sdk/next";
|
|
94
|
+
import { getCockpitConfig, getVariant, previewFrom } from "@mrrocket/sdk/next/server";
|
|
95
|
+
|
|
96
|
+
export default async function Page({ searchParams }: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {
|
|
97
|
+
const sp = await searchParams;
|
|
98
|
+
const config = await getCockpitConfig(); // key and host from the environment; pass { apiKey, host } to override
|
|
99
|
+
const variant = getVariant("rr-pricing-annual-first", { config, searchParams: sp, cookies: await cookies() });
|
|
100
|
+
return (
|
|
101
|
+
<CockpitProvider config={config ?? undefined} {...previewFrom(sp)}>
|
|
102
|
+
{variant === "a" ? <YearlyFirst /> : <MonthlyFirst />}
|
|
103
|
+
</CockpitProvider>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`getCockpitConfig` waits at most 2.5 seconds (`timeoutMs`) and returns `null` when MRRocket is unreachable, slow or answers an error: the page then renders with control variants and never throws.
|
|
109
|
+
|
|
110
|
+
The server needs an id it can read. Cookieless (the default) there is none, so pass your own, for example the signed-in user's id, to both sides: `getVariant(key, { config, searchParams: sp, distinctId: user.id })` and `<CockpitProvider distinctId={user.id}>`.
|
|
111
|
+
|
|
112
|
+
With `consent="cookies"`, give first-time visitors their `ck_id` before the page renders. This writes a cookie, so only do it once the visitor agreed:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// proxy.ts (Next.js 16; middleware.ts before)
|
|
116
|
+
import { NextResponse, type NextRequest } from "next/server";
|
|
117
|
+
import { withCockpitId } from "@mrrocket/sdk/next/proxy";
|
|
118
|
+
|
|
119
|
+
export function proxy(request: NextRequest) {
|
|
120
|
+
const response = NextResponse.next();
|
|
121
|
+
withCockpitId(request.cookies, response.cookies);
|
|
122
|
+
return response;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 5. Cloudflare Workers (OpenNext)
|
|
127
|
+
|
|
128
|
+
The server helpers (`getCockpitConfig`, `getVariant`, `getTexts`, `previewFrom`) and the proxy helper use only `fetch`, `AbortController` and plain objects, so they run on Node, the Edge runtime and Workers via `@opennextjs/cloudflare`. Put the two variables where `next build` reads them (`.env`, `.env.production`, or your CI environment), because Next.js inlines `NEXT_PUBLIC_*` during the build, not at request time.
|
|
129
|
+
|
|
130
|
+
## 6. Previews in MRRocket
|
|
131
|
+
|
|
132
|
+
MRRocket shows each variant of your page by loading it with `?ck_preview=<experiment>:<variant>&ck_embed=1`. The SDK renders that variant and tracks nothing while embedded, so previews never count as visits. If your site sends `X-Frame-Options: DENY` or a strict `frame-ancestors`, allow your MRRocket domain.
|
|
133
|
+
|
|
134
|
+
## 7. Feedback board
|
|
135
|
+
|
|
136
|
+
A Canny-style board for your own `/feedback` page: requests with votes and comments, a roadmap and a changelog. Visitors vote once; a duplicate hint shows similar requests while they type.
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
// app/feedback/page.tsx
|
|
140
|
+
import { FeedbackBoard } from "@mrrocket/sdk/next";
|
|
141
|
+
|
|
142
|
+
export default function Page() {
|
|
143
|
+
return <FeedbackBoard title="Feedback" theme="auto" accent="#3f76ff" />;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
It must sit inside `CockpitProvider`. Call `identify(user.id, { name, email })` and comments and requests carry the user. MRRocket also hosts the same board at `/b/<productId>`.
|
|
148
|
+
|
|
149
|
+
## 8. Feedback widget
|
|
150
|
+
|
|
151
|
+
A floating button with the top five requests and a short suggest form.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { FeedbackWidget, useFeedback } from "@mrrocket/sdk/next";
|
|
155
|
+
|
|
156
|
+
<FeedbackWidget position="bottom-right" label="Feedback" boardUrl="/feedback" />;
|
|
157
|
+
|
|
158
|
+
// Open it from your own button:
|
|
159
|
+
const { open, close } = useFeedback();
|
|
160
|
+
<button onClick={open}>Send feedback</button>;
|
|
161
|
+
|
|
162
|
+
// Or with no code at all:
|
|
163
|
+
<a data-ck-feedback>Feedback</a>;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 9. Surveys
|
|
167
|
+
|
|
168
|
+
Surveys you build in MRRocket (NPS, rating, choice, text) appear automatically: three seconds after the page opens, a small card shows bottom-left (a bottom sheet on phones). Nothing shows inside MRRocket previews or with `consent="off"`. A dismissed survey stays away for that visitor: across visits with `consent="cookies"` (localStorage), otherwise for the page load.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<CockpitProvider apiKey={key} surveys={false}>{children}</CockpitProvider> // switch surveys off
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Widgets style themselves with one scoped stylesheet (class prefix `ckw-`), light and dark by `prefers-color-scheme`. Use `theme="light" | "dark" | "auto"` and `accent` to adjust.
|
|
175
|
+
|
|
176
|
+
## 10. Affiliate referrals
|
|
177
|
+
|
|
178
|
+
When the product runs an affiliate program in MRRocket, the provider picks up partner referrals by itself: `?mrr_ref=<code>&mrr_click=<id>` from a partner's tracked link (`<mrrocket>/r/<code>`) or `?ref=<code>` from a direct link. It keeps the referral in a first-party cookie `mrr_ref` on your domain plus localStorage (for the program's days, default 60), sends a click beacon for direct links, and adds `$referral: { code, clickId }` to `identify()` and your own events.
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import { getReferral, referralForCheckout, referralLink } from "@mrrocket/sdk/next";
|
|
182
|
+
|
|
183
|
+
getReferral(); // { code, clickId, at } | null
|
|
184
|
+
referralForCheckout(); // { client_reference_id: "mrr_ref:<code>:<clickId>", metadata: { mrr_ref, mrr_click } } | null
|
|
185
|
+
referralLink("https://buy.stripe.com/abc"); // a Payment Link with client_reference_id=mrr_ref__<code>__<clickId>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
On the server (the route that creates a Stripe Checkout Session):
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { readReferralCookie, referralForCheckout } from "@mrrocket/sdk/next/server";
|
|
192
|
+
const ref = referralForCheckout(readReferralCookie((await cookies()).get("mrr_ref")?.value));
|
|
193
|
+
await stripe.checkout.sessions.create({ ...params, ...(ref ?? {}), subscription_data: { metadata: ref?.metadata ?? {} } });
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Apps with RevenueCat: set the subscriber attribute `$mrr_ref` (and `$mrr_click`), or give the partner an App Store offer code equal to their code.
|
|
197
|
+
|
|
198
|
+
| Prop | Default | What |
|
|
199
|
+
| --- | --- | --- |
|
|
200
|
+
| `attribution` | `"consent"` | `"consent"`: stored only while `consent="cookies"`, otherwise in memory for the page load; `"always"`: stored whatever the consent (only with a legal basis); `"off"`: referrals ignored |
|
|
201
|
+
| `referralParam` | `"ref"` | the query parameter of direct links |
|
|
202
|
+
|
|
203
|
+
The referral cookie is a marketing cookie: where the law asks for consent (EU, UK), it needs it. Withdrawing consent (leaving `"cookies"`) deletes it.
|
|
204
|
+
|
|
205
|
+
## Privacy and consent
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
<CockpitProvider apiKey={key} consent="cookieless" respectDnt>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
| `consent` | Stored in the browser | Sent |
|
|
212
|
+
| --- | --- | --- |
|
|
213
|
+
| `"cookieless"` (default) | Nothing: no cookie, no localStorage, no sessionStorage | Events without a visitor id |
|
|
214
|
+
| `"cookies"` | `ck_id` (1 year), `ck_sid` (30 minutes), dismissed surveys, `mrr_ref` (affiliate referral, program days) | Events with `ck_id` as `distinctId` |
|
|
215
|
+
| `"off"` | Nothing | Nothing, and no surveys |
|
|
216
|
+
|
|
217
|
+
- **Cookieless:** the server counts visitors with a hash of key, date, IP and user agent that changes every day, like Plausible; the IP is never stored. A visitor coming back on another day counts again, so retention needs `identify()` or cookies. After `identify(id)` events carry that id. Experiments, surveys and feedback use your `distinctId` prop, else a random id in memory for the page load: variants hold across client-side navigation, not across reloads, and a dismissed survey can come back after a reload.
|
|
218
|
+
- **Changing it:** `const { consent, setConsent } = useCockpit()`. From `"off"` to another mode, tracking starts and the current page view is sent right away. Leaving `"cookies"` deletes `ck_id`, `ck_sid` and the stored survey dismissals; `"off"` also drops unsent events. If the `consent` prop changes, the SDK follows it.
|
|
219
|
+
- **The SDK does not remember the choice.** Your consent banner stores it and calls `setConsent()` on every page load.
|
|
220
|
+
- **`respectDnt`** (default false): when the browser sends Do Not Track or Global Privacy Control, nothing is sent or stored, whatever the consent.
|
|
221
|
+
- **Sessions** end after 30 minutes without an event: in the `ck_sid` cookie with `"cookies"`, in memory otherwise (a reload starts a new session id).
|
|
222
|
+
- Previews inside MRRocket (`ck_embed=1`) track nothing.
|
|
223
|
+
|
|
224
|
+
## What it sends
|
|
225
|
+
|
|
226
|
+
`POST {host}/api/sdk/events`, batched every two seconds and on page hide, at most 100 events and 64 KB per request:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
{
|
|
230
|
+
key: string;
|
|
231
|
+
events: {
|
|
232
|
+
event: string;
|
|
233
|
+
distinctId?: string; // ck_id or the identify() id; missing cookieless until identify()
|
|
234
|
+
sessionId?: string;
|
|
235
|
+
props?: Record<string, unknown>; // page views carry { path }
|
|
236
|
+
ts?: number; // ms
|
|
237
|
+
url?: string; // location.href
|
|
238
|
+
referrer?: string; // document.referrer on the first page, then the previous in-app URL
|
|
239
|
+
utm_source?: string; // utm_* from the landing URL, kept for the page load
|
|
240
|
+
utm_medium?: string;
|
|
241
|
+
utm_campaign?: string;
|
|
242
|
+
utm_term?: string;
|
|
243
|
+
utm_content?: string;
|
|
244
|
+
screen?: { w: number; h: number };
|
|
245
|
+
}[];
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Answers `202 { ok: true, received, dropped }`; `400` bad body, `404` unknown key, `413` too large, `429` over 600 requests a minute per key. The server adds the country (from `x-vercel-ip-country` or `cf-ipcountry`) and browser, OS and device (from the user agent); the IP only feeds the daily hash. Bot user agents are not stored. `signup`, `checkout` and `purchase` feed Web analytics, `$identify` creates a person in People. Raw events are kept 90 days.
|
|
250
|
+
|
|
251
|
+
`GET {host}/api/sdk/config?key=…` returns the experiments for the key.
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export type PostStatus = "review" | "planned" | "progress" | "shipped";
|
|
2
|
+
export declare const STATUS_LABEL: Record<PostStatus, string>;
|
|
3
|
+
export interface FeedbackBoardInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
export interface FeedbackPost {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
body: string;
|
|
11
|
+
board: string;
|
|
12
|
+
status: PostStatus;
|
|
13
|
+
votes: number;
|
|
14
|
+
comments: number;
|
|
15
|
+
voted: boolean;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
18
|
+
export interface FeedbackList {
|
|
19
|
+
boards: FeedbackBoardInfo[];
|
|
20
|
+
posts: FeedbackPost[];
|
|
21
|
+
}
|
|
22
|
+
export interface FeedbackRoadmap {
|
|
23
|
+
columns: {
|
|
24
|
+
planned: FeedbackPost[];
|
|
25
|
+
progress: FeedbackPost[];
|
|
26
|
+
shipped: FeedbackPost[];
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface ChangelogEntry {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
body: string;
|
|
33
|
+
publishedAt: string;
|
|
34
|
+
}
|
|
35
|
+
export interface FeedbackComment {
|
|
36
|
+
id: string;
|
|
37
|
+
body: string;
|
|
38
|
+
author: string;
|
|
39
|
+
isTeam: boolean;
|
|
40
|
+
postedAt: string;
|
|
41
|
+
}
|
|
42
|
+
export type SurveyQuestionKind = "nps" | "rating" | "single" | "multi" | "text";
|
|
43
|
+
export interface SurveyQuestion {
|
|
44
|
+
key: string;
|
|
45
|
+
kind: SurveyQuestionKind;
|
|
46
|
+
title: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
required: boolean;
|
|
49
|
+
options?: string[];
|
|
50
|
+
scale?: number;
|
|
51
|
+
low?: string;
|
|
52
|
+
high?: string;
|
|
53
|
+
placeholder?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface SurveyDefinition {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
type: string;
|
|
59
|
+
thanks: string;
|
|
60
|
+
questions: SurveyQuestion[];
|
|
61
|
+
}
|
|
62
|
+
export type SurveyAnswer = number | string | string[];
|
|
63
|
+
export declare class ApiError extends Error {
|
|
64
|
+
status: number;
|
|
65
|
+
constructor(message: string, status: number);
|
|
66
|
+
}
|
|
67
|
+
interface Conn {
|
|
68
|
+
host: string;
|
|
69
|
+
apiKey: string;
|
|
70
|
+
distinctId: string;
|
|
71
|
+
user?: {
|
|
72
|
+
id: string;
|
|
73
|
+
[key: string]: unknown;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** The small fetch client behind the widgets. Every read takes an AbortSignal so unmounting cancels it. */
|
|
77
|
+
export declare const createApi: (c: Conn) => {
|
|
78
|
+
list: (o: {
|
|
79
|
+
board?: string;
|
|
80
|
+
sort?: "top" | "new";
|
|
81
|
+
q?: string;
|
|
82
|
+
}, signal?: AbortSignal) => Promise<FeedbackList>;
|
|
83
|
+
roadmap: (signal?: AbortSignal) => Promise<FeedbackRoadmap>;
|
|
84
|
+
changelog: (signal?: AbortSignal) => Promise<{
|
|
85
|
+
entries: ChangelogEntry[];
|
|
86
|
+
}>;
|
|
87
|
+
submit: (p: {
|
|
88
|
+
title: string;
|
|
89
|
+
body?: string;
|
|
90
|
+
board: string;
|
|
91
|
+
}) => Promise<FeedbackPost>;
|
|
92
|
+
vote: (id: string) => Promise<{
|
|
93
|
+
voted: boolean;
|
|
94
|
+
votes: number;
|
|
95
|
+
}>;
|
|
96
|
+
comments: (id: string, signal?: AbortSignal) => Promise<{
|
|
97
|
+
comments: FeedbackComment[];
|
|
98
|
+
}>;
|
|
99
|
+
comment: (id: string, body: string) => Promise<FeedbackComment>;
|
|
100
|
+
survey: (url: string, signal?: AbortSignal) => Promise<{
|
|
101
|
+
survey: SurveyDefinition | null;
|
|
102
|
+
}>;
|
|
103
|
+
seen: (id: string) => Promise<unknown>;
|
|
104
|
+
respond: (id: string, r: {
|
|
105
|
+
answers: Record<string, SurveyAnswer>;
|
|
106
|
+
completed: boolean;
|
|
107
|
+
seconds: number;
|
|
108
|
+
}) => Promise<unknown>;
|
|
109
|
+
};
|
|
110
|
+
export type Api = ReturnType<typeof createApi>;
|
|
111
|
+
/** The client for the provider's key, host and visitor. */
|
|
112
|
+
export declare const useApi: () => Api;
|
|
113
|
+
export {};
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/api.ts
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useCtx } from "./context.js";
|
|
4
|
+
var STATUS_LABEL = {
|
|
5
|
+
review: "Under review",
|
|
6
|
+
planned: "Planned",
|
|
7
|
+
progress: "In progress",
|
|
8
|
+
shipped: "Shipped"
|
|
9
|
+
};
|
|
10
|
+
var ApiError = class extends Error {
|
|
11
|
+
status;
|
|
12
|
+
constructor(message, status) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.status = status;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var qs = (params) => {
|
|
18
|
+
const p = new URLSearchParams();
|
|
19
|
+
for (const [k, v] of Object.entries(params)) if (v) p.set(k, v);
|
|
20
|
+
return p.toString();
|
|
21
|
+
};
|
|
22
|
+
var call = async (url, init, signal) => {
|
|
23
|
+
let res;
|
|
24
|
+
try {
|
|
25
|
+
res = await fetch(url, {
|
|
26
|
+
...init,
|
|
27
|
+
signal,
|
|
28
|
+
headers: init?.body ? { "content-type": "application/json" } : void 0
|
|
29
|
+
});
|
|
30
|
+
} catch (e) {
|
|
31
|
+
if (e.name === "AbortError") throw e;
|
|
32
|
+
throw new ApiError("Could not reach the server. Check your connection and try again.", 0);
|
|
33
|
+
}
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
let detail = "";
|
|
36
|
+
try {
|
|
37
|
+
detail = (await res.json()).error ?? "";
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
throw new ApiError(detail || (res.status === 429 ? "Too many requests. Try again in a minute." : "Something went wrong. Please try again."), res.status);
|
|
41
|
+
}
|
|
42
|
+
return await res.json();
|
|
43
|
+
};
|
|
44
|
+
var createApi = (c) => {
|
|
45
|
+
const base = `${c.host}/api/sdk`;
|
|
46
|
+
const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user };
|
|
47
|
+
const post = (path, body, signal) => call(`${base}${path}`, { method: "POST", body: JSON.stringify({ ...who, ...body }) }, signal);
|
|
48
|
+
return {
|
|
49
|
+
list: (o, signal) => call(`${base}/feedback?${qs({ key: c.apiKey, distinctId: c.distinctId, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
|
|
50
|
+
roadmap: (signal) => call(`${base}/feedback/roadmap?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
51
|
+
changelog: (signal) => call(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
52
|
+
submit: (p) => post("/feedback", p),
|
|
53
|
+
vote: (id) => post(`/feedback/${encodeURIComponent(id)}/vote`, {}),
|
|
54
|
+
comments: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
55
|
+
comment: (id, body) => post(`/feedback/${encodeURIComponent(id)}/comments`, { body }),
|
|
56
|
+
survey: (url, signal) => call(`${base}/surveys?${qs({ key: c.apiKey, distinctId: c.distinctId, url })}`, void 0, signal),
|
|
57
|
+
seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
|
|
58
|
+
respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r)
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
var useApi = () => {
|
|
62
|
+
const { apiKey, host, distinctId, user } = useCtx();
|
|
63
|
+
return useMemo(() => createApi({ apiKey, host, distinctId, user }), [apiKey, host, distinctId, user]);
|
|
64
|
+
};
|
|
65
|
+
export {
|
|
66
|
+
ApiError,
|
|
67
|
+
STATUS_LABEL,
|
|
68
|
+
createApi,
|
|
69
|
+
useApi
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useCtx } from \"./context\";\n\nexport type PostStatus = \"review\" | \"planned\" | \"progress\" | \"shipped\";\n\nexport const STATUS_LABEL: Record<PostStatus, string> = {\n review: \"Under review\",\n planned: \"Planned\",\n progress: \"In progress\",\n shipped: \"Shipped\",\n};\n\nexport interface FeedbackBoardInfo {\n id: string;\n label: string;\n}\n\nexport interface FeedbackPost {\n id: string;\n title: string;\n body: string;\n board: string;\n status: PostStatus;\n votes: number;\n comments: number;\n voted: boolean;\n createdAt: string;\n}\n\nexport interface FeedbackList {\n boards: FeedbackBoardInfo[];\n posts: FeedbackPost[];\n}\n\nexport interface FeedbackRoadmap {\n columns: { planned: FeedbackPost[]; progress: FeedbackPost[]; shipped: FeedbackPost[] };\n}\n\nexport interface ChangelogEntry {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n}\n\nexport interface FeedbackComment {\n id: string;\n body: string;\n author: string;\n isTeam: boolean;\n postedAt: string;\n}\n\nexport type SurveyQuestionKind = \"nps\" | \"rating\" | \"single\" | \"multi\" | \"text\";\n\nexport interface SurveyQuestion {\n key: string;\n kind: SurveyQuestionKind;\n title: string;\n description?: string;\n required: boolean;\n options?: string[];\n scale?: number;\n low?: string;\n high?: string;\n placeholder?: string;\n}\n\nexport interface SurveyDefinition {\n id: string;\n name: string;\n type: string;\n thanks: string;\n questions: SurveyQuestion[];\n}\n\nexport type SurveyAnswer = number | string | string[];\n\nexport class ApiError extends Error {\n status: number;\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\ninterface Conn {\n host: string;\n apiKey: string;\n distinctId: string;\n user?: { id: string; [key: string]: unknown };\n}\n\nconst qs = (params: Record<string, string | undefined>) => {\n const p = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) if (v) p.set(k, v);\n return p.toString();\n};\n\nconst call = async <T>(url: string, init: RequestInit | undefined, signal?: AbortSignal): Promise<T> => {\n let res: Response;\n try {\n res = await fetch(url, {\n ...init,\n signal,\n headers: init?.body ? { \"content-type\": \"application/json\" } : undefined,\n });\n } catch (e) {\n if ((e as Error).name === \"AbortError\") throw e;\n throw new ApiError(\"Could not reach the server. Check your connection and try again.\", 0);\n }\n if (!res.ok) {\n let detail = \"\";\n try {\n detail = ((await res.json()) as { error?: string }).error ?? \"\";\n } catch {\n // Not JSON; the status is enough.\n }\n throw new ApiError(detail || (res.status === 429 ? \"Too many requests. Try again in a minute.\" : \"Something went wrong. Please try again.\"), res.status);\n }\n return (await res.json()) as T;\n};\n\n/** The small fetch client behind the widgets. Every read takes an AbortSignal so unmounting cancels it. */\nexport const createApi = (c: Conn) => {\n const base = `${c.host}/api/sdk`;\n const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user };\n const post = <T>(path: string, body: Record<string, unknown>, signal?: AbortSignal) =>\n call<T>(`${base}${path}`, { method: \"POST\", body: JSON.stringify({ ...who, ...body }) }, signal);\n return {\n list: (o: { board?: string; sort?: \"top\" | \"new\"; q?: string }, signal?: AbortSignal) =>\n call<FeedbackList>(`${base}/feedback?${qs({ key: c.apiKey, distinctId: c.distinctId, board: o.board, sort: o.sort, q: o.q })}`, undefined, signal),\n roadmap: (signal?: AbortSignal) => call<FeedbackRoadmap>(`${base}/feedback/roadmap?${qs({ key: c.apiKey })}`, undefined, signal),\n changelog: (signal?: AbortSignal) => call<{ entries: ChangelogEntry[] }>(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, undefined, signal),\n submit: (p: { title: string; body?: string; board: string }) => post<FeedbackPost>(\"/feedback\", p),\n vote: (id: string) => post<{ voted: boolean; votes: number }>(`/feedback/${encodeURIComponent(id)}/vote`, {}),\n comments: (id: string, signal?: AbortSignal) =>\n call<{ comments: FeedbackComment[] }>(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, undefined, signal),\n comment: (id: string, body: string) => post<FeedbackComment>(`/feedback/${encodeURIComponent(id)}/comments`, { body }),\n survey: (url: string, signal?: AbortSignal) =>\n call<{ survey: SurveyDefinition | null }>(`${base}/surveys?${qs({ key: c.apiKey, distinctId: c.distinctId, url })}`, undefined, signal),\n seen: (id: string) => post<unknown>(`/surveys/${encodeURIComponent(id)}/seen`, { user: undefined }),\n respond: (id: string, r: { answers: Record<string, SurveyAnswer>; completed: boolean; seconds: number }) =>\n post<unknown>(`/surveys/${encodeURIComponent(id)}/respond`, r),\n };\n};\n\nexport type Api = ReturnType<typeof createApi>;\n\n/** The client for the provider's key, host and visitor. */\nexport const useApi = (): Api => {\n const { apiKey, host, distinctId, user } = useCtx();\n return useMemo(() => createApi({ apiKey, host, distinctId, user }), [apiKey, host, distinctId, user]);\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything of the SDK that needs the browser or React state. It is its own file with the "use client" directive so
|
|
3
|
+
* that the build keeps one client chunk: server components import these as client references, while the plain
|
|
4
|
+
* helpers (`core`, `server`, `proxy`) stay free of the directive.
|
|
5
|
+
*/
|
|
6
|
+
export { CockpitProvider, Experiment, Text, useCockpit, useVariant, type CockpitProviderProps } from "./client.js";
|
|
7
|
+
export { FeedbackBoard, FeedbackWidget, useFeedback, type FeedbackBoardProps, type FeedbackWidgetProps } from "./feedback.js";
|
|
8
|
+
export { Survey } from "./survey.js";
|
|
9
|
+
export type { CockpitUser } from "./context.js";
|
|
10
|
+
export { type ChangelogEntry, type FeedbackBoardInfo, type FeedbackComment, type FeedbackList, type FeedbackPost, type FeedbackRoadmap, type PostStatus, type SurveyAnswer, type SurveyDefinition, type SurveyQuestion, type SurveyQuestionKind, createApi, } from "./api.js";
|
|
11
|
+
export { CSS as WIDGET_CSS, useStyles } from "./styles.js";
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/browser.ts
|
|
4
|
+
import { CockpitProvider, Experiment, Text, useCockpit, useVariant } from "./client.js";
|
|
5
|
+
import { FeedbackBoard, FeedbackWidget, useFeedback } from "./feedback.js";
|
|
6
|
+
import { Survey } from "./survey.js";
|
|
7
|
+
import {
|
|
8
|
+
createApi
|
|
9
|
+
} from "./api.js";
|
|
10
|
+
import { CSS, useStyles } from "./styles.js";
|
|
11
|
+
export {
|
|
12
|
+
CockpitProvider,
|
|
13
|
+
Experiment,
|
|
14
|
+
FeedbackBoard,
|
|
15
|
+
FeedbackWidget,
|
|
16
|
+
Survey,
|
|
17
|
+
Text,
|
|
18
|
+
CSS as WIDGET_CSS,
|
|
19
|
+
createApi,
|
|
20
|
+
useCockpit,
|
|
21
|
+
useFeedback,
|
|
22
|
+
useStyles,
|
|
23
|
+
useVariant
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +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;AACrG,SAAS,eAAe,gBAAgB,mBAAsE;AAC9G,SAAS,cAAc;AAEvB;AAAA,EAYI;AAAA,OACG;AACP,SAAgB,KAAY,iBAAiB;","names":[]}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type AttributionMode } from "./referral.js";
|
|
3
|
+
import { type CockpitConfig, type ConsentMode, type VariantId } from "./core.js";
|
|
4
|
+
export interface CockpitProviderProps {
|
|
5
|
+
/** The product's public key from Cockpit › Settings › SDK. Safe in the browser. Default: `NEXT_PUBLIC_MRROCKET_KEY`. */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
/** Where MRRocket runs, like `https://app.example.com`. Default: `NEXT_PUBLIC_MRROCKET_HOST`, else the page's own origin. */
|
|
8
|
+
host?: string;
|
|
9
|
+
preview?: Record<string, VariantId>;
|
|
10
|
+
embedded?: boolean;
|
|
11
|
+
/** Config fetched on the server, to render the right variant on the first paint. */
|
|
12
|
+
config?: CockpitConfig;
|
|
13
|
+
/** Your own id for this visitor, for example a signed-in user id. Keeps variants stable without cookies. */
|
|
14
|
+
distinctId?: string;
|
|
15
|
+
/**
|
|
16
|
+
* "cookieless" (default) stores nothing in the browser, "cookies" keeps a visitor cookie for a year, "off" sends
|
|
17
|
+
* nothing. The SDK does not remember the choice: set it on every page load, from the prop or setConsent().
|
|
18
|
+
*/
|
|
19
|
+
consent?: ConsentMode;
|
|
20
|
+
/** Send nothing when the browser asks not to be tracked (Do Not Track or Global Privacy Control). */
|
|
21
|
+
respectDnt?: boolean;
|
|
22
|
+
/** Ask visitors the surveys you run in Cockpit. Set false to switch them off. */
|
|
23
|
+
surveys?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Affiliate referrals (`?ref=` links and MRRocket's tracked links). "consent" (default): stored in the first-party
|
|
26
|
+
* `mrr_ref` cookie only while consent is "cookies", in memory otherwise. "always": stored whatever the consent (only
|
|
27
|
+
* where your legal basis allows it). "off": referrals are ignored.
|
|
28
|
+
*/
|
|
29
|
+
attribution?: AttributionMode;
|
|
30
|
+
/** The query parameter of direct partner links, default `ref` (`https://your.site/?ref=maya`). */
|
|
31
|
+
referralParam?: string;
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Put it once around your app, in `app/layout.tsx`. It loads your experiments, keeps each visitor in one variant,
|
|
36
|
+
* records page views and sends your events to Cockpit in small batches.
|
|
37
|
+
*
|
|
38
|
+
* `preview` forces variants for this render, for example from the `ck_preview` search param on the server,
|
|
39
|
+
* so a Cockpit preview never flickers. Without it the provider reads the param itself after the first render.
|
|
40
|
+
*/
|
|
41
|
+
export declare const CockpitProvider: ({ apiKey: apiKeyProp, host: hostProp, preview: forced, embedded: embeddedProp, config: bootstrapConfig, distinctId: bootstrapId, consent: consentProp, respectDnt, surveys, attribution, referralParam, children, }: CockpitProviderProps) => import("react").JSX.Element;
|
|
42
|
+
/** `track`, `identify`, the consent mode and its setter, the visitor id and whether the experiments have loaded. */
|
|
43
|
+
export declare const useCockpit: () => {
|
|
44
|
+
track: (event: string, props?: Record<string, unknown>) => void;
|
|
45
|
+
identify: (id: string, props?: Record<string, unknown>) => void;
|
|
46
|
+
distinctId: string;
|
|
47
|
+
ready: boolean;
|
|
48
|
+
embedded: boolean;
|
|
49
|
+
consent: ConsentMode;
|
|
50
|
+
setConsent: (mode: ConsentMode) => void;
|
|
51
|
+
};
|
|
52
|
+
/** The variant this visitor sees, for example `"control"` or `"a"`. Records the exposure. */
|
|
53
|
+
export declare const useVariant: (key: string) => VariantId;
|
|
54
|
+
/** Renders the element for the visitor's variant; control when the variant has no element. */
|
|
55
|
+
export declare const Experiment: ({ name, variants }: {
|
|
56
|
+
name: string;
|
|
57
|
+
variants: Record<VariantId, ReactNode> & {
|
|
58
|
+
control: ReactNode;
|
|
59
|
+
};
|
|
60
|
+
}) => import("react").JSX.Element;
|
|
61
|
+
/**
|
|
62
|
+
* A text that experiments may change without a deploy: Cockpit sends the new copy for `id` and this renders it.
|
|
63
|
+
* Write your default as the children.
|
|
64
|
+
*/
|
|
65
|
+
export declare const Text: ({ id, children }: {
|
|
66
|
+
id: string;
|
|
67
|
+
children: string;
|
|
68
|
+
}) => import("react").JSX.Element;
|