@mrrocket/sdk 0.14.0 → 0.16.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +45 -4
  3. package/dist/api-client.d.ts +67 -5
  4. package/dist/api-client.js +39 -8
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/api.d.ts +4 -1
  7. package/dist/api.js +2 -1
  8. package/dist/api.js.map +1 -1
  9. package/dist/batch.d.ts +6 -4
  10. package/dist/batch.js +3 -3
  11. package/dist/batch.js.map +1 -1
  12. package/dist/browser/mrrocket-flows.js +2 -2
  13. package/dist/browser/mrrocket-forms.js +1 -1
  14. package/dist/browser/mrrocket-messenger.js +202 -46
  15. package/dist/browser/mrrocket-push.js +1 -1
  16. package/dist/browser/mrrocket-replay.js +3 -3
  17. package/dist/browser/mrrocket.js +1 -1
  18. package/dist/client.d.ts +10 -2
  19. package/dist/client.js +36 -15
  20. package/dist/client.js.map +1 -1
  21. package/dist/context.d.ts +5 -1
  22. package/dist/context.js.map +1 -1
  23. package/dist/core.d.ts +1 -1
  24. package/dist/core.js +1 -1
  25. package/dist/core.js.map +1 -1
  26. package/dist/errors.d.ts +5 -1
  27. package/dist/errors.js +8 -2
  28. package/dist/errors.js.map +1 -1
  29. package/dist/flows-ui.js +48 -1
  30. package/dist/flows-ui.js.map +1 -1
  31. package/dist/flows-web.d.ts +2 -0
  32. package/dist/flows-web.js +4 -1
  33. package/dist/flows-web.js.map +1 -1
  34. package/dist/flows.d.ts +61 -1
  35. package/dist/flows.js +64 -21
  36. package/dist/flows.js.map +1 -1
  37. package/dist/forms.d.ts +4 -0
  38. package/dist/forms.js +4 -1
  39. package/dist/forms.js.map +1 -1
  40. package/dist/identity.d.ts +42 -0
  41. package/dist/identity.js +24 -0
  42. package/dist/identity.js.map +1 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/messenger-ui.d.ts +21 -6
  46. package/dist/messenger-ui.js +334 -80
  47. package/dist/messenger-ui.js.map +1 -1
  48. package/dist/messenger.d.ts +11 -3
  49. package/dist/messenger.js +9 -5
  50. package/dist/messenger.js.map +1 -1
  51. package/dist/push-web.d.ts +2 -13
  52. package/dist/push-web.js +4 -2
  53. package/dist/push-web.js.map +1 -1
  54. package/dist/react-native/index.d.cts +162 -14
  55. package/dist/react-native/index.d.ts +162 -14
  56. package/dist/react-native/index.js +552 -207
  57. package/dist/react-native/index.js.map +1 -1
  58. package/dist/replay.d.ts +4 -1
  59. package/dist/replay.js +8 -2
  60. package/dist/replay.js.map +1 -1
  61. package/dist/server.d.ts +2 -0
  62. package/dist/server.js +2 -0
  63. package/dist/server.js.map +1 -1
  64. package/dist/styles-css.d.ts +1 -1
  65. package/dist/styles-css.js +200 -44
  66. package/dist/styles-css.js.map +1 -1
  67. package/dist/user-hash.d.ts +27 -0
  68. package/dist/user-hash.js +24 -0
  69. package/dist/user-hash.js.map +1 -0
  70. package/package.json +9 -1
package/dist/push-web.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/push-web.ts
2
+ import { warnUnsigned } from "./identity.js";
2
3
  var b64urlBytes = (s) => {
3
4
  const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(s.length / 4) * 4, "="));
4
5
  const out = new Uint8Array(bin.length);
