@omega.js/client 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.
Files changed (72) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +874 -0
  3. package/dist/index.js +999 -0
  4. package/dist/modules/analytics.js +584 -0
  5. package/dist/modules/auth.js +469 -0
  6. package/dist/modules/bindings.js +319 -0
  7. package/dist/modules/device.js +282 -0
  8. package/dist/modules/dom.js +96 -0
  9. package/dist/modules/features.js +30 -0
  10. package/dist/modules/firestore.js +313 -0
  11. package/dist/modules/form-manager.js +1577 -0
  12. package/dist/modules/icon-core.js +226 -0
  13. package/dist/modules/icon-renderer.js +149 -0
  14. package/dist/modules/live-page.js +235 -0
  15. package/dist/modules/logger.js +36 -0
  16. package/dist/modules/motion.js +853 -0
  17. package/dist/modules/notifications.js +433 -0
  18. package/dist/modules/path-prefix.js +22 -0
  19. package/dist/modules/request.js +223 -0
  20. package/dist/modules/sentry.js +108 -0
  21. package/dist/modules/service-worker.js +237 -0
  22. package/dist/modules/storage.js +133 -0
  23. package/dist/modules/triggers.js +117 -0
  24. package/dist/modules/utilities.js +479 -0
  25. package/dist/modules/vert-document.js +354 -0
  26. package/dist/modules/verts.js +1133 -0
  27. package/dist/vendor/account/engine.js +182 -0
  28. package/dist/vendor/account/features.js +220 -0
  29. package/dist/vendor/account/index.js +53 -0
  30. package/dist/vendor/account/schema.js +272 -0
  31. package/dist/vendor/account/subscription.js +38 -0
  32. package/dist/vendor/analytics/adapters/ga4.js +26 -0
  33. package/dist/vendor/analytics/adapters/meta.js +26 -0
  34. package/dist/vendor/analytics/adapters/resolve.js +130 -0
  35. package/dist/vendor/analytics/adapters/tiktok.js +27 -0
  36. package/dist/vendor/analytics/catalog.js +908 -0
  37. package/dist/vendor/analytics/consent.js +49 -0
  38. package/dist/vendor/analytics/core.js +141 -0
  39. package/dist/vendor/analytics/identity.js +136 -0
  40. package/dist/vendor/analytics/index.js +170 -0
  41. package/dist/vendor/analytics/logger.js +40 -0
  42. package/dist/vendor/analytics/transports/browser.js +110 -0
  43. package/dist/vendor/monitoring/browser.js +207 -0
  44. package/dist/vendor/monitoring/core.js +180 -0
  45. package/dist/vendor/monitoring/logger.js +39 -0
  46. package/docs/architecture.md +59 -0
  47. package/docs/bindings.md +235 -0
  48. package/docs/build-system.md +32 -0
  49. package/docs/cdp-debugging.md +29 -0
  50. package/docs/code-patterns.md +96 -0
  51. package/docs/common-tasks.md +36 -0
  52. package/docs/dependencies.md +19 -0
  53. package/docs/index.md +159 -0
  54. package/docs/modules.md +180 -0
  55. package/docs/shared/agent-docs.md +89 -0
  56. package/docs/shared/analytics.md +612 -0
  57. package/docs/shared/brands.md +51 -0
  58. package/docs/shared/breaking-changes.md +497 -0
  59. package/docs/shared/config.md +1387 -0
  60. package/docs/shared/deploys.md +215 -0
  61. package/docs/shared/icons.md +201 -0
  62. package/docs/shared/local-dev.md +147 -0
  63. package/docs/shared/logging.md +202 -0
  64. package/docs/shared/monitoring.md +153 -0
  65. package/docs/shared/publishing.md +183 -0
  66. package/docs/shared/rulings.md +34 -0
  67. package/docs/shared/testing.md +147 -0
  68. package/docs/shared/theming.md +604 -0
  69. package/docs/shared/translation.md +291 -0
  70. package/docs/shared/updates.md +61 -0
  71. package/docs/testing.md +9 -0
  72. package/package.json +65 -0
