@yesplz-ai/analytics-react-native 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,707 @@
1
+ // src/analytics.ts
2
+ import { Platform, AppState } from "react-native";
3
+
4
+ // src/constants.ts
5
+ var VERSION = "0.1.0-rn";
6
+ var EventType = {
7
+ ViewItemList: "view_item_list",
8
+ ViewItem: "yp_product_view",
9
+ ViewProduct: "yp_product_view",
10
+ SelectItem: "yp_product_select",
11
+ SelectProduct: "yp_product_select",
12
+ AddToCart: "yp_product_cart",
13
+ RemoveFromCart: "yp_product_remove_from_cart",
14
+ BeginCheckout: "yp_begin_checkout",
15
+ Purchase: "yp_product_purchase",
16
+ FavoriteProduct: "yp_product_favorite",
17
+ UnfavoriteProduct: "yp_product_unfavorite",
18
+ AddToWishlist: "yp_product_favorite",
19
+ RemoveFromWishlist: "yp_product_unfavorite",
20
+ ViewBrand: "yp_brand_view",
21
+ FavoriteBrand: "yp_brand_favorite",
22
+ UnfavoriteBrand: "yp_brand_unfavorite"
23
+ };
24
+ var GaEventType = {
25
+ [EventType.ViewProduct]: "view_item",
26
+ [EventType.SelectItem]: "select_item",
27
+ [EventType.AddToCart]: "add_to_cart",
28
+ [EventType.RemoveFromCart]: "remove_from_cart",
29
+ [EventType.BeginCheckout]: "begin_checkout",
30
+ [EventType.Purchase]: "purchase",
31
+ [EventType.FavoriteProduct]: "add_to_wishlist",
32
+ [EventType.UnfavoriteProduct]: "remove_from_wishlist"
33
+ };
34
+ var GaEventTypeReverse = {
35
+ view_item: EventType.ViewProduct,
36
+ select_item: EventType.SelectItem,
37
+ add_to_cart: EventType.AddToCart,
38
+ remove_from_cart: EventType.RemoveFromCart,
39
+ begin_checkout: EventType.BeginCheckout,
40
+ purchase: EventType.Purchase,
41
+ add_to_wishlist: EventType.FavoriteProduct,
42
+ remove_from_wishlist: EventType.UnfavoriteProduct
43
+ };
44
+ var ItemListId = {
45
+ SEARCH_RESULTS: "search-results",
46
+ PRODUCT_LIST: "product-list",
47
+ YOU_MAY_ALSO_LIKE: "you-may-also-like",
48
+ COMPLETE_THE_LOOK: "complete-the-look",
49
+ COLLABORATIVE_FILTERING: "collaborative-filtering",
50
+ MORE_FROM_BRAND: "more-from-brand",
51
+ STYLE_WITH: "style-with",
52
+ SHOPPING_CART: "shopping-cart",
53
+ ITEM_FOR_YOU: "item-for-you",
54
+ BRAND_FOR_YOU: "brand-for-you"
55
+ };
56
+ var LIST_ID_ALIASES = {
57
+ Search: ItemListId.SEARCH_RESULTS,
58
+ "search-results": ItemListId.SEARCH_RESULTS,
59
+ PLP: ItemListId.PRODUCT_LIST,
60
+ "product-list": ItemListId.PRODUCT_LIST,
61
+ YMAL: ItemListId.YOU_MAY_ALSO_LIKE,
62
+ "you-may-also-like": ItemListId.YOU_MAY_ALSO_LIKE,
63
+ CTL: ItemListId.COMPLETE_THE_LOOK,
64
+ "complete-the-look": ItemListId.COMPLETE_THE_LOOK,
65
+ CF: ItemListId.COLLABORATIVE_FILTERING,
66
+ "collaborative-filtering": ItemListId.COLLABORATIVE_FILTERING,
67
+ "frequently-bought-together": ItemListId.COLLABORATIVE_FILTERING,
68
+ Brand: ItemListId.MORE_FROM_BRAND,
69
+ "more-from-brand": ItemListId.MORE_FROM_BRAND,
70
+ STW: ItemListId.STYLE_WITH,
71
+ "style-with": ItemListId.STYLE_WITH,
72
+ CART: ItemListId.SHOPPING_CART,
73
+ "shopping-cart": ItemListId.SHOPPING_CART,
74
+ "item-for-you": ItemListId.ITEM_FOR_YOU,
75
+ "brand-for-you": ItemListId.BRAND_FOR_YOU
76
+ };
77
+ function normalizeItemListId(input) {
78
+ if (!input) return input;
79
+ const alias = LIST_ID_ALIASES[input];
80
+ if (alias) return alias;
81
+ const canonical = Object.values(ItemListId);
82
+ if (canonical.includes(input)) return input;
83
+ return input;
84
+ }
85
+ var API_EVENTS = [
86
+ EventType.ViewProduct,
87
+ EventType.AddToCart,
88
+ EventType.RemoveFromCart,
89
+ EventType.BeginCheckout,
90
+ EventType.Purchase,
91
+ EventType.FavoriteProduct,
92
+ EventType.UnfavoriteProduct,
93
+ EventType.ViewBrand,
94
+ EventType.FavoriteBrand,
95
+ EventType.UnfavoriteBrand
96
+ ];
97
+
98
+ // src/storage.ts
99
+ import AsyncStorage from "@react-native-async-storage/async-storage";
100
+ var StorageKeys = {
101
+ CID: "yp_cid",
102
+ SESSION: "yp_session",
103
+ USER_ID: "yp_user_id",
104
+ USER_PROPS: "yp_user_props",
105
+ ATTRIBUTION: "yp_attribution",
106
+ PENDING_EVENTS: "yp_pending_events"
107
+ };
108
+ var Storage = {
109
+ async getItem(key) {
110
+ try {
111
+ return await AsyncStorage.getItem(key);
112
+ } catch {
113
+ return null;
114
+ }
115
+ },
116
+ async setItem(key, value) {
117
+ try {
118
+ await AsyncStorage.setItem(key, value);
119
+ } catch {
120
+ }
121
+ },
122
+ async removeItem(key) {
123
+ try {
124
+ await AsyncStorage.removeItem(key);
125
+ } catch {
126
+ }
127
+ },
128
+ async getJSON(key, fallback) {
129
+ const raw = await Storage.getItem(key);
130
+ if (!raw) return fallback;
131
+ try {
132
+ return JSON.parse(raw);
133
+ } catch {
134
+ return fallback;
135
+ }
136
+ },
137
+ async setJSON(key, value) {
138
+ try {
139
+ await Storage.setItem(key, JSON.stringify(value));
140
+ } catch {
141
+ }
142
+ }
143
+ };
144
+
145
+ // src/ids.ts
146
+ function generateId() {
147
+ return Date.now().toString(36) + Math.random().toString(36).substring(2);
148
+ }
149
+ function hashCode(str) {
150
+ let hash = 0;
151
+ for (let i = 0; i < str.length; i++) {
152
+ hash = (hash << 5) - hash + str.charCodeAt(i);
153
+ hash |= 0;
154
+ }
155
+ return hash;
156
+ }
157
+ function numericSessionId(sessionId) {
158
+ if (!sessionId) return Math.floor(Date.now() / 1e3);
159
+ return Math.abs(hashCode(sessionId)) || Math.floor(Date.now() / 1e3);
160
+ }
161
+ async function loadIdentity() {
162
+ const [cidStored, sessionStored, userIdStored, userPropsStored] = await Promise.all([
163
+ Storage.getItem(StorageKeys.CID),
164
+ Storage.getItem(StorageKeys.SESSION),
165
+ Storage.getItem(StorageKeys.USER_ID),
166
+ Storage.getItem(StorageKeys.USER_PROPS)
167
+ ]);
168
+ let cid = cidStored;
169
+ if (!cid) {
170
+ cid = generateId();
171
+ await Storage.setItem(StorageKeys.CID, cid);
172
+ }
173
+ let sessionId = sessionStored;
174
+ if (!sessionId) {
175
+ sessionId = generateId();
176
+ await Storage.setItem(StorageKeys.SESSION, sessionId);
177
+ }
178
+ let userProperties = {};
179
+ if (userPropsStored) {
180
+ try {
181
+ userProperties = JSON.parse(userPropsStored);
182
+ } catch {
183
+ userProperties = {};
184
+ }
185
+ }
186
+ return { cid, sessionId, userId: userIdStored, userProperties };
187
+ }
188
+ async function saveSession(sessionId) {
189
+ await Storage.setItem(StorageKeys.SESSION, sessionId);
190
+ }
191
+ async function saveUser(userId, userProperties, cacheUserId) {
192
+ if (cacheUserId) {
193
+ await Storage.setItem(StorageKeys.USER_ID, userId);
194
+ }
195
+ await Storage.setJSON(StorageKeys.USER_PROPS, userProperties);
196
+ }
197
+ async function clearUser() {
198
+ await Storage.removeItem(StorageKeys.USER_ID);
199
+ await Storage.removeItem(StorageKeys.USER_PROPS);
200
+ }
201
+
202
+ // src/attribution.ts
203
+ var EXPIRY_MS = 30 * 60 * 1e3;
204
+ function normalizeId(rawId, idPrefix) {
205
+ if (!rawId) return null;
206
+ const idStr = String(rawId);
207
+ if (idPrefix && idStr.startsWith(idPrefix)) {
208
+ return idStr.slice(idPrefix.length);
209
+ }
210
+ const match = idStr.match(/^[a-zA-Z]+-(.+)$/);
211
+ return match ? match[1] : idStr;
212
+ }
213
+ async function loadStore() {
214
+ return Storage.getJSON(StorageKeys.ATTRIBUTION, {});
215
+ }
216
+ async function saveStore(store) {
217
+ await Storage.setJSON(StorageKeys.ATTRIBUTION, store);
218
+ }
219
+ function pruneExpired(store) {
220
+ const now = Date.now();
221
+ const next = {};
222
+ for (const [k, v] of Object.entries(store)) {
223
+ if (now - v.ts <= EXPIRY_MS) next[k] = v;
224
+ }
225
+ return next;
226
+ }
227
+ async function handleAttribution(eventType, props, idPrefix) {
228
+ const normalizedEventType = GaEventTypeReverse[eventType] || eventType;
229
+ if (normalizedEventType === EventType.SelectItem) {
230
+ const itemListId = props.item_list_id;
231
+ const rawId = props.id ?? props.items?.[0]?.item_id;
232
+ const productId = normalizeId(rawId, idPrefix);
233
+ if (itemListId && productId) {
234
+ let store = await loadStore();
235
+ store = pruneExpired(store);
236
+ store[productId] = { source: itemListId, ts: Date.now() };
237
+ await saveStore(store);
238
+ }
239
+ return;
240
+ }
241
+ if (normalizedEventType === EventType.ViewProduct) {
242
+ const rawId = props.id ?? props.items?.[0]?.item_id;
243
+ const productId = normalizeId(rawId, idPrefix);
244
+ if (!productId) return;
245
+ const store = await loadStore();
246
+ const entry = store[productId];
247
+ if (entry && Date.now() - entry.ts <= EXPIRY_MS) {
248
+ props.item_list_id = entry.source;
249
+ if (Array.isArray(props.items)) {
250
+ props.items = props.items.map((item) => ({ ...item, item_list_id: entry.source }));
251
+ }
252
+ }
253
+ return;
254
+ }
255
+ if (normalizedEventType === EventType.AddToCart) {
256
+ if (!Array.isArray(props.items)) return;
257
+ const store = await loadStore();
258
+ props.items = props.items.map((item) => {
259
+ const productId = normalizeId(item.item_id, idPrefix);
260
+ const entry = productId ? store[productId] : null;
261
+ if (entry && Date.now() - entry.ts <= EXPIRY_MS) {
262
+ return { ...item, item_list_id: entry.source };
263
+ }
264
+ return item;
265
+ });
266
+ const firstMatch = props.items.find((item) => item.item_list_id);
267
+ if (firstMatch?.item_list_id) {
268
+ props.item_list_id = firstMatch.item_list_id;
269
+ }
270
+ }
271
+ }
272
+
273
+ // src/ga.ts
274
+ function toSnakeCase(str) {
275
+ return str.replace(/-/g, "_");
276
+ }
277
+ async function sendToGA(eventType, props, options) {
278
+ const gaEvent = GaEventType[eventType] || toSnakeCase(eventType);
279
+ const {
280
+ items,
281
+ currency,
282
+ value,
283
+ transaction_id,
284
+ tax,
285
+ shipping,
286
+ coupon,
287
+ ...rest
288
+ } = props;
289
+ const params = {
290
+ session_id: options.numericSessionId,
291
+ engagement_time_msec: 1,
292
+ session_engaged: "1"
293
+ };
294
+ if (currency) params.currency = currency;
295
+ if (value !== void 0) params.value = value;
296
+ if (transaction_id) params.transaction_id = transaction_id;
297
+ if (tax !== void 0) params.tax = tax;
298
+ if (shipping !== void 0) params.shipping = shipping;
299
+ if (coupon) params.coupon = coupon;
300
+ if (items) params.items = items;
301
+ const passthroughKeys = [
302
+ "item_list_id",
303
+ "data_provider",
304
+ "event_source",
305
+ "id",
306
+ "search_term",
307
+ "total",
308
+ "total_text",
309
+ "total_vector",
310
+ "page_location",
311
+ "page_title",
312
+ "page_referrer",
313
+ "language",
314
+ "test_marker",
315
+ "yp_platform",
316
+ "yp_platform_version",
317
+ "yp_user_agent"
318
+ ];
319
+ for (const key of passthroughKeys) {
320
+ if (rest[key] !== void 0) params[key] = rest[key];
321
+ }
322
+ const body = {
323
+ client_id: options.clientId || options.sessionId,
324
+ timestamp_micros: Date.now() * 1e3,
325
+ events: [{ name: gaEvent, params }]
326
+ };
327
+ if (options.userId) body.user_id = options.userId;
328
+ const sep = options.gaProxyUrl.includes("?") ? "&" : "?";
329
+ const url = options.debug ? `${options.gaProxyUrl}${sep}debug=1` : options.gaProxyUrl;
330
+ try {
331
+ const res = await fetch(url, {
332
+ method: "POST",
333
+ headers: { "Content-Type": "application/json" },
334
+ body: JSON.stringify(body)
335
+ });
336
+ if (options.debug) {
337
+ console.log(`[YesPlz Analytics] GA MP \u2192 ${gaEvent} (status ${res.status})`);
338
+ }
339
+ } catch (e) {
340
+ if (options.debug) {
341
+ console.log(`[YesPlz Analytics] GA MP error: ${e.message}`);
342
+ }
343
+ }
344
+ }
345
+
346
+ // src/beacon.ts
347
+ var Beacon = class {
348
+ constructor(options) {
349
+ this.options = options;
350
+ this.queue = [];
351
+ this.isSending = false;
352
+ this.debounceTimer = null;
353
+ }
354
+ enqueue(event) {
355
+ this.queue.push(event);
356
+ this.scheduleSend();
357
+ }
358
+ enqueueAll(events) {
359
+ this.queue.push(...events);
360
+ this.scheduleSend();
361
+ }
362
+ scheduleSend() {
363
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
364
+ this.debounceTimer = setTimeout(() => {
365
+ void this.send();
366
+ }, this.options.debounceMs ?? 1e3);
367
+ }
368
+ async send(retryCount = 0) {
369
+ if (this.queue.length === 0) return;
370
+ if (!this.options.apiEndpoint) return;
371
+ if (this.isSending) return;
372
+ this.isSending = true;
373
+ const eventsToSend = this.queue.splice(0, this.queue.length);
374
+ const events = eventsToSend.map((event) => {
375
+ const merged = {
376
+ ...event,
377
+ ...this.options.defaultParams ?? {}
378
+ };
379
+ const rawId = event.extra_data.id;
380
+ if (rawId && this.options.idPrefix) {
381
+ merged.extra_data = {
382
+ ...event.extra_data,
383
+ id: `${this.options.idPrefix}${rawId}`
384
+ };
385
+ }
386
+ return merged;
387
+ });
388
+ try {
389
+ const headers = { "Content-Type": "application/json" };
390
+ if (this.options.apiToken) headers.Authorization = `Token ${this.options.apiToken}`;
391
+ const res = await fetch(this.options.apiEndpoint, {
392
+ method: "POST",
393
+ headers,
394
+ body: JSON.stringify(events)
395
+ });
396
+ if (res.status > 299) {
397
+ throw new Error(`Failed to send events (status ${res.status})`);
398
+ }
399
+ if (this.options.debug) {
400
+ console.log(`[YesPlz Analytics] Sync: ${events.length} events sent`);
401
+ }
402
+ await Storage.removeItem(StorageKeys.PENDING_EVENTS);
403
+ } catch (e) {
404
+ if (this.options.debug) {
405
+ console.log(`[YesPlz Analytics] Sync failed: ${e.message}`);
406
+ }
407
+ this.queue.unshift(...eventsToSend);
408
+ this.isSending = false;
409
+ if (retryCount < 1) {
410
+ setTimeout(() => void this.send(retryCount + 1), 2e3);
411
+ }
412
+ return;
413
+ }
414
+ this.isSending = false;
415
+ if (this.queue.length > 0) this.scheduleSend();
416
+ }
417
+ async flush() {
418
+ if (this.debounceTimer) {
419
+ clearTimeout(this.debounceTimer);
420
+ this.debounceTimer = null;
421
+ }
422
+ await this.send();
423
+ }
424
+ async savePending() {
425
+ if (this.queue.length === 0) return;
426
+ const toSave = this.queue.slice(-100);
427
+ await Storage.setJSON(StorageKeys.PENDING_EVENTS, toSave);
428
+ this.queue = [];
429
+ }
430
+ async restorePending() {
431
+ const pending = await Storage.getJSON(StorageKeys.PENDING_EVENTS, []);
432
+ if (pending.length > 0) {
433
+ this.queue.unshift(...pending);
434
+ await Storage.removeItem(StorageKeys.PENDING_EVENTS);
435
+ this.scheduleSend();
436
+ }
437
+ }
438
+ };
439
+
440
+ // src/analytics.ts
441
+ var DEFAULT_DEBOUNCE_MS = 1e3;
442
+ var DEFAULT_API_BASE_URL = "https://api.yesplz.ai";
443
+ var DEFAULT_GA_PROXY_BASE_URL = "https://ypsh-admin.yesplz.ai/api/mp/collect";
444
+ function buildUserAgent() {
445
+ const os = Platform.OS;
446
+ const version = Platform.Version;
447
+ return `YesPlzSDK-RN/${VERSION} (${os}; ${String(version)})`;
448
+ }
449
+ var Analytics = class {
450
+ constructor() {
451
+ this.config = {
452
+ debug: false,
453
+ debounceMs: DEFAULT_DEBOUNCE_MS,
454
+ cacheUserId: true,
455
+ idPrefix: ""
456
+ };
457
+ this.cid = null;
458
+ this.sessionId = null;
459
+ this.userId = null;
460
+ this.userProperties = {};
461
+ this.isInitialized = false;
462
+ this.initPromise = null;
463
+ this.beacon = null;
464
+ this.pendingTracks = [];
465
+ this.appStateSubscription = null;
466
+ }
467
+ init(appId, config = {}) {
468
+ if (this.isInitialized || this.initPromise) return this;
469
+ this.initPromise = this.initInternal(appId, config);
470
+ void this.initPromise;
471
+ return this;
472
+ }
473
+ ready() {
474
+ return this.initPromise ?? Promise.resolve();
475
+ }
476
+ async initInternal(appId, config) {
477
+ const normalizedAppId = String(appId).trim();
478
+ if (!normalizedAppId) {
479
+ throw new Error("[YesPlz Analytics] appId must be a non-empty string");
480
+ }
481
+ const apiBaseUrl = (config.apiBaseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, "");
482
+ const apiEndpoint = config.apiEndpoint || `${apiBaseUrl}/api/v1/retailer/${encodeURIComponent(normalizedAppId)}/interactions/`;
483
+ const gaProxyBaseUrl = config.gaProxyBaseUrl || DEFAULT_GA_PROXY_BASE_URL;
484
+ const gaProxySep = gaProxyBaseUrl.includes("?") ? "&" : "?";
485
+ const gaProxyUrl = `${gaProxyBaseUrl}${gaProxySep}shop=${encodeURIComponent(normalizedAppId)}`;
486
+ const mergedDefaultParams = {
487
+ ...config.defaultParams || {}
488
+ };
489
+ if (config.testMarker) {
490
+ mergedDefaultParams.test_marker = config.testMarker;
491
+ }
492
+ this.config = {
493
+ ...this.config,
494
+ appId: normalizedAppId,
495
+ apiEndpoint,
496
+ apiBaseUrl,
497
+ apiToken: config.apiToken ?? null,
498
+ gaProxyBaseUrl,
499
+ gaProxyUrl,
500
+ idPrefix: config.idPrefix || "",
501
+ defaultParams: mergedDefaultParams,
502
+ testMarker: config.testMarker,
503
+ debug: config.debug ?? false,
504
+ cacheUserId: config.cacheUserId ?? true,
505
+ debounceMs: config.debounceMs || DEFAULT_DEBOUNCE_MS
506
+ };
507
+ const identity = await loadIdentity();
508
+ this.cid = identity.cid;
509
+ this.sessionId = identity.sessionId;
510
+ this.userId = identity.userId;
511
+ this.userProperties = identity.userProperties;
512
+ if (config.userId) {
513
+ await this.setUserInternal(config.userId, config.userProperties || {});
514
+ }
515
+ if (apiEndpoint) {
516
+ this.beacon = new Beacon({
517
+ apiEndpoint,
518
+ apiToken: this.config.apiToken,
519
+ defaultParams: this.config.defaultParams,
520
+ idPrefix: this.config.idPrefix,
521
+ debounceMs: this.config.debounceMs,
522
+ debug: this.config.debug
523
+ });
524
+ await this.beacon.restorePending();
525
+ }
526
+ this.appStateSubscription = AppState.addEventListener(
527
+ "change",
528
+ (state) => {
529
+ if (state === "background" || state === "inactive") {
530
+ void this.beacon?.savePending();
531
+ }
532
+ }
533
+ );
534
+ this.isInitialized = true;
535
+ this.log("Initialized");
536
+ const queued = this.pendingTracks.splice(0, this.pendingTracks.length);
537
+ for (const item of queued) {
538
+ void this.dispatchTrack(item.eventType, item.properties);
539
+ }
540
+ }
541
+ async setUser(userId, userProperties = {}) {
542
+ await this.ready();
543
+ if (userId === null) {
544
+ this.userId = null;
545
+ this.userProperties = {};
546
+ await clearUser();
547
+ this.log("User cleared");
548
+ return this;
549
+ }
550
+ await this.setUserInternal(userId, userProperties);
551
+ return this;
552
+ }
553
+ async setUserInternal(userId, userProperties) {
554
+ const id = String(userId);
555
+ if (id.length === 0) {
556
+ console.error("[YesPlz Analytics] User ID must be a non-empty value");
557
+ return;
558
+ }
559
+ this.userId = id;
560
+ this.userProperties = JSON.parse(JSON.stringify(userProperties));
561
+ await saveUser(this.userId, this.userProperties, this.config.cacheUserId);
562
+ this.log(`User set: ${id}`);
563
+ }
564
+ getUser() {
565
+ if (!this.userId) return null;
566
+ return { userId: this.userId, userProperties: this.userProperties };
567
+ }
568
+ getSessionId() {
569
+ return this.sessionId;
570
+ }
571
+ getClientId() {
572
+ return this.cid;
573
+ }
574
+ track(eventType, properties = {}) {
575
+ if (typeof eventType !== "string" || eventType.length === 0) {
576
+ console.error("[YesPlz Analytics] Event type must be a non-empty string");
577
+ return this;
578
+ }
579
+ if (!this.isInitialized) {
580
+ this.pendingTracks.push({
581
+ eventType,
582
+ properties: JSON.parse(JSON.stringify(properties))
583
+ });
584
+ return this;
585
+ }
586
+ void this.dispatchTrack(eventType, properties);
587
+ return this;
588
+ }
589
+ async dispatchTrack(eventType, properties) {
590
+ const props = JSON.parse(JSON.stringify(properties));
591
+ await handleAttribution(eventType, props, this.config.idPrefix);
592
+ if (eventType === EventType.Purchase && !props.transaction_id) {
593
+ props.transaction_id = generateId();
594
+ }
595
+ if (props.id && !Array.isArray(props.items)) {
596
+ props.items = [{ item_id: props.id, ...props }];
597
+ }
598
+ const metadata = this.collectMetadata();
599
+ Object.assign(props, metadata);
600
+ props.data_provider = "yesplz";
601
+ if (this.config.testMarker) {
602
+ props.test_marker = this.config.testMarker;
603
+ }
604
+ await this.sendGA(eventType, props);
605
+ const apiEventType = GaEventTypeReverse[eventType] || eventType;
606
+ if (API_EVENTS.includes(apiEventType)) {
607
+ this.queueApi(apiEventType, props, metadata);
608
+ }
609
+ }
610
+ async sendGA(eventType, props) {
611
+ if (!this.config.gaProxyUrl) return;
612
+ if (!this.cid || !this.sessionId) return;
613
+ const gaEventName = GaEventType[eventType] ?? eventType;
614
+ await sendToGA(eventType, props, {
615
+ gaProxyUrl: this.config.gaProxyUrl,
616
+ clientId: this.cid,
617
+ sessionId: this.sessionId,
618
+ numericSessionId: numericSessionId(this.sessionId),
619
+ userId: this.userId,
620
+ debug: this.config.debug
621
+ });
622
+ this.log(`Track: ${gaEventName}`, props.items?.length ? { items: props.items.length } : void 0);
623
+ }
624
+ queueApi(eventType, props, metadata) {
625
+ if (!this.beacon) return;
626
+ const items = props.items && props.items.length > 0 ? props.items : [props];
627
+ const fallbackUrl = `app://${this.config.appId ?? "yesplz"}/`;
628
+ const url = typeof props.page_location === "string" && props.page_location.length > 0 ? props.page_location : fallbackUrl;
629
+ const referrerUrl = typeof props.page_referrer === "string" && props.page_referrer.length > 0 ? props.page_referrer : null;
630
+ const events = items.map((item) => {
631
+ const merged = {
632
+ ...props,
633
+ ...item,
634
+ ...metadata
635
+ };
636
+ const itemId = item.item_id;
637
+ if (itemId) merged.id = itemId;
638
+ merged.identifier_type = eventType.startsWith("yp_brand_") ? "brand_name" : "product_id";
639
+ const event = {
640
+ event_type: eventType,
641
+ url,
642
+ referrer_url: referrerUrl,
643
+ user_id: this.userId,
644
+ session_id: this.sessionId,
645
+ extra_data: merged
646
+ };
647
+ if (Object.keys(this.userProperties).length > 0) {
648
+ event.extra_user_data = this.userProperties;
649
+ }
650
+ return event;
651
+ });
652
+ this.beacon.enqueueAll(events);
653
+ }
654
+ collectMetadata() {
655
+ const metadata = {
656
+ yp_client_time: Date.now(),
657
+ yp_platform: Platform.OS,
658
+ yp_platform_version: String(Platform.Version),
659
+ yp_user_agent: buildUserAgent(),
660
+ yp_timezone_offset: (/* @__PURE__ */ new Date()).getTimezoneOffset()
661
+ };
662
+ try {
663
+ metadata.yp_timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
664
+ } catch {
665
+ }
666
+ if (this.cid) metadata.google_client_id = this.cid;
667
+ return metadata;
668
+ }
669
+ async flush() {
670
+ await this.ready();
671
+ await this.beacon?.flush();
672
+ return this;
673
+ }
674
+ async reset() {
675
+ await this.ready();
676
+ this.userId = null;
677
+ this.userProperties = {};
678
+ await clearUser();
679
+ await Storage.removeItem(StorageKeys.PENDING_EVENTS);
680
+ await Storage.removeItem(StorageKeys.ATTRIBUTION);
681
+ const newSession = generateId();
682
+ this.sessionId = newSession;
683
+ await saveSession(newSession);
684
+ this.log("Reset");
685
+ return this;
686
+ }
687
+ teardown() {
688
+ this.appStateSubscription?.remove();
689
+ this.appStateSubscription = null;
690
+ }
691
+ log(message, data) {
692
+ if (this.config.debug) {
693
+ console.log(`[YesPlz Analytics] ${message}`, data ?? "");
694
+ }
695
+ }
696
+ };
697
+ var globalRef = globalThis;
698
+ var analytics = globalRef.__yesplz_analytics_rn__ || (globalRef.__yesplz_analytics_rn__ = new Analytics());
699
+ export {
700
+ Analytics,
701
+ EventType,
702
+ ItemListId,
703
+ VERSION,
704
+ analytics,
705
+ normalizeItemListId
706
+ };
707
+ //# sourceMappingURL=index.js.map