@@ -49,8 +50,9 @@ var subscribeWebPush = async (ctx, options = {}) => {
49
50
  const saved = await fetch(`${ctx.host}/api/sdk/push`, {
50
51
  method: "POST",
51
52
  headers: { "content-type": "application/json" },
52
- body: JSON.stringify({ key: ctx.apiKey, distinctId: ctx.distinctId, platform: "web", subscription: sub.toJSON(), host: ctx.host })
53
+ body: JSON.stringify({ key: ctx.apiKey, distinctId: ctx.distinctId, userHash: ctx.userHash, platform: "web", subscription: sub.toJSON(), host: ctx.host })
53
54
  });
55
+ warnUnsigned(saved.status);
54
56
  return saved.ok ? { status: "subscribed" } : { status: "error", reason: `MRRocket answered ${saved.status}.` };
55
57
  } catch (e) {
56
58
  return { status: "error", reason: e instanceof Error ? e.message : String(e) };
@@ -65,7 +67,7 @@ var unsubscribeWebPush = async (ctx) => {
65
67
  await fetch(`${ctx.host}/api/sdk/push`, {
66
68
  method: "POST",
67
69
  headers: { "content-type": "application/json" },
68
- body: JSON.stringify({ key: ctx.apiKey, distinctId: ctx.distinctId, platform: "web", remove: true, endpoint: sub.endpoint })
70
+ body: JSON.stringify({ key: ctx.apiKey, distinctId: ctx.distinctId, userHash: ctx.userHash, platform: "web", remove: true, endpoint: sub.endpoint })
69
71
  }).catch(() => null);
70
72
  return await sub.unsubscribe();
71
73
  } catch {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/push-web.ts"],"sourcesContent":["/**\n * Web push in the browser (SDK 0.7.0): signs the signed-in person up for push notifications from the product's Lifecycle\n * journeys. Loaded only when the site calls `push.subscribe()` (a dynamic import in Next.js, `sdk-push.js` for the script\n * tag), so pages that never ask carry none of it.\n *\n * 1. The browser's permission (asked only from a click, and only when the site says so; `ask: false` only re-sends an\n * existing subscription).\n * 2. The service worker the site hosts (`/mrrocket-sw.js`, a copy of the file MRRocket serves), registered with the\n * public key and MRRocket's address in its URL, so it can report opened notifications. A site with its own service\n * worker imports ours there and passes `serviceWorker: false`.\n * 3. The workspace's VAPID key from `GET /api/sdk/push`, the browser's subscription with it, and the subscription sent to\n * `POST /api/sdk/push` for this person. A subscription made with another key is replaced.\n */\nexport type PushStatus = \"subscribed\" | \"denied\" | \"not-asked\" | \"unsupported\" | \"not-signed-in\" | \"error\";\n\nexport interface PushResult {\n status: PushStatus;\n /** What went wrong, for \"error\" and \"unsupported\". */\n reason?: string;\n}\n\nexport interface WebPushOptions {\n /**\n * The service worker's path on your site (default \"/mrrocket-sw.js\"), or false when your site already has one that\n * imports ours (`importScripts(\"/mrrocket-sw.js\")`): the SDK then uses the one that is active.\n */\n serviceWorker?: string | false;\n /** Ask for the permission when it was not given yet (default true). False: only re-send an existing subscription. */\n ask?: boolean;\n}\n\nexport interface WebPushContext {\n host: string;\n apiKey: string;\n /** The identify() id; push needs a signed-in person. */\n distinctId: string | undefined;\n /** The permission, asked synchronously by the caller inside the click (Safari needs that), or the current one. */\n permission?: Promise<NotificationPermission>;\n}\n\nconst b64urlBytes = (s: string) => {\n const bin = atob(s.replace(/-/g, \"+\").replace(/_/g, \"/\").padEnd(Math.ceil(s.length / 4) * 4, \"=\"));\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n};\n\nconst sameKey = (a: ArrayBuffer | null | undefined, b: Uint8Array) => {\n if (!a) return false;\n const x = new Uint8Array(a);\n return x.length === b.length && x.every((v, i) => v === b[i]);\n};\n\n/** True where the browser can receive web push (Safari 16.4+ only for sites added to the Home Screen on iOS). */\nexport const webPushSupported = () =>\n typeof window !== \"undefined\" && \"serviceWorker\" in navigator && \"PushManager\" in window && \"Notification\" in window;\n\n/** The permission, asked right away when `ask` and not given yet. Call it synchronously in the click handler. */\nexport const askPermission = (ask = true): Promise<NotificationPermission> => {\n if (!webPushSupported()) return Promise.resolve(\"denied\");\n if (Notification.permission !== \"default\" || !ask) return Promise.resolve(Notification.permission);\n try {\n return Promise.resolve(Notification.requestPermission());\n } catch {\n return Promise.resolve(\"denied\");\n }\n};\n\nconst registration = async (host: string, apiKey: string, sw: string | false) => {\n if (sw !== false) {\n const url = `${sw}?key=${encodeURIComponent(apiKey)}&host=${encodeURIComponent(host)}`;\n await navigator.serviceWorker.register(url);\n }\n return navigator.serviceWorker.ready;\n};\n\nexport const subscribeWebPush = async (ctx: WebPushContext, options: WebPushOptions = {}): Promise<PushResult> => {\n if (!webPushSupported()) return { status: \"unsupported\", reason: \"This browser has no web push.\" };\n if (!ctx.distinctId) return { status: \"not-signed-in\" };\n const ask = options.ask !== false;\n const permission = await (ctx.permission ?? askPermission(ask));\n if (permission === \"denied\") return { status: \"denied\" };\n if (permission !== \"granted\") return { status: ask ? \"denied\" : \"not-asked\" };\n try {\n const r = await fetch(`${ctx.host}/api/sdk/push?key=${encodeURIComponent(ctx.apiKey)}`);\n const vapid = r.ok ? ((await r.json()) as { web?: { vapidKey?: string } }).web?.vapidKey : undefined;\n if (!vapid) return { status: \"error\", reason: `MRRocket answered ${r.status} for the push key.` };\n const key = b64urlBytes(vapid);\n const reg = await registration(ctx.host, ctx.apiKey, options.serviceWorker ?? \"/mrrocket-sw.js\");\n let sub = await reg.pushManager.getSubscription();\n if (sub && !sameKey(sub.options?.applicationServerKey, key)) {\n await sub.unsubscribe().catch(() => false);\n sub = null;\n }\n sub ??= await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });\n const saved = await fetch(`${ctx.host}/api/sdk/push`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ key: ctx.apiKey, distinctId: ctx.distinctId, platform: \"web\", subscription: sub.toJSON(), host: ctx.host }),\n });\n return saved.ok ? { status: \"subscribed\" } : { status: \"error\", reason: `MRRocket answered ${saved.status}.` };\n } catch (e) {\n return { status: \"error\", reason: e instanceof Error ? e.message : String(e) };\n }\n};\n\n/** Ends this browser's subscription and tells MRRocket (sign-out, or the person turned push off). */\nexport const unsubscribeWebPush = async (ctx: WebPushContext): Promise<boolean> => {\n if (!webPushSupported()) return false;\n try {\n const reg = await navigator.serviceWorker.getRegistration();\n const sub = await reg?.pushManager.getSubscription();\n if (!sub) return false;\n await fetch(`${ctx.host}/api/sdk/push`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ key: ctx.apiKey, distinctId: ctx.distinctId, platform: \"web\", remove: true, endpoint: sub.endpoint }),\n }).catch(() => null);\n return await sub.unsubscribe();\n } catch {\n return false;\n }\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/push-web.ts"],"sourcesContent":["/**\n * Web push in the browser (SDK 0.7.0): signs the signed-in person up for push notifications from the product's Lifecycle\n * journeys. Loaded only when the site calls `push.subscribe()` (a dynamic import in Next.js, `sdk-push.js` for the script\n * tag), so pages that never ask carry none of it.\n *\n * 1. The browser's permission (asked only from a click, and only when the site says so; `ask: false` only re-sends an\n * existing subscription).\n * 2. The service worker the site hosts (`/mrrocket-sw.js`, a copy of the file MRRocket serves), registered with the\n * public key and MRRocket's address in its URL, so it can report opened notifications. A site with its own service\n * worker imports ours there and passes `serviceWorker: false`.\n * 3. The workspace's VAPID key from `GET /api/sdk/push`, the browser's subscription with it, and the subscription sent to\n * `POST /api/sdk/push` for this person. A subscription made with another key is replaced.\n */\nimport { warnUnsigned } from \"./identity\";\n\nexport type PushStatus = \"subscribed\" | \"denied\" | \"not-asked\" | \"unsupported\" | \"not-signed-in\" | \"error\";\n\nexport interface PushResult {\n status: PushStatus;\n /** What went wrong, for \"error\" and \"unsupported\". */\n reason?: string;\n}\n\nexport interface WebPushOptions {\n /**\n * The service worker's path on your site (default \"/mrrocket-sw.js\"), or false when your site already has one that\n * imports ours (`importScripts(\"/mrrocket-sw.js\")`): the SDK then uses the one that is active.\n */\n serviceWorker?: string | false;\n /** Ask for the permission when it was not given yet (default true). False: only re-send an existing subscription. */\n ask?: boolean;\n}\n\nexport interface WebPushContext {\n host: string;\n apiKey: string;\n /** The identify() id; push needs a signed-in person. */\n distinctId: string | undefined;\n /** The identity hash of that id (0.16.0), when the site gave one to identify. */\n userHash?: string;\n /** The permission, asked synchronously by the caller inside the click (Safari needs that), or the current one. */\n permission?: Promise<NotificationPermission>;\n}\n\nconst b64urlBytes = (s: string) => {\n const bin = atob(s.replace(/-/g, \"+\").replace(/_/g, \"/\").padEnd(Math.ceil(s.length / 4) * 4, \"=\"));\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n};\n\nconst sameKey = (a: ArrayBuffer | null | undefined, b: Uint8Array) => {\n if (!a) return false;\n const x = new Uint8Array(a);\n return x.length === b.length && x.every((v, i) => v === b[i]);\n};\n\n/** True where the browser can receive web push (Safari 16.4+ only for sites added to the Home Screen on iOS). */\nexport const webPushSupported = () =>\n typeof window !== \"undefined\" && \"serviceWorker\" in navigator && \"PushManager\" in window && \"Notification\" in window;\n\n/** The permission, asked right away when `ask` and not given yet. Call it synchronously in the click handler. */\nexport const askPermission = (ask = true): Promise<NotificationPermission> => {\n if (!webPushSupported()) return Promise.resolve(\"denied\");\n if (Notification.permission !== \"default\" || !ask) return Promise.resolve(Notification.permission);\n try {\n return Promise.resolve(Notification.requestPermission());\n } catch {\n return Promise.resolve(\"denied\");\n }\n};\n\nconst registration = async (host: string, apiKey: string, sw: string | false) => {\n if (sw !== false) {\n const url = `${sw}?key=${encodeURIComponent(apiKey)}&host=${encodeURIComponent(host)}`;\n await navigator.serviceWorker.register(url);\n }\n return navigator.serviceWorker.ready;\n};\n\nexport const subscribeWebPush = async (ctx: WebPushContext, options: WebPushOptions = {}): Promise<PushResult> => {\n if (!webPushSupported()) return { status: \"unsupported\", reason: \"This browser has no web push.\" };\n if (!ctx.distinctId) return { status: \"not-signed-in\" };\n const ask = options.ask !== false;\n const permission = await (ctx.permission ?? askPermission(ask));\n if (permission === \"denied\") return { status: \"denied\" };\n if (permission !== \"granted\") return { status: ask ? \"denied\" : \"not-asked\" };\n try {\n const r = await fetch(`${ctx.host}/api/sdk/push?key=${encodeURIComponent(ctx.apiKey)}`);\n const vapid = r.ok ? ((await r.json()) as { web?: { vapidKey?: string } }).web?.vapidKey : undefined;\n if (!vapid) return { status: \"error\", reason: `MRRocket answered ${r.status} for the push key.` };\n const key = b64urlBytes(vapid);\n const reg = await registration(ctx.host, ctx.apiKey, options.serviceWorker ?? \"/mrrocket-sw.js\");\n let sub = await reg.pushManager.getSubscription();\n if (sub && !sameKey(sub.options?.applicationServerKey, key)) {\n await sub.unsubscribe().catch(() => false);\n sub = null;\n }\n sub ??= await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });\n const saved = await fetch(`${ctx.host}/api/sdk/push`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ key: ctx.apiKey, distinctId: ctx.distinctId, userHash: ctx.userHash, platform: \"web\", subscription: sub.toJSON(), host: ctx.host }),\n });\n warnUnsigned(saved.status);\n return saved.ok ? { status: \"subscribed\" } : { status: \"error\", reason: `MRRocket answered ${saved.status}.` };\n } catch (e) {\n return { status: \"error\", reason: e instanceof Error ? e.message : String(e) };\n }\n};\n\n/** Ends this browser's subscription and tells MRRocket (sign-out, or the person turned push off). */\nexport const unsubscribeWebPush = async (ctx: WebPushContext): Promise<boolean> => {\n if (!webPushSupported()) return false;\n try {\n const reg = await navigator.serviceWorker.getRegistration();\n const sub = await reg?.pushManager.getSubscription();\n if (!sub) return false;\n await fetch(`${ctx.host}/api/sdk/push`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ key: ctx.apiKey, distinctId: ctx.distinctId, userHash: ctx.userHash, platform: \"web\", remove: true, endpoint: sub.endpoint }),\n }).catch(() => null);\n return await sub.unsubscribe();\n } catch {\n return false;\n }\n};\n"],"mappings":"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","names":[]}