@@ -0,0 +1,272 @@
1
+ /**
2
+ * USER_SCHEMA — the canonical OMEGA user/account schema (pure data, no logic).
3
+ *
4
+ * Extracted verbatim from @omega.js/backend's src/manager/helpers/user.js, which is
5
+ * the authoritative shape (@omega.js/client's DEFAULT_ACCOUNT had drifted from it).
6
+ *
7
+ * Each leaf field is { type, default, nullable }
8
+ * Special keys:
9
+ * $passthrough — preserve all existing keys from input, don't strip unknowns
10
+ * $template — shape applied to every dynamic key in a $passthrough object
11
+ * '$template' — (string value) reference to parent's $template
12
+ * '$timestamp' — shorthand for { timestamp, timestampUNIX } defaulting to epoch
13
+ * '$timestamp:now' — same but defaults to current time
14
+ * '$uuid', '$randomId', '$apiKey', '$oldDate' — resolved at runtime (generators
15
+ * are injected by the host — see engine.js; absent generators resolve to null)
16
+ */
17
+
18
+ /**
19
+ * One attribution TOUCH — a single visit's campaign context, shared by
20
+ * `attribution.first` and `attribution.last`
21
+ * ([#384](https://github.com/Omega-JS-Stack/omega/issues/384)). `tags` holds the
22
+ * utm set and `clickIds` the ad-platform click ids (fbclid/gclid/…), both
23
+ * passthrough because the platforms keep adding params. The schema is read-only
24
+ * to the engine, so one object serves both slots.
25
+ */
26
+ const ATTRIBUTION_TOUCH = {
27
+ tags: { $passthrough: true },
28
+ clickIds: { $passthrough: true },
29
+ referrer: { type: 'string', default: null, nullable: true },
30
+ url: { type: 'string', default: null, nullable: true },
31
+ page: { type: 'string', default: null, nullable: true },
32
+ timestamp: { type: 'string', default: null, nullable: true },
33
+ };
34
+
35
+ const USER_SCHEMA = {
36
+ auth: {
37
+ uid: { type: 'string', default: null, nullable: true },
38
+ email: { type: 'string', default: null, nullable: true },
39
+ temporary: { type: 'boolean', default: false },
40
+ },
41
+ subscription: {
42
+ product: {
43
+ id: { type: 'string', default: 'basic' },
44
+ name: { type: 'string', default: 'Basic' },
45
+ },
46
+ status: { type: 'string', default: 'active' },
47
+ expires: '$timestamp',
48
+ trial: {
49
+ claimed: { type: 'boolean', default: false },
50
+ expires: '$timestamp',
51
+ // How the trial ENDED: 'converted' | 'lapsed' | null (still running, or never
52
+ // answered). `claimed` says only that a trial happened — a converted trial and a
53
+ // lapsed one carry identical dates — so this is the one stored conversion signal.
54
+ // Stamped by the trial-lapse sweep once the provider confirms which it was.
55
+ outcome: { type: 'string', default: null, nullable: true },
56
+ },
57
+ cancellation: {
58
+ pending: { type: 'boolean', default: false },
59
+ date: '$timestamp',
60
+ },
61
+ payment: {
62
+ provider: { type: 'string', default: null, nullable: true },
63
+ orderId: { type: 'string', default: null, nullable: true },
64
+ resourceId: { type: 'string', default: null, nullable: true },
65
+ frequency: { type: 'string', default: null, nullable: true },
66
+ price: { type: 'number', default: 0 },
67
+ startDate: '$timestamp',
68
+ updatedBy: {
69
+ event: {
70
+ name: { type: 'string', default: null, nullable: true },
71
+ id: { type: 'string', default: null, nullable: true },
72
+ },
73
+ date: '$timestamp',
74
+ },
75
+ },
76
+ // The discount riding the subscription right now
77
+ // ([#325](https://github.com/Omega-JS-Stack/omega/issues/325)). Shaped as a
78
+ // discount-codes validate() RESULT — the one shape the whole payment stack
79
+ // already speaks — so the billing card reads exactly what the apply route
80
+ // wrote, and neither side learns a second spelling. `valid: false` (the
81
+ // default) IS "no discount": absent and denied read the same everywhere.
82
+ //
83
+ // Both shapes are always present and default to 0, unlike a fresh validate()
84
+ // result which omits the one it is not: this node is MERGED onto a document
85
+ // that may already carry a discount, and a percent claim landing on a stored
86
+ // amount would otherwise be read as the older, wrong number.
87
+ //
88
+ // `source` is what makes it safe to read as a CLAIM. Today only the winback
89
+ // claim writes this node; a code typed at checkout will set the same node
90
+ // when checkout starts writing it, and a winback pitch suppressed by someone
91
+ // else's promo is an offer silently withheld — so the node says which system
92
+ // applied it ('winback' | 'checkout'), and only 'winback' is the claimed
93
+ // signal the cancel flow gates on.
94
+ //
95
+ // `resourceId` is the subscription the discount was applied TO, stamped at
96
+ // claim time ([#333](https://github.com/Omega-JS-Stack/omega/issues/333)).
97
+ // It is what makes the node clearable: the unified webhook write carries no
98
+ // discount key, so a merge preserves this node forever, and a customer who
99
+ // churned and resubscribed carried a spent claim into the NEW subscription,
100
+ // where `source: 'winback'` reads as "already claimed" and silently retires
101
+ // an offer the backend would grant again. The webhook pipeline compares the
102
+ // stamp against the subscription each event is about and CLEARS the node
103
+ // (back to these defaults) on a mismatch: a new subscription is a clean
104
+ // slate. An unstamped node (written before the stamp existed) is read as
105
+ // belonging to the subscription it is found on, because nothing can prove
106
+ // otherwise and no live discount is ever taken away on a guess, and the
107
+ // pipeline stamps it there, so it clears on the next resubscribe like any
108
+ // other.
109
+ discount: {
110
+ valid: { type: 'boolean', default: false },
111
+ code: { type: 'string', default: null, nullable: true },
112
+ percent: { type: 'number', default: 0 },
113
+ amount: { type: 'number', default: 0 },
114
+ duration: { type: 'string', default: null, nullable: true },
115
+ source: { type: 'string', default: null, nullable: true },
116
+ resourceId: { type: 'string', default: null, nullable: true },
117
+ },
118
+ },
119
+ roles: {
120
+ $passthrough: true,
121
+ admin: { type: 'boolean', default: false },
122
+ betaTester: { type: 'boolean', default: false },
123
+ developer: { type: 'boolean', default: false },
124
+ },
125
+ flags: {
126
+ $passthrough: true,
127
+ signupProcessed: { type: 'boolean', default: false },
128
+ },
129
+ affiliate: {
130
+ code: { type: 'string', default: '$randomId' },
131
+ referrals: { type: 'array', default: [] },
132
+ },
133
+ metadata: {
134
+ created: '$timestamp:now',
135
+ updated: '$timestamp:now',
136
+ },
137
+ activity: {
138
+ geolocation: {
139
+ ip: { type: 'string', default: null, nullable: true },
140
+ continent: { type: 'string', default: null, nullable: true },
141
+ country: { type: 'string', default: null, nullable: true },
142
+ region: { type: 'string', default: null, nullable: true },
143
+ city: { type: 'string', default: null, nullable: true },
144
+ latitude: { type: 'number', default: 0 },
145
+ longitude: { type: 'number', default: 0 },
146
+ },
147
+ client: {
148
+ language: { type: 'string', default: null, nullable: true },
149
+ mobile: { type: 'boolean', default: false },
150
+ device: { type: 'string', default: null, nullable: true },
151
+ platform: { type: 'string', default: null, nullable: true },
152
+ browser: { type: 'string', default: null, nullable: true },
153
+ vendor: { type: 'string', default: null, nullable: true },
154
+ runtime: { type: 'string', default: null, nullable: true },
155
+ userAgent: { type: 'string', default: null, nullable: true },
156
+ url: { type: 'string', default: null, nullable: true },
157
+ },
158
+ },
159
+ api: {
160
+ clientId: { type: 'string', default: '$uuid' },
161
+ privateKey: { type: 'string', default: '$apiKey' },
162
+ },
163
+ // The documents this account owns, by KIND: `{ teams: ['team-abc'] }`
164
+ // ([#647](https://github.com/Omega-JS-Stack/omega/issues/647)). A counted
165
+ // feature's catalog entry names the kinds its counters mirror onto
166
+ // (`usage: { mirror: ['teams'] }`), and this is what resolves a kind to real
167
+ // document paths — so a mirror is declared once in config instead of
168
+ // re-derived at every call site. Server-written like `usage`: a client that
169
+ // could write it could point another account's counters at its own doc.
170
+ owns: {
171
+ $passthrough: true,
172
+ },
173
+ usage: {
174
+ $passthrough: true,
175
+ // Admin-granted extra credits, feature id → number
176
+ // ([#647](https://github.com/Omega-JS-Stack/omega/issues/647)). Declared
177
+ // HERE so the sibling `$template` never resolves it as a counter block:
178
+ // it is a map of limits, not a map of counts. `usage` is a framework field
179
+ // the security rules deny every client, so an override can only ever be
180
+ // server-written — which is what makes it trustworthy as a limit.
181
+ overrides: { $passthrough: true },
182
+ $template: {
183
+ monthly: { type: 'number', default: 0 },
184
+ daily: { type: 'number', default: 0 },
185
+ total: { type: 'number', default: 0 },
186
+ // When the counter last moved. The old `id` field went with the retired
187
+ // `increment(name, value, { id })` option (#647): nothing writes it now,
188
+ // and a schema field nothing fills reads as a fact that is always null.
189
+ last: {
190
+ timestamp: { type: 'string', default: '$oldDate' },
191
+ timestampUNIX: { type: 'number', default: 0 },
192
+ },
193
+ },
194
+ },
195
+ personal: {
196
+ birthday: '$timestamp',
197
+ gender: { type: 'string', default: null, nullable: true },
198
+ location: {
199
+ country: { type: 'string', default: null, nullable: true },
200
+ region: { type: 'string', default: null, nullable: true },
201
+ city: { type: 'string', default: null, nullable: true },
202
+ postalCode: { type: 'string', default: null, nullable: true },
203
+ street: { type: 'string', default: null, nullable: true },
204
+ },
205
+ name: {
206
+ first: { type: 'string', default: null, nullable: true },
207
+ last: { type: 'string', default: null, nullable: true },
208
+ },
209
+ company: {
210
+ name: { type: 'string', default: null, nullable: true },
211
+ position: { type: 'string', default: null, nullable: true },
212
+ },
213
+ telephone: {
214
+ countryCode: { type: 'number', default: 0 },
215
+ national: { type: 'number', default: 0 },
216
+ },
217
+ },
218
+ connections: {
219
+ $passthrough: true,
220
+ },
221
+ attribution: {
222
+ affiliate: {
223
+ code: { type: 'string', default: null, nullable: true },
224
+ timestamp: { type: 'string', default: null, nullable: true },
225
+ url: { type: 'string', default: null, nullable: true },
226
+ page: { type: 'string', default: null, nullable: true },
227
+ },
228
+ // The touch model: `first` is the visit that first brought the user here,
229
+ // written once and never overwritten; `last` is the newest TAGGED visit. No
230
+ // expiry — the timestamps carry any read-time lookback window. Replaces the
231
+ // single `utm` blob outright (no dual-read).
232
+ first: ATTRIBUTION_TOUCH,
233
+ last: ATTRIBUTION_TOUCH,
234
+ },
235
+ // The tracking-consent snapshot the client captured, stored verbatim: passthrough
236
+ // because this layer never interprets it — the consent module owns its shape. NOT
237
+ // the same thing as `consent` below, which is the legal/marketing decision.
238
+ trackingConsent: {
239
+ $passthrough: true,
240
+ },
241
+ consent: {
242
+ legal: {
243
+ status: { type: 'string', default: 'revoked' },
244
+ grantedAt: {
245
+ timestamp: { type: 'string', default: null, nullable: true },
246
+ timestampUNIX: { type: 'number', default: null, nullable: true },
247
+ source: { type: 'string', default: null, nullable: true },
248
+ ip: { type: 'string', default: null, nullable: true },
249
+ text: { type: 'string', default: null, nullable: true },
250
+ },
251
+ },
252
+ marketing: {
253
+ status: { type: 'string', default: 'revoked' },
254
+ grantedAt: {
255
+ timestamp: { type: 'string', default: null, nullable: true },
256
+ timestampUNIX: { type: 'number', default: null, nullable: true },
257
+ source: { type: 'string', default: null, nullable: true },
258
+ ip: { type: 'string', default: null, nullable: true },
259
+ text: { type: 'string', default: null, nullable: true },
260
+ },
261
+ revokedAt: {
262
+ timestamp: { type: 'string', default: null, nullable: true },
263
+ timestampUNIX: { type: 'number', default: null, nullable: true },
264
+ source: { type: 'string', default: null, nullable: true },
265
+ ip: { type: 'string', default: null, nullable: true },
266
+ text: { type: 'string', default: null, nullable: true },
267
+ },
268
+ },
269
+ },
270
+ };
271
+
272
+ module.exports = USER_SCHEMA;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * resolveSubscription — extracted verbatim from @omega.js/backend's User.resolveSubscription
3
+ * (the superset: @omega.js/client's drifted copy lacked `everPaid`).
4
+ *
5
+ * Resolves calculated subscription fields that require derivation logic.
6
+ * Raw data (product.id, status, trial, cancellation) is on the account directly.
7
+ * Returns: { plan, active, trialing, cancelling, everPaid }
8
+ * - plan: the plan ID the user effectively has access to RIGHT NOW ('basic' if cancelled/suspended)
9
+ * - active: user has active access (active, trialing, or cancelling)
10
+ * - trialing: user is in an active trial (status is 'active' but trial hasn't expired)
11
+ * - cancelling: cancellation is pending (status is 'active' but cancellation.pending is true)
12
+ * - everPaid: user has had a paid subscription at some point (payment.startDate exists)
13
+ */
14
+ function resolveSubscription(account) {
15
+ const subscription = (account?.subscription || account?.properties?.subscription) || {};
16
+ const productId = subscription.product?.id || 'basic';
17
+
18
+ let trialing = false;
19
+ let cancelling = false;
20
+
21
+ if (productId !== 'basic' && subscription.status === 'active') {
22
+ trialing = !!(subscription.trial?.claimed
23
+ && subscription.trial?.expires?.timestampUNIX > Math.floor(Date.now() / 1000));
24
+ cancelling = !trialing && !!subscription.cancellation?.pending;
25
+ }
26
+
27
+ const active = (productId !== 'basic' && subscription.status === 'active');
28
+
29
+ return {
30
+ plan: active ? productId : 'basic',
31
+ active,
32
+ trialing,
33
+ cancelling,
34
+ everPaid: (subscription.payment?.startDate?.timestampUNIX || 0) > 0,
35
+ };
36
+ }
37
+
38
+ module.exports = resolveSubscription;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * GA4 adapter — Google Analytics 4.
3
+ *
4
+ * The canonical param vocabulary IS GA4's, so every entry without a `map()`
5
+ * passes straight through. Consent category: 'analytics'.
6
+ */
7
+
8
+ const { createAdapter, pick } = require('./resolve.js');
9
+
10
+ // GA4 takes campaign attribution as FLAT event params (the Measurement
11
+ // Protocol's campaign reference), so it rides the payload itself. `gclid` joins
12
+ // them as a plain param: GA4 has no match block to put a click id in, and it is
13
+ // what a Google Ads enhanced-conversions upload will key on when that platform
14
+ // file lands ([#385](https://github.com/Omega-JS-Stack/omega/issues/385)).
15
+ const ATTRIBUTION_PARAMS = ['campaign_id', 'campaign', 'source', 'medium', 'term', 'content', 'gclid'];
16
+
17
+ const adapter = createAdapter({
18
+ provider: 'ga4',
19
+ consentCategory: 'analytics',
20
+ attach: (descriptor, attribution) => {
21
+ Object.assign(descriptor.payload, pick(attribution, ATTRIBUTION_PARAMS));
22
+ },
23
+ });
24
+
25
+ module.exports = adapter;
26
+ module.exports.ATTRIBUTION_PARAMS = ATTRIBUTION_PARAMS;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Meta adapter — the Facebook Pixel and the Conversions API.
3
+ *
4
+ * Meta's commerce vocabulary (content_ids, num_items) differs from GA4's
5
+ * items[], so those entries carry a `map()` in the catalog; everything else
6
+ * passes through as custom data. Consent category: 'marketing'.
7
+ */
8
+
9
+ const { createAdapter, pick, attachPage } = require('./resolve.js');
10
+
11
+ // Meta's click/browser ids live in the user_data (match) block, never in the
12
+ // event's custom data.
13
+ const ATTRIBUTION_KEYS = ['fbc', 'fbp'];
14
+
15
+ const adapter = createAdapter({
16
+ provider: 'meta',
17
+ consentCategory: 'marketing',
18
+ attach: (descriptor, attribution) => {
19
+ Object.assign(descriptor.userData, pick(attribution, ATTRIBUTION_KEYS));
20
+ // The Conversions API's `event_source_url` (#497) — the transport's to place.
21
+ attachPage(descriptor, attribution);
22
+ },
23
+ });
24
+
25
+ module.exports = adapter;
26
+ module.exports.ATTRIBUTION_KEYS = ATTRIBUTION_KEYS;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The shared adapter mechanism — internal to the adapters.
3
+ *
4
+ * Every adapter is the same three steps: look the canonical event up in the
5
+ * catalog, take THIS provider's mapping (absent → null, the caller logs the
6
+ * dev skip), and run the entry's `map()` (or pass the canonical params
7
+ * through). Only the attribution attachment differs per provider, so that is
8
+ * the one thing an adapter file supplies.
9
+ *
10
+ * Pure: no I/O, no globals, no state. A descriptor is a plain object a
11
+ * transport executes.
12
+ */
13
+
14
+ const { entryFor } = require('../catalog.js');
15
+
16
+ /**
17
+ * Copy the keys a provider understands out of a flat source object.
18
+ * @param {object} source - The source object (may be undefined).
19
+ * @param {string[]} keys - The keys to take.
20
+ * @returns {object} Only the keys that are present.
21
+ */
22
+ function pick(source, keys) {
23
+ const out = {};
24
+ if (!source) {
25
+ return out;
26
+ }
27
+
28
+ for (const key of keys) {
29
+ if (source[key] !== undefined && source[key] !== null) {
30
+ out[key] = source[key];
31
+ }
32
+ }
33
+
34
+ return out;
35
+ }
36
+
37
+ // The page a conversion happened on, as the attribution touch captured it. Only
38
+ // the providers whose server API READS one take it (`attachPage` below).
39
+ const PAGE_KEYS = ['url', 'referrer'];
40
+
41
+ /**
42
+ * Attach the conversion's page to a descriptor — the touch's url, and its
43
+ * referrer when there is one
44
+ * ([#497](https://github.com/Omega-JS-Stack/omega/issues/497)).
45
+ *
46
+ * The URL is neither payload nor match data, so it rides its own descriptor
47
+ * member, and only when the touch actually carried one: a server conversion
48
+ * fires from a webhook or an auth trigger, and an INVENTED url is worse to a
49
+ * platform than an absent one. A referrer with no url is not half a page.
50
+ *
51
+ * @param {object} descriptor - The descriptor being built (mutated).
52
+ * @param {object} [attribution] - The flat attribution context.
53
+ */
54
+ function attachPage(descriptor, attribution) {
55
+ const page = pick(attribution, PAGE_KEYS);
56
+
57
+ if (page.url) {
58
+ descriptor.page = page;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Build one provider's adapter.
64
+ *
65
+ * @param {object} options
66
+ * @param {string} options.provider - The provider key, matching the catalog's `providers` key.
67
+ * @param {string} options.consentCategory - 'analytics' | 'marketing'.
68
+ * @param {Function} [options.attach] - (descriptor, attribution) => void; places the
69
+ * captured attribution where THIS provider wants it. Stage C
70
+ * ([#384](https://github.com/Omega-JS-Stack/omega/issues/384)) owns the capture;
71
+ * stage D ([#385](https://github.com/Omega-JS-Stack/omega/issues/385)) fills the
72
+ * rest of `userData` with server match data.
73
+ * @returns {{ provider: string, CONSENT_CATEGORY: string, resolve: Function }}
74
+ */
75
+ function createAdapter({ provider, consentCategory, attach }) {
76
+ /**
77
+ * Resolve a canonical event into this provider's descriptor.
78
+ * @param {string} canonicalName - The canonical event name.
79
+ * @param {object} [params] - The canonical params.
80
+ * @param {object} [context] - { attribution, consent, runtime }.
81
+ * @returns {{ provider: string, name: string, kind: string, payload: object, userData: object }|null}
82
+ */
83
+ function resolve(canonicalName, params = {}, context = {}) {
84
+ const entry = entryFor(canonicalName);
85
+ if (!entry) {
86
+ return null;
87
+ }
88
+
89
+ const mapping = entry.providers[provider];
90
+ if (!mapping) {
91
+ return null;
92
+ }
93
+
94
+ const descriptor = {
95
+ provider,
96
+ name: mapping.name,
97
+ kind: mapping.kind,
98
+ payload: mapping.map ? mapping.map(params, context) : { ...params },
99
+ // The identity/match block. Empty here by design: attribution lands via
100
+ // `attach` below, and the server enrichment is stage D's.
101
+ userData: {},
102
+ };
103
+
104
+ // The one mapping shape a transport executes differently: a provider's own
105
+ // pixel method (TikTok's `ttq.page()`), carried only when the catalog names
106
+ // one so nothing has to test for a key that is normally absent.
107
+ if (mapping.method) {
108
+ descriptor.method = mapping.method;
109
+ }
110
+
111
+ // The other optional mapping key a TRANSPORT reads rather than a platform's
112
+ // payload: Meta's `action_source`, the required server-event field naming
113
+ // WHERE a conversion happened. Carried the same way `method` is, only when
114
+ // the catalog overrides the default the sender supplies
115
+ // ([#498](https://github.com/Omega-JS-Stack/omega/issues/498)).
116
+ if (mapping.actionSource) {
117
+ descriptor.actionSource = mapping.actionSource;
118
+ }
119
+
120
+ if (attach) {
121
+ attach(descriptor, context.attribution);
122
+ }
123
+
124
+ return descriptor;
125
+ }
126
+
127
+ return { provider, CONSENT_CATEGORY: consentCategory, resolve };
128
+ }
129
+
130
+ module.exports = { createAdapter, pick, attachPage };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * TikTok adapter — the TikTok Pixel and the Events API.
3
+ *
4
+ * TikTok's commerce vocabulary is a `contents` array of product objects
5
+ * (content_id + content_name + price + quantity) beside the order's
6
+ * content_type/value/currency, so those entries carry a `map()` in the catalog;
7
+ * everything else passes through as properties. Consent category: 'marketing'.
8
+ */
9
+
10
+ const { createAdapter, pick, attachPage } = require('./resolve.js');
11
+
12
+ // TikTok's click id and cookie live in the Events API's context.user block —
13
+ // never in the event properties.
14
+ const ATTRIBUTION_KEYS = ['ttclid', 'ttp'];
15
+
16
+ const adapter = createAdapter({
17
+ provider: 'tiktok',
18
+ consentCategory: 'marketing',
19
+ attach: (descriptor, attribution) => {
20
+ Object.assign(descriptor.userData, pick(attribution, ATTRIBUTION_KEYS));
21
+ // The Events API 2.0 data item's `page` member (#497) — the transport's to place.
22
+ attachPage(descriptor, attribution);
23
+ },
24
+ });
25
+
26
+ module.exports = adapter;
27
+ module.exports.ATTRIBUTION_KEYS = ATTRIBUTION_KEYS;