@buildbase/sdk 0.0.59 → 0.0.62
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 +38 -0
- package/README.md +95 -0
- package/dist/css/styles.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +84 -0
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.d.ts +23 -0
- package/dist/mcp/index.mjs +1 -1
- package/dist/react/chunks/SettingsDialog-CKwldz7y.mjs +2 -0
- package/dist/react/chunks/{SettingsDialog-BUxloUTO.cjs → SettingsDialog-D4FCwpN7.cjs} +1 -1
- package/dist/react/chunks/SubscriptionDialog-DJmrU0r1.cjs +2 -0
- package/dist/react/chunks/SubscriptionDialog-DoWnS_JD.mjs +2 -0
- package/dist/react/chunks/react-Be1VMiVO.mjs +2 -0
- package/dist/react/chunks/react-ClieHJPH.cjs +2 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +201 -28
- package/dist/react/index.mjs +1 -1
- package/dist/tracking/index.cjs +2 -0
- package/dist/tracking/index.d.ts +657 -0
- package/dist/tracking/index.mjs +2 -0
- package/package.json +12 -2
- package/dist/react/chunks/SettingsDialog-nnVWUC3-.mjs +0 -2
- package/dist/react/chunks/SubscriptionDialog-CE1B3iaq.cjs +0 -2
- package/dist/react/chunks/SubscriptionDialog-Dne8BsN6.mjs +0 -2
- package/dist/react/chunks/react-CpHs0f3L.mjs +0 -2
- package/dist/react/chunks/react-kP8lTlQi.cjs +0 -2
- /package/dist/react/chunks/{ar-DHxjPtfZ.mjs → ar-CHTRXlXt.mjs} +0 -0
- /package/dist/react/chunks/{ar-D7uaSur2.cjs → ar-DPJVNHJV.cjs} +0 -0
- /package/dist/react/chunks/{de-D_xfGk1N.mjs → de-CdPeKWZ2.mjs} +0 -0
- /package/dist/react/chunks/{de-CBzx6NJq.cjs → de-DPd09yf-.cjs} +0 -0
- /package/dist/react/chunks/{es-D4-Dv2t5.mjs → es-BWGnjJKJ.mjs} +0 -0
- /package/dist/react/chunks/{es-WVRwuj-e.cjs → es-WdOYGt6G.cjs} +0 -0
- /package/dist/react/chunks/{fr-DurP7qkZ.mjs → fr-Big5DFPB.mjs} +0 -0
- /package/dist/react/chunks/{fr-CEpK52jT.cjs → fr-LIbCvYeh.cjs} +0 -0
- /package/dist/react/chunks/{hi-CVqVRSoy.cjs → hi-BoCY3pFe.cjs} +0 -0
- /package/dist/react/chunks/{hi-7roM83DB.mjs → hi-jArNRknR.mjs} +0 -0
- /package/dist/react/chunks/{ja-DT8x4SW1.mjs → ja-Cwdz0hCu.mjs} +0 -0
- /package/dist/react/chunks/{ja-BrDXec_v.cjs → ja-E0jqCiVq.cjs} +0 -0
- /package/dist/react/chunks/{zh-CH1Z55Je.mjs → zh-glRKcdia.mjs} +0 -0
- /package/dist/react/chunks/{zh-CdCzcD53.cjs → zh-tU-AqFYI.cjs} +0 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remembering where a visitor came from, across a whole product.
|
|
5
|
+
*
|
|
6
|
+
* A person clicks an ad, lands on `abc.com`, reads for a while, then signs up
|
|
7
|
+
* on `app.abc.com`. The click id was in the URL of the first page and is long
|
|
8
|
+
* gone by the second. Unless it was stored somewhere both subdomains can read,
|
|
9
|
+
* the sign-up looks like it came from nowhere and the ad that paid for it gets
|
|
10
|
+
* no credit.
|
|
11
|
+
*
|
|
12
|
+
* Google solves this by writing `_gcl_aw` on the registrable domain, so every
|
|
13
|
+
* subdomain shares it. This does the same thing for the ids we carry into our
|
|
14
|
+
* own events and, later, into a server-side conversion upload.
|
|
15
|
+
*
|
|
16
|
+
* ── Why the domain is probed rather than computed ───────────────────────────
|
|
17
|
+
* "The registrable domain" cannot be derived by counting labels: `abc.co.uk`
|
|
18
|
+
* has three, `abc.com` has two, and a hard-coded rule gets one of them wrong.
|
|
19
|
+
* The Public Suffix List is the correct answer and is far too large to ship in
|
|
20
|
+
* a tracking bundle.
|
|
21
|
+
*
|
|
22
|
+
* So we ask the browser, which already knows: try to set a cookie on the
|
|
23
|
+
* broadest candidate, read it back, and keep the first that sticks. A browser
|
|
24
|
+
* refuses `domain=.co.uk` because it is a public suffix, so that candidate
|
|
25
|
+
* fails and `abc.co.uk` wins - the right answer, from the only party that has
|
|
26
|
+
* the list.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* The URL parameters worth keeping.
|
|
30
|
+
*
|
|
31
|
+
* Click ids first - they are what an ad network matches a conversion against,
|
|
32
|
+
* and they are the reason this file exists. UTMs are reporting rather than
|
|
33
|
+
* billing, but they answer "which campaign" and cost nothing to carry.
|
|
34
|
+
*
|
|
35
|
+
* Click ids are handled byte-exactly: they are opaque, mixed-case, base64-ish
|
|
36
|
+
* strings issued by the network, so lowercasing one silently corrupts it and
|
|
37
|
+
* the network reports zero conversions with nothing in the logs to explain it.
|
|
38
|
+
*/
|
|
39
|
+
declare const ATTRIBUTION_PARAMS: readonly ["gclid", "gbraid", "wbraid", "dclid", "fbclid", "msclkid", "li_fat_id", "twclid", "ttclid", "rdt_cid", "epik", "irclickid", "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content", "utm_id"];
|
|
40
|
+
type AttributionParam = (typeof ATTRIBUTION_PARAMS)[number];
|
|
41
|
+
type Attribution = Partial<Record<AttributionParam, string>>;
|
|
42
|
+
/**
|
|
43
|
+
* Capture what this page arrived with, and return everything known so far.
|
|
44
|
+
*
|
|
45
|
+
* First touch wins for any given parameter. A visitor who arrives on an ad,
|
|
46
|
+
* comes back later through a newsletter, and only then signs up should still
|
|
47
|
+
* credit the ad - overwriting on the second visit would quietly reassign every
|
|
48
|
+
* conversion to whatever the last touch happened to be. New parameters are
|
|
49
|
+
* still added, so a later campaign tag enriches rather than replaces.
|
|
50
|
+
*/
|
|
51
|
+
declare function captureAttribution(): Attribution;
|
|
52
|
+
/** What is stored, without touching the URL. */
|
|
53
|
+
declare function getAttribution(): Attribution;
|
|
54
|
+
|
|
55
|
+
interface PendingCheckout {
|
|
56
|
+
eventId: string;
|
|
57
|
+
planVersionId?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The workspace buying. The return from Stripe fires `purchase` (or
|
|
60
|
+
* `checkout_abandoned`) as soon as the tags are ready, which is before the
|
|
61
|
+
* page has loaded its workspace - so the account the event belongs to has to
|
|
62
|
+
* travel with the checkout rather than be read on arrival.
|
|
63
|
+
*/
|
|
64
|
+
workspaceId?: string;
|
|
65
|
+
/** Set instead of a plan when what was bought is a credit pack. */
|
|
66
|
+
creditPackageId?: string;
|
|
67
|
+
billingInterval?: string;
|
|
68
|
+
currency?: string;
|
|
69
|
+
/**
|
|
70
|
+
* The Stripe session this checkout opened. Stripe puts the same id in the
|
|
71
|
+
* return URL, which is how a return is recognised without the app having
|
|
72
|
+
* to say so.
|
|
73
|
+
*/
|
|
74
|
+
sessionId?: string;
|
|
75
|
+
/** What is charged today, in major units - what every ad platform expects. */
|
|
76
|
+
value?: number;
|
|
77
|
+
/** A card-required trial comes back through Stripe too, and is not a sale. */
|
|
78
|
+
kind?: 'purchase' | 'trial_started';
|
|
79
|
+
/** Epoch ms, so an entry that outlived its usefulness can be ignored. */
|
|
80
|
+
startedAt: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The provider allowlist, as the SDK sees it.
|
|
85
|
+
*
|
|
86
|
+
* ── Why this is a copy ──────────────────────────────────────────────────────
|
|
87
|
+
* The server has the same table. It is duplicated here rather than fetched
|
|
88
|
+
* because this is the last check before a string becomes a `<script>` tag in a
|
|
89
|
+
* customer's application, and a check that arrives over the network from the
|
|
90
|
+
* thing being checked is not a check. The server is one deploy; a published
|
|
91
|
+
* SDK is pinned in a customer's lockfile and may be older or newer than it.
|
|
92
|
+
* Whichever side is stricter should win, and neither should be able to talk
|
|
93
|
+
* the other into loading something.
|
|
94
|
+
*
|
|
95
|
+
* Kept in step by `tag-providers.parity.test.ts`, which fails when this drifts
|
|
96
|
+
* from the platform's copy in a way that matters.
|
|
97
|
+
*
|
|
98
|
+
* Every rule from the server-side file holds here: patterns anchored at both
|
|
99
|
+
* ends, character classes only, an id is never a URL, and `scriptOrigin` comes
|
|
100
|
+
* from this table and is never built from anything a tenant typed.
|
|
101
|
+
*/
|
|
102
|
+
type TagConsentCategory = 'analytics' | 'marketing';
|
|
103
|
+
interface TagProviderSpec {
|
|
104
|
+
/** Display name, used in the console and in generated disclosure text. */
|
|
105
|
+
title: string;
|
|
106
|
+
/** The legal entity, for the tenant's own privacy notice. */
|
|
107
|
+
vendor: string;
|
|
108
|
+
/** Link for the tenant's disclosure. */
|
|
109
|
+
privacyUrl: string;
|
|
110
|
+
/** What the console calls the id field, in the vendor's own words. */
|
|
111
|
+
idLabel: string;
|
|
112
|
+
/**
|
|
113
|
+
* The only shape we accept. Anchored, character classes only.
|
|
114
|
+
* Stored as a string rather than a RegExp so it crosses the wire to the
|
|
115
|
+
* console form and the SDK unchanged - one pattern, three validators.
|
|
116
|
+
*/
|
|
117
|
+
idPattern: string;
|
|
118
|
+
/** A real-shaped example. Doubles as the console placeholder. */
|
|
119
|
+
idExample: string;
|
|
120
|
+
/** Where the script is fetched from. Never built from tenant input. */
|
|
121
|
+
scriptOrigin: string;
|
|
122
|
+
defaultConsentCategory: TagConsentCategory;
|
|
123
|
+
/**
|
|
124
|
+
* Whether the vendor honours Google Consent Mode v2. Those that do can load
|
|
125
|
+
* with all four signals denied and start measuring when consent arrives;
|
|
126
|
+
* those that do not must not be loaded at all beforehand.
|
|
127
|
+
*/
|
|
128
|
+
supportsConsentMode: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* True when the vendor has no fixed conversion event names and instead uses
|
|
131
|
+
* per-conversion ids the tenant creates in the ad platform. X, LinkedIn and
|
|
132
|
+
* Google Ads all work this way, so the console must collect an id per
|
|
133
|
+
* canonical event or their conversions simply never fire.
|
|
134
|
+
*/
|
|
135
|
+
needsConversionIds: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Whether the vendor's own API accepts more than one id on a page. Mirrors
|
|
138
|
+
* the platform's copy; see the note there.
|
|
139
|
+
*/
|
|
140
|
+
multiInstance?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Closed set of regional hosts, when the vendor has them. The console shows
|
|
143
|
+
* a picker; anything not in this list is rejected.
|
|
144
|
+
*/
|
|
145
|
+
hosts?: readonly string[];
|
|
146
|
+
}
|
|
147
|
+
declare const TAG_PROVIDERS: {
|
|
148
|
+
readonly gtm: {
|
|
149
|
+
readonly title: "Google Tag Manager";
|
|
150
|
+
readonly vendor: "Google LLC";
|
|
151
|
+
readonly privacyUrl: "https://policies.google.com/privacy";
|
|
152
|
+
readonly idLabel: "Container ID";
|
|
153
|
+
readonly idPattern: "^GTM-[A-Z0-9]{4,10}$";
|
|
154
|
+
readonly idExample: "GTM-ABC1234";
|
|
155
|
+
readonly scriptOrigin: "https://www.googletagmanager.com";
|
|
156
|
+
readonly defaultConsentCategory: "analytics";
|
|
157
|
+
readonly supportsConsentMode: true;
|
|
158
|
+
readonly needsConversionIds: false;
|
|
159
|
+
};
|
|
160
|
+
readonly ga4: {
|
|
161
|
+
readonly title: "Google Analytics 4";
|
|
162
|
+
readonly vendor: "Google LLC";
|
|
163
|
+
readonly privacyUrl: "https://policies.google.com/privacy";
|
|
164
|
+
readonly idLabel: "Measurement ID";
|
|
165
|
+
readonly idPattern: "^G-[A-Z0-9]{8,12}$";
|
|
166
|
+
readonly idExample: "G-ABCD123456";
|
|
167
|
+
readonly scriptOrigin: "https://www.googletagmanager.com";
|
|
168
|
+
readonly defaultConsentCategory: "analytics";
|
|
169
|
+
readonly supportsConsentMode: true;
|
|
170
|
+
readonly needsConversionIds: false;
|
|
171
|
+
};
|
|
172
|
+
readonly google_ads: {
|
|
173
|
+
readonly title: "Google Ads";
|
|
174
|
+
readonly vendor: "Google LLC";
|
|
175
|
+
readonly privacyUrl: "https://policies.google.com/privacy";
|
|
176
|
+
readonly idLabel: "Conversion ID";
|
|
177
|
+
readonly idPattern: "^AW-[0-9]{9,12}$";
|
|
178
|
+
readonly idExample: "AW-123456789";
|
|
179
|
+
readonly scriptOrigin: "https://www.googletagmanager.com";
|
|
180
|
+
readonly defaultConsentCategory: "marketing";
|
|
181
|
+
readonly supportsConsentMode: true;
|
|
182
|
+
readonly needsConversionIds: true;
|
|
183
|
+
};
|
|
184
|
+
readonly meta_pixel: {
|
|
185
|
+
readonly title: "Meta Pixel";
|
|
186
|
+
readonly vendor: "Meta Platforms Ireland Limited";
|
|
187
|
+
readonly privacyUrl: "https://www.facebook.com/privacy/policy";
|
|
188
|
+
readonly idLabel: "Pixel ID";
|
|
189
|
+
readonly idPattern: "^[0-9]{15,16}$";
|
|
190
|
+
readonly idExample: "123456789012345";
|
|
191
|
+
readonly scriptOrigin: "https://connect.facebook.net";
|
|
192
|
+
readonly defaultConsentCategory: "marketing";
|
|
193
|
+
readonly supportsConsentMode: false;
|
|
194
|
+
readonly needsConversionIds: false;
|
|
195
|
+
readonly multiInstance: true;
|
|
196
|
+
};
|
|
197
|
+
readonly x_pixel: {
|
|
198
|
+
readonly title: "X (Twitter) Pixel";
|
|
199
|
+
readonly vendor: "X Corp.";
|
|
200
|
+
readonly privacyUrl: "https://x.com/en/privacy";
|
|
201
|
+
readonly idLabel: "Pixel ID";
|
|
202
|
+
readonly idPattern: "^o[a-z0-9]{4,6}$";
|
|
203
|
+
readonly idExample: "o1a2b3";
|
|
204
|
+
readonly scriptOrigin: "https://static.ads-twitter.com";
|
|
205
|
+
readonly defaultConsentCategory: "marketing";
|
|
206
|
+
readonly supportsConsentMode: false;
|
|
207
|
+
readonly needsConversionIds: true;
|
|
208
|
+
readonly multiInstance: true;
|
|
209
|
+
};
|
|
210
|
+
readonly linkedin: {
|
|
211
|
+
readonly title: "LinkedIn Insight Tag";
|
|
212
|
+
readonly vendor: "LinkedIn Ireland Unlimited Company";
|
|
213
|
+
readonly privacyUrl: "https://www.linkedin.com/legal/privacy-policy";
|
|
214
|
+
readonly idLabel: "Partner ID";
|
|
215
|
+
readonly idPattern: "^[0-9]{6,9}$";
|
|
216
|
+
readonly idExample: "1234567";
|
|
217
|
+
readonly scriptOrigin: "https://snap.licdn.com";
|
|
218
|
+
readonly defaultConsentCategory: "marketing";
|
|
219
|
+
readonly supportsConsentMode: false;
|
|
220
|
+
readonly needsConversionIds: true;
|
|
221
|
+
readonly multiInstance: true;
|
|
222
|
+
};
|
|
223
|
+
readonly tiktok: {
|
|
224
|
+
readonly title: "TikTok Pixel";
|
|
225
|
+
readonly vendor: "TikTok Information Technologies UK Limited";
|
|
226
|
+
readonly privacyUrl: "https://www.tiktok.com/legal/privacy-policy";
|
|
227
|
+
readonly idLabel: "Pixel ID";
|
|
228
|
+
readonly idPattern: "^[A-Z0-9]{20}$";
|
|
229
|
+
readonly idExample: "C4A1B2C3D4E5F6G7H8I9";
|
|
230
|
+
readonly scriptOrigin: "https://analytics.tiktok.com";
|
|
231
|
+
readonly defaultConsentCategory: "marketing";
|
|
232
|
+
readonly supportsConsentMode: false;
|
|
233
|
+
readonly needsConversionIds: false;
|
|
234
|
+
readonly multiInstance: true;
|
|
235
|
+
};
|
|
236
|
+
readonly reddit: {
|
|
237
|
+
readonly title: "Reddit Pixel";
|
|
238
|
+
readonly vendor: "Reddit, Inc.";
|
|
239
|
+
readonly privacyUrl: "https://www.reddit.com/policies/privacy-policy";
|
|
240
|
+
readonly idLabel: "Advertiser ID";
|
|
241
|
+
readonly idPattern: "^t2_[a-z0-9]{4,12}$";
|
|
242
|
+
readonly idExample: "t2_abc123";
|
|
243
|
+
readonly scriptOrigin: "https://www.redditstatic.com";
|
|
244
|
+
readonly defaultConsentCategory: "marketing";
|
|
245
|
+
readonly supportsConsentMode: false;
|
|
246
|
+
readonly needsConversionIds: false;
|
|
247
|
+
readonly multiInstance: true;
|
|
248
|
+
};
|
|
249
|
+
readonly bing_uet: {
|
|
250
|
+
readonly title: "Microsoft Advertising (UET)";
|
|
251
|
+
readonly vendor: "Microsoft Corporation";
|
|
252
|
+
readonly privacyUrl: "https://privacy.microsoft.com/en-us/privacystatement";
|
|
253
|
+
readonly idLabel: "Tag ID";
|
|
254
|
+
readonly idPattern: "^[0-9]{7,9}$";
|
|
255
|
+
readonly idExample: "12345678";
|
|
256
|
+
readonly scriptOrigin: "https://bat.bing.com";
|
|
257
|
+
readonly defaultConsentCategory: "marketing";
|
|
258
|
+
readonly supportsConsentMode: false;
|
|
259
|
+
readonly needsConversionIds: false;
|
|
260
|
+
readonly multiInstance: true;
|
|
261
|
+
};
|
|
262
|
+
readonly clarity: {
|
|
263
|
+
readonly title: "Microsoft Clarity";
|
|
264
|
+
readonly vendor: "Microsoft Corporation";
|
|
265
|
+
readonly privacyUrl: "https://privacy.microsoft.com/en-us/privacystatement";
|
|
266
|
+
readonly idLabel: "Project ID";
|
|
267
|
+
readonly idPattern: "^[a-z0-9]{10}$";
|
|
268
|
+
readonly idExample: "ab12cd34ef";
|
|
269
|
+
readonly scriptOrigin: "https://www.clarity.ms";
|
|
270
|
+
readonly defaultConsentCategory: "analytics";
|
|
271
|
+
readonly supportsConsentMode: false;
|
|
272
|
+
readonly needsConversionIds: false;
|
|
273
|
+
};
|
|
274
|
+
readonly hotjar: {
|
|
275
|
+
readonly title: "Hotjar";
|
|
276
|
+
readonly vendor: "Hotjar Limited";
|
|
277
|
+
readonly privacyUrl: "https://www.hotjar.com/legal/policies/privacy/";
|
|
278
|
+
readonly idLabel: "Site ID";
|
|
279
|
+
readonly idPattern: "^[0-9]{6,9}$";
|
|
280
|
+
readonly idExample: "1234567";
|
|
281
|
+
readonly scriptOrigin: "https://static.hotjar.com";
|
|
282
|
+
readonly defaultConsentCategory: "analytics";
|
|
283
|
+
readonly supportsConsentMode: false;
|
|
284
|
+
readonly needsConversionIds: false;
|
|
285
|
+
};
|
|
286
|
+
readonly posthog: {
|
|
287
|
+
readonly title: "PostHog";
|
|
288
|
+
readonly vendor: "PostHog, Inc.";
|
|
289
|
+
readonly privacyUrl: "https://posthog.com/privacy";
|
|
290
|
+
readonly idLabel: "Project API key";
|
|
291
|
+
readonly idPattern: "^phc_[A-Za-z0-9]{32,48}$";
|
|
292
|
+
readonly idExample: "phc_abcdefghijklmnopqrstuvwxyz012345";
|
|
293
|
+
readonly scriptOrigin: "https://us-assets.i.posthog.com";
|
|
294
|
+
readonly defaultConsentCategory: "analytics";
|
|
295
|
+
readonly supportsConsentMode: false;
|
|
296
|
+
readonly needsConversionIds: false;
|
|
297
|
+
readonly hosts: readonly ["us.i.posthog.com", "eu.i.posthog.com"];
|
|
298
|
+
};
|
|
299
|
+
readonly plausible: {
|
|
300
|
+
readonly title: "Plausible Analytics";
|
|
301
|
+
readonly vendor: "Plausible Insights OÜ";
|
|
302
|
+
readonly privacyUrl: "https://plausible.io/privacy";
|
|
303
|
+
readonly idLabel: "Domain";
|
|
304
|
+
readonly idPattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$";
|
|
305
|
+
readonly idExample: "example.com";
|
|
306
|
+
readonly scriptOrigin: "https://plausible.io";
|
|
307
|
+
readonly defaultConsentCategory: "analytics";
|
|
308
|
+
readonly supportsConsentMode: false;
|
|
309
|
+
readonly needsConversionIds: false;
|
|
310
|
+
};
|
|
311
|
+
readonly ahrefs: {
|
|
312
|
+
readonly title: "Ahrefs Analytics";
|
|
313
|
+
readonly vendor: "Ahrefs Pte. Ltd.";
|
|
314
|
+
readonly privacyUrl: "https://ahrefs.com/privacy";
|
|
315
|
+
readonly idLabel: "Analytics key";
|
|
316
|
+
readonly idPattern: "^[A-Za-z0-9+/=]{20,64}$";
|
|
317
|
+
readonly idExample: "AbCdEfGhIjKlMnOpQrStUv==";
|
|
318
|
+
readonly scriptOrigin: "https://analytics.ahrefs.com";
|
|
319
|
+
readonly defaultConsentCategory: "analytics";
|
|
320
|
+
readonly supportsConsentMode: false;
|
|
321
|
+
readonly needsConversionIds: false;
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
type TagProviderId = keyof typeof TAG_PROVIDERS;
|
|
325
|
+
declare const TAG_PROVIDER_IDS: TagProviderId[];
|
|
326
|
+
declare function isTagProviderId(value: unknown): value is TagProviderId;
|
|
327
|
+
/**
|
|
328
|
+
* Whether `id` is a valid identifier for `provider`.
|
|
329
|
+
*
|
|
330
|
+
* Called on write in the console, again on write at the API, and a third time
|
|
331
|
+
* by the SDK on read. The triple-check is deliberate: the SDK validates
|
|
332
|
+
* because a document could have been written by a migration or edited directly
|
|
333
|
+
* in Mongo, and the SDK is the one that turns a string into a script tag.
|
|
334
|
+
*/
|
|
335
|
+
declare function isValidProviderId(provider: TagProviderId, id: unknown): boolean;
|
|
336
|
+
/**
|
|
337
|
+
* Every origin a tenant's app may need in its own Content-Security-Policy.
|
|
338
|
+
*
|
|
339
|
+
* Published in the docs so a customer running a CSP knows what to allow. We do
|
|
340
|
+
* not set their policy for them - it is their app - but they should not have
|
|
341
|
+
* to discover this list one console error at a time.
|
|
342
|
+
*/
|
|
343
|
+
declare const TAG_SCRIPT_ORIGINS: readonly string[];
|
|
344
|
+
|
|
345
|
+
/** One script, as the platform's `/v1/public/:orgId/tracking` returns it. */
|
|
346
|
+
interface PublicTrackingScript {
|
|
347
|
+
key: string;
|
|
348
|
+
provider: string;
|
|
349
|
+
providerId?: string;
|
|
350
|
+
host?: string;
|
|
351
|
+
src?: string;
|
|
352
|
+
dataAttributes?: Record<string, string>;
|
|
353
|
+
consentCategory: TagConsentCategory;
|
|
354
|
+
/** Keyed by event name - ours or the customer's own. */
|
|
355
|
+
conversionIds?: Record<string, string>;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* One row a consent banner can render.
|
|
359
|
+
*
|
|
360
|
+
* Derived from what is actually installed, never from the org's whole library:
|
|
361
|
+
* a banner that names a vendor the page did not load is asking for consent
|
|
362
|
+
* under false pretences.
|
|
363
|
+
*/
|
|
364
|
+
interface TrackingConsentEntry {
|
|
365
|
+
key: string;
|
|
366
|
+
name: string;
|
|
367
|
+
vendor: string;
|
|
368
|
+
category: TagConsentCategory;
|
|
369
|
+
privacyUrl?: string;
|
|
370
|
+
}
|
|
371
|
+
interface PublicTrackingConfig {
|
|
372
|
+
enabled: boolean;
|
|
373
|
+
scripts: PublicTrackingScript[];
|
|
374
|
+
consent: {
|
|
375
|
+
manifest: TrackingConsentEntry[];
|
|
376
|
+
};
|
|
377
|
+
dataLayerName?: string;
|
|
378
|
+
}
|
|
379
|
+
/** What the visitor has agreed to. `null` until they have been asked. */
|
|
380
|
+
interface ConsentState {
|
|
381
|
+
analytics: boolean;
|
|
382
|
+
marketing: boolean;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* The events the SDK fires on the app's behalf.
|
|
386
|
+
*
|
|
387
|
+
* Deliberately only the moments BuildBase itself observes. A customer's own
|
|
388
|
+
* events go through `track()` and reach the data layer, but are never mapped
|
|
389
|
+
* onto a vendor's standard event - guessing that someone's `upgraded_seat` is
|
|
390
|
+
* Meta's `Purchase` is how a conversion report becomes fiction.
|
|
391
|
+
*/
|
|
392
|
+
type CanonicalEventName = 'sign_up' | 'login' | 'trial_started' | 'checkout_started' | 'purchase' | 'subscription_created' | 'subscription_upgraded' | 'lead' | 'workspace_created';
|
|
393
|
+
/** Who fires a given event: the SDK, or the app. */
|
|
394
|
+
type EventOwnership = 'auto' | 'manual';
|
|
395
|
+
/** Per-call options for `track`. */
|
|
396
|
+
interface TrackOptions {
|
|
397
|
+
/**
|
|
398
|
+
* Use this id instead of generating one.
|
|
399
|
+
*
|
|
400
|
+
* The id is what joins a browser event to a later server-side send of the
|
|
401
|
+
* same occurrence, so a caller that has already written it somewhere the
|
|
402
|
+
* server will read it back - Stripe checkout metadata is the case this
|
|
403
|
+
* exists for - has to be able to fire the event with that same id.
|
|
404
|
+
*/
|
|
405
|
+
eventId?: string;
|
|
406
|
+
}
|
|
407
|
+
interface TrackingEvent {
|
|
408
|
+
name: CanonicalEventName | string;
|
|
409
|
+
params: Record<string, unknown>;
|
|
410
|
+
/** Shared with any later server-side send, so the two can be deduplicated. */
|
|
411
|
+
eventId: string;
|
|
412
|
+
handledBy: EventOwnership;
|
|
413
|
+
}
|
|
414
|
+
interface TrackingEventsConfig {
|
|
415
|
+
/** Default `auto`: the SDK fires to every installed script. */
|
|
416
|
+
mode?: EventOwnership;
|
|
417
|
+
/** Per-event override, for the few a customer wants to own. */
|
|
418
|
+
overrides?: Partial<Record<CanonicalEventName, EventOwnership>>;
|
|
419
|
+
}
|
|
420
|
+
interface TrackingOptions {
|
|
421
|
+
/** Set false to fetch nothing and render nothing. */
|
|
422
|
+
enabled?: boolean;
|
|
423
|
+
/**
|
|
424
|
+
* `auto` (default) sets Consent Mode v2 to denied and waits for
|
|
425
|
+
* `setConsent`. `granted` is the app asserting it has a lawful basis
|
|
426
|
+
* already - documented as their assertion, not ours. `denied` keeps
|
|
427
|
+
* everything off.
|
|
428
|
+
*/
|
|
429
|
+
consent?: 'auto' | 'granted' | 'denied';
|
|
430
|
+
/**
|
|
431
|
+
* Fire a `page_view` on SPA navigation. Turn it off when the app's GTM
|
|
432
|
+
* container already does it - double-counted pageviews is the single most
|
|
433
|
+
* likely support ticket this feature generates.
|
|
434
|
+
*/
|
|
435
|
+
autoPageview?: boolean;
|
|
436
|
+
events?: TrackingEventsConfig;
|
|
437
|
+
/**
|
|
438
|
+
* Called for every canonical event. Returning `true` tells the SDK the app
|
|
439
|
+
* handled this occurrence and suppresses its own fire.
|
|
440
|
+
*/
|
|
441
|
+
onEvent?: (event: TrackingEvent) => boolean | void;
|
|
442
|
+
/**
|
|
443
|
+
* Fields added to every event, including the ones the SDK fires itself.
|
|
444
|
+
*
|
|
445
|
+
* The events BuildBase owns - a signup, a checkout, a purchase - carry what
|
|
446
|
+
* BuildBase knows, which is never the thing a particular business builds
|
|
447
|
+
* its funnel on. A plan intent chosen before signup, an experiment variant,
|
|
448
|
+
* an industry: without somewhere to put them, the only way to get one onto
|
|
449
|
+
* a signup was to take the event over as `manual` and hand-write the
|
|
450
|
+
* fan-out to every vendor, which is a bad trade for one field.
|
|
451
|
+
*
|
|
452
|
+
* Use `setProperties` for anything not known at mount.
|
|
453
|
+
*/
|
|
454
|
+
properties?: Record<string, unknown>;
|
|
455
|
+
/**
|
|
456
|
+
* The last word on what an event carries, called just before it is sent.
|
|
457
|
+
*
|
|
458
|
+
* Returns fields to merge in - which may overwrite anything already there,
|
|
459
|
+
* including ours. Use it for values that depend on the event: a plan on
|
|
460
|
+
* `checkout_started`, a source on `sign_up`. To suppress an event entirely,
|
|
461
|
+
* return `true` from `onEvent` instead and send nothing.
|
|
462
|
+
*
|
|
463
|
+
* It cannot rename an event: the name is what every vendor mapping is keyed
|
|
464
|
+
* on, and renaming one here would silently unmap it.
|
|
465
|
+
*/
|
|
466
|
+
enrich?: (event: {
|
|
467
|
+
name: string;
|
|
468
|
+
params: Record<string, unknown>;
|
|
469
|
+
}) => Record<string, unknown> | void;
|
|
470
|
+
/** Log what was installed, skipped and fired. */
|
|
471
|
+
debug?: boolean;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
interface TrackingContextValue {
|
|
475
|
+
/** What is installed, for a banner to render. Empty until config loads. */
|
|
476
|
+
consent: {
|
|
477
|
+
manifest: TrackingConsentEntry[];
|
|
478
|
+
state: ConsentState | null;
|
|
479
|
+
set: (state: ConsentState) => void;
|
|
480
|
+
};
|
|
481
|
+
/** Fire an event. Canonical names map to every vendor; others go to the data layer. */
|
|
482
|
+
track: (name: string, params?: Record<string, unknown>, options?: TrackOptions) => void;
|
|
483
|
+
/**
|
|
484
|
+
* Attach a user id to every vendor that supports one, and optionally the
|
|
485
|
+
* traits a funnel segments on - a plan, a company size, an experiment arm.
|
|
486
|
+
* Never an email or a name: every ad network forbids it in these fields.
|
|
487
|
+
*/
|
|
488
|
+
setUser: (userId: string, traits?: Record<string, unknown>) => void;
|
|
489
|
+
/**
|
|
490
|
+
* Add fields to every event from here on. For anything not known when the
|
|
491
|
+
* provider mounted: an experiment assignment, a tenant tier fetched after
|
|
492
|
+
* login. Merged, so calling it twice keeps both, and setting a key to
|
|
493
|
+
* `undefined` takes it back - which is what to do on sign-out, so the next
|
|
494
|
+
* person's events do not carry the last person's.
|
|
495
|
+
*/
|
|
496
|
+
setProperties: (properties: Record<string, unknown>) => void;
|
|
497
|
+
/**
|
|
498
|
+
* What `properties` and `setProperties` currently amount to.
|
|
499
|
+
*
|
|
500
|
+
* Read by the checkout hooks, which stamp them onto the Stripe session so
|
|
501
|
+
* that a conversion sent later from our servers carries the same fields the
|
|
502
|
+
* browser would have sent. Without it a custom field lands on whichever of
|
|
503
|
+
* the two hits the vendor happens to keep.
|
|
504
|
+
*/
|
|
505
|
+
getProperties: () => Record<string, unknown>;
|
|
506
|
+
/**
|
|
507
|
+
* Report a checkout that has come back from Stripe, correcting its value
|
|
508
|
+
* against what was actually charged. Used by the return leg and by
|
|
509
|
+
* `useCheckoutCompleted`; both spend the same parked checkout, so whichever
|
|
510
|
+
* runs first is the one that reports.
|
|
511
|
+
*/
|
|
512
|
+
completeCheckout: (pending: PendingCheckout, extraParams?: Record<string, unknown>) => Promise<void>;
|
|
513
|
+
/** Which providers are loaded, keyed by script key. */
|
|
514
|
+
installed: Record<string, string>;
|
|
515
|
+
/**
|
|
516
|
+
* True once the org's configuration has been fetched and the scripts it
|
|
517
|
+
* allows are installed. Anything fired before this reaches `onEvent` and no
|
|
518
|
+
* vendor, so a caller that owns a once-per-session event waits for it.
|
|
519
|
+
*/
|
|
520
|
+
ready: boolean;
|
|
521
|
+
/**
|
|
522
|
+
* Where this visitor came from - click ids and campaign params, captured on
|
|
523
|
+
* the first page they landed on anywhere in the product and shared across
|
|
524
|
+
* subdomains. Rides on every event so a sign-up on `app.abc.com` can still
|
|
525
|
+
* be credited to the ad that was clicked on `abc.com`.
|
|
526
|
+
*/
|
|
527
|
+
attribution: Attribution;
|
|
528
|
+
}
|
|
529
|
+
declare function useTracking(): TrackingContextValue;
|
|
530
|
+
interface BuildBaseTrackingProps extends TrackingOptions {
|
|
531
|
+
/** The BuildBase API base URL. */
|
|
532
|
+
serverUrl: string;
|
|
533
|
+
/** The organization id. */
|
|
534
|
+
orgId: string;
|
|
535
|
+
/**
|
|
536
|
+
* The auth client id for *this* app. Required: a client is what an app is,
|
|
537
|
+
* and it is what decides which scripts load here rather than on the org's
|
|
538
|
+
* other properties.
|
|
539
|
+
*/
|
|
540
|
+
clientId: string;
|
|
541
|
+
/**
|
|
542
|
+
* A GA4 identity handed over from another domain, so this page measures as
|
|
543
|
+
* the same user rather than a new one.
|
|
544
|
+
*
|
|
545
|
+
* For the hosted sign-in pages, which are on a different registrable domain
|
|
546
|
+
* from the app the visitor came from. GA4's `_ga` cookie cannot cross that
|
|
547
|
+
* boundary, so without this the sign-in step lands under somebody who never
|
|
548
|
+
* visited the app and the funnel cannot be built at all.
|
|
549
|
+
*
|
|
550
|
+
* `signIn()` sends it with the auth request; the pages read it back and pass
|
|
551
|
+
* it here. Ordinary apps never set this.
|
|
552
|
+
*/
|
|
553
|
+
identity?: {
|
|
554
|
+
gaClientId?: string;
|
|
555
|
+
gaSessionId?: string;
|
|
556
|
+
};
|
|
557
|
+
children?: React.ReactNode;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Installs the org's configured tracking scripts into this app.
|
|
561
|
+
*
|
|
562
|
+
* Mountable on its own (a marketing site that wants tags and nothing else) and
|
|
563
|
+
* mounted internally by `SaaSOSProvider`, which additionally knows about
|
|
564
|
+
* signup, login and checkout and fires those events for you.
|
|
565
|
+
*/
|
|
566
|
+
declare const BuildBaseTracking: React.FC<BuildBaseTrackingProps>;
|
|
567
|
+
/**
|
|
568
|
+
* Fire `purchase` for a checkout the customer has just come back from paying.
|
|
569
|
+
*
|
|
570
|
+
* Call it on the page Stripe returns to, and nowhere else - the SDK has no
|
|
571
|
+
* way to recognise that page, because its URL is the app's own `successUrl`.
|
|
572
|
+
*
|
|
573
|
+
* It is a no-op when no checkout is in flight, so it is safe on a page a
|
|
574
|
+
* customer might also reach directly, and it consumes what it reads, so a
|
|
575
|
+
* refresh cannot count the sale twice. The event carries the same id that was
|
|
576
|
+
* written onto the Stripe subscription as `bb_event_id`.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```tsx
|
|
580
|
+
* export default function BillingSuccess() {
|
|
581
|
+
* useCheckoutCompleted({ value: 79, currency: 'USD' });
|
|
582
|
+
* return <p>Thanks - your plan is active.</p>;
|
|
583
|
+
* }
|
|
584
|
+
* ```
|
|
585
|
+
*/
|
|
586
|
+
declare function useCheckoutCompleted(params?: Record<string, unknown>): void;
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* What each vendor calls the events we fire.
|
|
590
|
+
*
|
|
591
|
+
* `null` means the vendor has no counterpart and we deliberately send nothing.
|
|
592
|
+
* No ad network treats a returning login as a conversion, and reporting one
|
|
593
|
+
* inflates every campaign that ever touched an existing customer. An upgrade
|
|
594
|
+
* is not mapped to `Purchase` for the same reason - it double-counts against
|
|
595
|
+
* the original subscription.
|
|
596
|
+
*
|
|
597
|
+
* X, LinkedIn and Google Ads take no event name at all: they take a
|
|
598
|
+
* per-conversion id the tenant created in the ad platform, which is why
|
|
599
|
+
* `conversionIds` rides along on the script config.
|
|
600
|
+
*/
|
|
601
|
+
declare const EVENT_MAPPINGS: Record<CanonicalEventName, {
|
|
602
|
+
ga4: string | null;
|
|
603
|
+
meta: string | null;
|
|
604
|
+
tiktok: string | null;
|
|
605
|
+
reddit: string | null;
|
|
606
|
+
}>;
|
|
607
|
+
declare function isCanonicalEvent(name: string): name is CanonicalEventName;
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* The steps between a visit and a paying, retained customer that happen
|
|
611
|
+
* inside BuildBase's own UI.
|
|
612
|
+
*
|
|
613
|
+
* `sign_up` and `purchase` say that someone arrived and that someone paid.
|
|
614
|
+
* They cannot say where the rest went: who opened the plan picker and closed
|
|
615
|
+
* it, who picked a plan and backed out of Stripe, who kept hitting a feature
|
|
616
|
+
* their plan does not include. Those moments happen in the settings modal, the
|
|
617
|
+
* plan picker and the gates - components the SDK renders - so the SDK is the
|
|
618
|
+
* only thing that can report them, and it does it for every app without a line
|
|
619
|
+
* of code on their side.
|
|
620
|
+
*
|
|
621
|
+
* Product events, never conversions. None of them map to a Meta, TikTok or
|
|
622
|
+
* Reddit event, so no ad network sees them; they reach the data layer and the
|
|
623
|
+
* vendors that take arbitrary names - GA4, PostHog, Clarity, Hotjar, Plausible.
|
|
624
|
+
* Google Ads, X and LinkedIn hear one only if the tenant attaches a conversion
|
|
625
|
+
* id to that exact name in the console.
|
|
626
|
+
*
|
|
627
|
+
* The names are frozen once released. They become column values in somebody's
|
|
628
|
+
* warehouse and step names in somebody's saved funnel, and a rename breaks both
|
|
629
|
+
* without an error.
|
|
630
|
+
*/
|
|
631
|
+
declare const FUNNEL_EVENTS: {
|
|
632
|
+
readonly sign_in_started: "The sign-in / sign-up button was pressed.";
|
|
633
|
+
readonly pricing_viewed: "A <PricingPage> finished loading its plans.";
|
|
634
|
+
readonly pricing_plan_selected: "A plan was chosen on a <PricingPage>.";
|
|
635
|
+
readonly workspace_switcher_opened: "The workspace switcher was opened.";
|
|
636
|
+
readonly workspace_settings_opened: "The workspace settings modal was opened.";
|
|
637
|
+
readonly workspace_settings_section_viewed: "A different section of the settings modal was opened.";
|
|
638
|
+
readonly plan_picker_opened: "The plan picker was shown.";
|
|
639
|
+
readonly billing_interval_changed: "Monthly / quarterly / yearly was switched in the plan picker.";
|
|
640
|
+
readonly plan_selected: "A plan was chosen in the plan picker.";
|
|
641
|
+
readonly plan_picker_dismissed: "The plan picker was closed without choosing a plan.";
|
|
642
|
+
readonly checkout_abandoned: "The customer came back from Stripe without paying.";
|
|
643
|
+
readonly billing_portal_opened: "Stripe’s billing portal was opened.";
|
|
644
|
+
readonly subscription_cancel_started: "The cancel-subscription confirmation was opened.";
|
|
645
|
+
readonly subscription_cancelled: "A subscription was cancelled.";
|
|
646
|
+
readonly subscription_resumed: "A cancelled subscription was resumed.";
|
|
647
|
+
readonly feature_gate_shown: "A feature gate rendered its \"not on your plan\" side.";
|
|
648
|
+
readonly quota_limit_reached: "A quota gate rendered because a quota is used up.";
|
|
649
|
+
readonly quota_threshold_reached: "A usage-threshold gate rendered.";
|
|
650
|
+
readonly plan_gate_shown: "A plan gate rendered its \"needs another plan\" side.";
|
|
651
|
+
readonly subscription_notice_shown: "A billing notice (trial ending, payment failed…) was shown.";
|
|
652
|
+
readonly sdk_error: "An SDK operation failed (component, action and code only).";
|
|
653
|
+
};
|
|
654
|
+
type FunnelEventName = keyof typeof FUNNEL_EVENTS;
|
|
655
|
+
|
|
656
|
+
export { ATTRIBUTION_PARAMS, BuildBaseTracking, EVENT_MAPPINGS, FUNNEL_EVENTS, TAG_PROVIDERS, TAG_PROVIDER_IDS, TAG_SCRIPT_ORIGINS, captureAttribution, getAttribution, isCanonicalEvent, isTagProviderId, isValidProviderId, useCheckoutCompleted, useTracking };
|
|
657
|
+
export type { Attribution, AttributionParam, BuildBaseTrackingProps, CanonicalEventName, ConsentState, EventOwnership, FunnelEventName, PublicTrackingConfig, PublicTrackingScript, TagConsentCategory, TagProviderId, TagProviderSpec, TrackOptions, TrackingConsentEntry, TrackingContextValue, TrackingEvent, TrackingEventsConfig, TrackingOptions };
|