@@ -1,10 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
- /**
5
- * The SDK's fetch client and the shapes it reads, without React, so the script tag's widgets (src/script) use it too.
6
- * `./api` re-exports all of it with the `useApi` hook.
7
- */
8
4
  type PostStatus = "review" | "planned" | "progress" | "shipped";
9
5
  interface FeedbackBoardInfo {
10
6
  id: string;
@@ -78,6 +74,28 @@ interface SurveyDefinition {
78
74
  questions: SurveyQuestion[];
79
75
  }
80
76
  type SurveyAnswer = number | string | string[];
77
+ /** A file in the messenger's chat: a screenshot, a photo or a PDF the visitor sent (servers from SDK 0.15 on). */
78
+ interface ChatFile {
79
+ /** From an upload: the signed id a message sends. In a message: the file's own id (`attachmentUrl` reads it). */
80
+ id: string;
81
+ name: string;
82
+ type: "image/png" | "image/jpeg" | "image/webp" | "image/gif" | "application/pdf";
83
+ /** Bytes. */
84
+ size: number;
85
+ }
86
+ /** A file to upload: a Blob or File on the web, the image picker's `{ uri, name, type }` in React Native. */
87
+ type ChatUpload = Blob | {
88
+ uri: string;
89
+ name: string;
90
+ type: string;
91
+ };
92
+ /** The bug context a visitor message carries; the server fills browser and system from the user agent when left out. */
93
+ interface ChatContext {
94
+ browser?: string;
95
+ os?: string;
96
+ /** "1440×900". */
97
+ viewport?: string;
98
+ }
81
99
  /** One message of the messenger's chat. "visitor" is the person on the site; the rest come from the product's team. */
82
100
  interface ChatMessage {
83
101
  id: string;
@@ -89,6 +107,8 @@ interface ChatMessage {
89
107
  id: string;
90
108
  title: string;
91
109
  }[];
110
+ /** Files sent with it (servers from SDK 0.15 on). */
111
+ attachments?: ChatFile[];
92
112
  }
93
113
  /** A published help article in a list (the messenger's Help tab). */
94
114
  interface ArticleSummary {
@@ -126,11 +146,17 @@ interface Article {
126
146
  updatedAt: string;
127
147
  blocks: ArticleBlock[];
128
148
  }
149
+ /** Where the chat button sits: a corner, the middle of the top or bottom edge, or the middle of a side. */
150
+ type MessengerPosition = "top-left" | "top-center" | "top-right" | "middle-left" | "middle-right" | "bottom-left" | "bottom-center" | "bottom-right";
151
+ /** How round the messenger is. */
152
+ type MessengerCorners = "round" | "soft" | "square";
129
153
  /** The messenger's look and texts as set in MRRocket › Support › Messenger (servers from SDK 0.9 on). */
130
154
  interface MessengerLook {
131
155
  accent: string;
156
+ /** Up to two more colors blended into Home's background glow (MRRocket from 0.16; older servers leave it out). */
157
+ glow?: string[];
132
158
  theme: "light" | "dark" | "auto";
133
- position: "bottom-right" | "bottom-left";
159
+ position: MessengerPosition;
134
160
  title: string;
135
161
  greeting: string;
136
162
  label: string;
@@ -142,6 +168,16 @@ interface MessengerLook {
142
168
  sound: boolean;
143
169
  preview: boolean;
144
170
  askEmail: boolean;
171
+ /** How round panel, bubbles, cards and fields are (MRRocket from 0.15; older servers leave it out: round). */
172
+ corners?: MessengerCorners;
173
+ /** The site's own font (the default) or the system's (web only; from 0.15). */
174
+ font?: "site" | "system";
175
+ /** Home's top: plain like the rest, or in the accent color (from 0.15; older servers leave it out: plain). */
176
+ header?: "plain" | "color";
177
+ /** "Powered by MRRocket" at the bottom; the server sends false only on plans that may hide it (from 0.15). */
178
+ branding?: boolean;
179
+ /** The product's icon (its favicon) at the top left of Home; without it the title's first letter. */
180
+ logo?: string;
145
181
  }
146
182
  /** The visitor's newest chat (open or closed) and its messages. */
147
183
  interface ChatThread {
@@ -161,6 +197,11 @@ interface Conn {
161
197
  id?: string;
162
198
  [key: string]: unknown;
163
199
  };
200
+ /**
201
+ * The identity hash of `distinctId` (0.16.0, see identity.ts), sent with every request that names the visitor: a
202
+ * JSON field next to `distinctId`, a query parameter, a form field. Leave it out for any other id.
203
+ */
204
+ userHash?: string;
164
205
  }
165
206
  /** The small fetch client behind the widgets. Every read takes an AbortSignal so unmounting cancels it. */
166
207
  declare const createApi: (c: Conn) => {
@@ -197,11 +238,18 @@ declare const createApi: (c: Conn) => {
197
238
  seconds: number;
198
239
  }) => Promise<unknown>;
199
240
  chat: (signal?: AbortSignal) => Promise<ChatThread>;
241
+ /** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */
200
242
  sendChat: (m: {
201
243
  body: string;
202
244
  email?: string;
203
245
  url?: string;
246
+ attachments?: string[];
247
+ context?: ChatContext;
204
248
  }) => Promise<ChatThread>;
249
+ /** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
250
+ uploadChatFile: (file: ChatUpload, signal?: AbortSignal) => Promise<ChatFile>;
251
+ /** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
252
+ attachmentUrl: (id: string) => string;
205
253
  articles: (q?: string, signal?: AbortSignal) => Promise<{
206
254
  articles: ArticleSummary[];
207
255
  }>;
@@ -243,6 +291,29 @@ interface CancelOffer {
243
291
  plan?: string;
244
292
  planName?: string;
245
293
  }
294
+ /** 0.15.0: a second question after an answer ("What would be a fair price?"); no answers means a free text. */
295
+ interface CancelFollowUp {
296
+ question: string;
297
+ /** The choices, in order; empty for a free text. */
298
+ answers: string[];
299
+ }
300
+ /**
301
+ * 0.15.0: a different offer for some customers of one answer. Every condition that is set must hold; the first rule
302
+ * that matches wins, else the answer's own offer. `offer: null` means no offer (straight to the confirmation).
303
+ */
304
+ interface CancelRule {
305
+ when: {
306
+ /** The follow-up's answer, as written. */
307
+ answer?: string;
308
+ /** The customer's plan: its id or name as MRRocket knows it from the payment provider (any of a list). */
309
+ plan?: string | string[];
310
+ /** A customer for fewer than this many months. */
311
+ tenureUnder?: number;
312
+ /** A customer for at least this many months. */
313
+ tenureOver?: number;
314
+ };
315
+ offer: CancelOffer | null;
316
+ }
246
317
  /** The product's live cancel flow (MRRocket › Churn › Cancel flow). */
247
318
  interface CancelFlowConfig {
248
319
  /** Grows with every publish. */
@@ -255,6 +326,12 @@ interface CancelFlowConfig {
255
326
  }[];
256
327
  /** The offer per answer id. */
257
328
  offers: Record<string, CancelOffer>;
329
+ /** 0.15.0: the follow-up question per answer id. */
330
+ followUps?: Record<string, CancelFollowUp>;
331
+ /** 0.15.0: the rules per answer id, checked in order before its offer. */
332
+ rules?: Record<string, CancelRule[]>;
333
+ /** 0.15.0: what one answer offers after its first offer was declined. */
334
+ second?: Record<string, CancelOffer>;
258
335
  confirm: {
259
336
  title: string;
260
337
  body: string;
@@ -326,6 +403,8 @@ interface CancelFlowResult {
326
403
  outcome: "cancelled" | "saved" | "kept";
327
404
  reason?: string;
328
405
  reasonLabel?: string;
406
+ /** 0.15.0: the answer to the follow-up question. */
407
+ answer?: string;
329
408
  offer?: CancelOffer;
330
409
  /** What the customer wrote on the confirmation, if anything. */
331
410
  comment?: string;
@@ -344,6 +423,10 @@ interface CancelFlowOptions {
344
423
  currency: string;
345
424
  interval?: "month" | "year";
346
425
  };
426
+ /** 0.15.0: the customer's plan (its id or name in your billing), for offers that depend on it. */
427
+ plan?: string;
428
+ /** 0.15.0: since when they pay (a date, ISO string or ms), for offers that depend on how long. */
429
+ customerSince?: Date | string | number;
347
430
  /** The customer confirmed: cancel the subscription in your code. Throwing keeps the dialog open with an error. */
348
431
  onCancel?: (result: CancelFlowResult) => unknown;
349
432
  /** The customer took an offer: apply it in your code (coupon, pause, plan change). Throwing keeps the dialog open. */
@@ -354,11 +437,16 @@ interface CancelFlowOptions {
354
437
  /** What a renderer draws. */
355
438
  type FlowView = {
356
439
  type: "cancel";
357
- screen: "survey" | "offer" | "confirm";
440
+ screen: "survey" | "followup" | "offer" | "confirm";
358
441
  flow: CancelFlowConfig;
359
442
  /** The answer picked on the survey. */
360
443
  reason?: string;
444
+ /** 0.15.0: the follow-up question on its screen, and its answer so far. */
445
+ followUp?: CancelFollowUp;
446
+ answer?: string;
361
447
  offer?: CancelOffer;
448
+ /** 0.15.0: 2 for the offer after a declined one. */
449
+ round?: 1 | 2;
362
450
  price?: CancelFlowOptions["price"];
363
451
  busy: boolean;
364
452
  error?: string;
@@ -395,8 +483,10 @@ interface FlowsController {
395
483
  view(): FlowView | null;
396
484
  subscribe(listener: () => void): () => void;
397
485
  /** `accept(comment)`: a "Talk to us" offer sends what the customer wrote with it. */
486
+ /** `answer(text)`: the follow-up's choice or free text (0.15.0); `next()` moves on from the survey and the follow-up. */
398
487
  cancel: {
399
488
  pick(reason: string): void;
489
+ answer(text: string): void;
400
490
  next(): void;
401
491
  accept(comment?: string): void;
402
492
  decline(): void;
@@ -531,7 +621,7 @@ interface PaywallHost {
531
621
 
532
622
  type VariantId = string;
533
623
  /** This build of the SDK. Sent with the config request so MRRocket knows what the site runs (error tracking, flags). */
534
- declare const SDK_VERSION = "0.14.0";
624
+ declare const SDK_VERSION = "0.16.0";
535
625
  /** One variant of an experiment. `changes` replaces texts that the page marks with `<Text id>`. */
536
626
  interface VariantConfig {
537
627
  id: VariantId;
@@ -672,6 +762,27 @@ interface AppContext {
672
762
  locale?: string;
673
763
  }
674
764
 
765
+ /**
766
+ * Identity verification (0.16.0), the browser and app side. After `identify(id)` the SDK's id is your app's own user
767
+ * id, and that id is no secret (a Clerk id shows in URLs and logs), so on its own it proves nothing: anyone who knows it
768
+ * could read that person's support chat or post as them. Your server signs the id with the product's server key
769
+ * (`userHash(user.id)` from `@mrrocket/sdk/next/server`) and you hand the result to `identify(id, props, { hash })`.
770
+ * From then on every request for that id carries it as `userHash` (a JSON field, a query parameter or a form field), and
771
+ * MRRocket checks it.
772
+ *
773
+ * The hash belongs to one id: it is forgotten on `reset()` (sign-out) and when `identify` is called for another id
774
+ * without a hash, so it never goes out with someone else's id (MRRocket refuses a hash that does not match with 401).
775
+ * Shared by the Next.js provider, the script tag and React Native: no React, no browser APIs.
776
+ */
777
+ /** The third argument of `identify`. */
778
+ interface IdentifyOptions {
779
+ /**
780
+ * The id signed by your server: `await userHash(user.id)` from `@mrrocket/sdk/next/server` (or the same HMAC in any
781
+ * language). Needed when MRRocket › Settings › Products › Sign-in › "Only signed users" is on.
782
+ */
783
+ hash?: string;
784
+ }
785
+
675
786
  /**
676
787
  * The React Native client without React: the event queue (batched, persisted for offline use, retried with backoff),
677
788
  * the app lifecycle (`$app_open`, `$app_background`, a new session after 30 minutes away), screens, identity and
@@ -733,6 +844,13 @@ interface RegisterForPushOptions {
733
844
  projectId?: string;
734
845
  }
735
846
  type NativePushStatus = "registered" | "denied" | "not-asked" | "unavailable" | "not-signed-in" | "error";
847
+ /**
848
+ * 0.16.0, what `requestReview()` did: "requested" the store's rating dialog was asked for (the store decides whether it
849
+ * shows; Apple shows it at most three times a year); "not-now" no live review request may ask this person now (not a
850
+ * customer, asked in the last 120 days, no request live); "unavailable" the device has no dialog or `expo-store-review`
851
+ * is not installed; "not-signed-in" call `identify` first.
852
+ */
853
+ type NativeReviewResult = "requested" | "not-now" | "unavailable" | "not-signed-in";
736
854
  interface NativePushResult {
737
855
  status: NativePushStatus;
738
856
  /** The Expo push token, once registered. */
@@ -749,8 +867,14 @@ interface MRRocketClient {
749
867
  tap(x: number, y: number): void;
750
868
  /** The person's answer to session recordings (`replay` in the options); false stops and forgets what was not sent. */
751
869
  setReplay(on: boolean): void;
752
- identify(id: string, props?: Record<string, unknown>): void;
753
- /** Forgets the signed-in person (sign-out): a new anonymous id and a new session. */
870
+ /**
871
+ * Who the person is once they sign in. `{ hash }` (0.16.0) is the id signed by your backend (`userHash(id)` from
872
+ * `@mrrocket/sdk/next/server`, or the same HMAC in any language), fetched after sign-in; sent with every request for
873
+ * that id so MRRocket knows it is really them. Needed with "Only signed users" (MRRocket › Settings › Products ›
874
+ * Sign-in). Not stored on the device: call identify with it on every start.
875
+ */
876
+ identify(id: string, props?: Record<string, unknown>, options?: IdentifyOptions): void;
877
+ /** Forgets the signed-in person and their hash (sign-out): a new anonymous id and a new session. */
754
878
  reset(): void;
755
879
  setConsent(consent: NativeConsent): void;
756
880
  captureError(error: unknown): void;
@@ -797,6 +921,12 @@ interface MRRocketClient {
797
921
  registerForPush(options?: RegisterForPushOptions): Promise<NativePushResult>;
798
922
  /** Removes this device's token for the signed-in person (sign-out does it on its own in `reset`). */
799
923
  unregisterForPush(): Promise<boolean>;
924
+ /**
925
+ * 0.16.0: asks for a store review at the moment your app picked (the trigger of a review request in MRRocket ›
926
+ * Reviews › Requests). MRRocket says whether this signed-in person may be asked now; if so the App Store's or Google
927
+ * Play's own rating dialog opens (needs `expo-store-review`) and MRRocket counts the request. Never filtered by mood.
928
+ */
929
+ requestReview(): Promise<NativeReviewResult>;
800
930
  /** The cancel flow, onboarding and messages for a renderer (the provider's, or your own). */
801
931
  flows: FlowsController;
802
932
  /** Sends what is queued now. */
@@ -895,15 +1025,22 @@ declare const useMessenger: () => {
895
1025
  interface NativeMessengerOptions {
896
1026
  /** A round chat button (default false: open it with `useMessenger().open()` from your own button). */
897
1027
  launcher?: boolean;
898
- /** Where the chat button sits (default bottom right). */
899
- position?: "bottom-right" | "bottom-left";
1028
+ /** Where the chat button sits: a corner, the middle of the top or bottom edge, or the middle of a side (default bottom right). */
1029
+ position?: MessengerPosition;
900
1030
  /** Text beside the chat button's icon; empty: the round button. */
901
1031
  label?: string;
1032
+ /** The name beside the team's mark at the top of Home (MRRocket fills in the product's name). */
902
1033
  title?: string;
903
1034
  /** The big line on Home and the first line of an empty chat. */
904
1035
  greeting?: string;
905
- /** How soon the team usually answers, said under "Send us a message" and in the chat's head. */
1036
+ /** How soon the team usually answers, said on Home. */
906
1037
  replyTime?: "minutes" | "hours" | "day";
1038
+ /** How round bubbles, cards and fields are (default round; the sheet itself keeps the system's corners). */
1039
+ corners?: MessengerCorners;
1040
+ /** Home's top: plain like the rest (default), or in the accent color with raised cards. */
1041
+ header?: "plain" | "color";
1042
+ /** The product's icon at the top left of Home (MRRocket sends the product's favicon). */
1043
+ logo?: string;
907
1044
  /** Ask people the app did not identify with an email for an email (optional for them). Default true. */
908
1045
  askEmail?: boolean;
909
1046
  /** The Help tab with your published help articles (default true; hidden while there are none). */
@@ -979,7 +1116,7 @@ declare const MRRocketProvider: ({ client: given, children, consent, flows, appe
979
1116
  declare const useMRRocket: () => {
980
1117
  track: (event: string, props?: Record<string, unknown>) => void;
981
1118
  screen: (name: string, props?: Record<string, unknown>) => void;
982
- identify: (id: string, props?: Record<string, unknown>) => void;
1119
+ identify: (id: string, props?: Record<string, unknown>, options?: IdentifyOptions) => void;
983
1120
  reset: () => void;
984
1121
  setConsent: (consent: NativeConsent) => void;
985
1122
  setReplay: (on: boolean) => void;
@@ -1022,6 +1159,17 @@ declare const usePush: () => {
1022
1159
  declare const usePaywall: () => {
1023
1160
  open: MRRocketClient["openPaywall"];
1024
1161
  };
1162
+ /**
1163
+ * 0.16.0: store reviews from a review request in MRRocket › Reviews › Requests (needs `expo-store-review`). Call
1164
+ * `request()` at the moment the request names (after the third finished challenge, a 7-day streak …): MRRocket says
1165
+ * whether this signed-in person may be asked now, and the App Store's or Google Play's own rating dialog opens.
1166
+ *
1167
+ * const review = useReview();
1168
+ * if (challengesDone === 3) void review.request();
1169
+ */
1170
+ declare const useReview: () => {
1171
+ request: MRRocketClient["requestReview"];
1172
+ };
1025
1173
  /** Starts an onboarding flow now (the first live one without an id), whatever its trigger. */
1026
1174
  declare const useOnboarding: () => {
1027
1175
  start: (id?: string) => boolean;
@@ -1060,4 +1208,4 @@ declare const useExpoRouterTracking: () => void;
1060
1208
  */
1061
1209
  declare const useNavigationTracking: () => (state: unknown) => void;
1062
1210
 
1063
- export { type AppContext, type CancelFlowConfig, type CancelFlowOptions, type CancelFlowResult, type CancelOffer, type ChatMessage, type ChatThread, type CockpitConfig, type FeedbackPost, type FlagConfig, type FlowView, type InAppMessage, type KeyValueStorage, type MRRocketClient, MRRocketFeedback, MRRocketFlows, MRRocketMask, MRRocketMessenger, type MRRocketOptions, MRRocketProvider, type MRRocketProviderProps, MRRocketSurveys, type NativeAppearance, type NativeConsent, type NativeFeedbackOptions, type NativeMessengerOptions, type NativePushResult, type NativePushStatus, type OnboardingFlow, type OnboardingStep, type PaywallConfig, type PaywallOptions, type PaywallPage, type PaywallPlan, type PaywallResult, type RegisterForPushOptions, SDK_VERSION, type SurveyDefinition, type VariantId, activeRouteName, createMRRocket, getMRRocket, useCancelFlow, useExpoRouterTracking, useFeedback, useFlag, useFlagVariant, useMRRocket, useMessenger, useNavigationTracking, useOnboarding, usePaywall, usePush, useText, useVariant };
1211
+ export { type AppContext, type CancelFlowConfig, type CancelFlowOptions, type CancelFlowResult, type CancelOffer, type ChatMessage, type ChatThread, type CockpitConfig, type FeedbackPost, type FlagConfig, type FlowView, type IdentifyOptions, type InAppMessage, type KeyValueStorage, type MRRocketClient, MRRocketFeedback, MRRocketFlows, MRRocketMask, MRRocketMessenger, type MRRocketOptions, MRRocketProvider, type MRRocketProviderProps, MRRocketSurveys, type NativeAppearance, type NativeConsent, type NativeFeedbackOptions, type NativeMessengerOptions, type NativePushResult, type NativePushStatus, type NativeReviewResult, type OnboardingFlow, type OnboardingStep, type PaywallConfig, type PaywallOptions, type PaywallPage, type PaywallPlan, type PaywallResult, type RegisterForPushOptions, SDK_VERSION, type SurveyDefinition, type VariantId, activeRouteName, createMRRocket, getMRRocket, useCancelFlow, useExpoRouterTracking, useFeedback, useFlag, useFlagVariant, useMRRocket, useMessenger, useNavigationTracking, useOnboarding, usePaywall, usePush, useReview, useText, useVariant };