@tolinku/web-sdk 0.1.0 → 0.3.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/README.md +52 -2
- package/dist/index.d.mts +145 -3
- package/dist/index.d.ts +145 -3
- package/dist/index.js +454 -56
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +454 -56
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -275,6 +275,208 @@ var Analytics = class {
|
|
|
275
275
|
}
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
+
// src/ecommerce.ts
|
|
279
|
+
var BATCH_SIZE2 = 10;
|
|
280
|
+
var FLUSH_INTERVAL_MS2 = 5e3;
|
|
281
|
+
var MAX_QUEUE_SIZE2 = 500;
|
|
282
|
+
var CART_ID_KEY = "tolk_cart_id";
|
|
283
|
+
var Ecommerce = class {
|
|
284
|
+
// fallback when sessionStorage unavailable
|
|
285
|
+
constructor(client, getUserId) {
|
|
286
|
+
this.client = client;
|
|
287
|
+
this.queue = [];
|
|
288
|
+
this.flushTimer = null;
|
|
289
|
+
this.unloadHandler = null;
|
|
290
|
+
this.memoryCartId = null;
|
|
291
|
+
this.getUserId = getUserId;
|
|
292
|
+
if (typeof window !== "undefined") {
|
|
293
|
+
this.unloadHandler = () => this.flushBeacon();
|
|
294
|
+
window.addEventListener("beforeunload", this.unloadHandler);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// ─── Public methods (13 event types) ────────────────────
|
|
298
|
+
async viewItem(params) {
|
|
299
|
+
await this.enqueue({ event_type: "view_item", items: params.items });
|
|
300
|
+
}
|
|
301
|
+
async addToCart(params) {
|
|
302
|
+
const cartId = params.cart_id || this.getOrCreateCartId();
|
|
303
|
+
await this.enqueue({ event_type: "add_to_cart", items: params.items, cart_id: cartId });
|
|
304
|
+
}
|
|
305
|
+
async removeFromCart(params) {
|
|
306
|
+
await this.enqueue({ event_type: "remove_from_cart", items: params.items, cart_id: params.cart_id || this.getCartId() });
|
|
307
|
+
}
|
|
308
|
+
async addToWishlist(params) {
|
|
309
|
+
await this.enqueue({ event_type: "add_to_wishlist", items: params.items });
|
|
310
|
+
}
|
|
311
|
+
async viewCart() {
|
|
312
|
+
await this.enqueue({ event_type: "view_cart", cart_id: this.getCartId() });
|
|
313
|
+
}
|
|
314
|
+
async addPaymentInfo(params) {
|
|
315
|
+
await this.enqueue({ event_type: "add_payment_info", cart_id: params?.cart_id || this.getCartId() });
|
|
316
|
+
}
|
|
317
|
+
async beginCheckout(params) {
|
|
318
|
+
await this.enqueue({
|
|
319
|
+
event_type: "begin_checkout",
|
|
320
|
+
revenue: params.revenue,
|
|
321
|
+
currency: params.currency,
|
|
322
|
+
cart_id: params.cart_id || this.getCartId(),
|
|
323
|
+
items: params.items
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async purchase(params) {
|
|
327
|
+
const cartId = params.cart_id || this.getCartId();
|
|
328
|
+
await this.enqueue({
|
|
329
|
+
event_type: "purchase",
|
|
330
|
+
transaction_id: params.transaction_id,
|
|
331
|
+
revenue: params.revenue,
|
|
332
|
+
currency: params.currency,
|
|
333
|
+
cart_id: cartId,
|
|
334
|
+
coupon_code: params.coupon_code,
|
|
335
|
+
discount: params.discount,
|
|
336
|
+
shipping: params.shipping,
|
|
337
|
+
tax: params.tax,
|
|
338
|
+
items: params.items
|
|
339
|
+
});
|
|
340
|
+
this.clearCartId();
|
|
341
|
+
}
|
|
342
|
+
async refund(params) {
|
|
343
|
+
await this.enqueue({
|
|
344
|
+
event_type: "refund",
|
|
345
|
+
transaction_id: params.transaction_id,
|
|
346
|
+
revenue: params.revenue,
|
|
347
|
+
currency: params.currency,
|
|
348
|
+
items: params.items
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
async search(params) {
|
|
352
|
+
await this.enqueue({ event_type: "search", properties: { search_term: params.search_term } });
|
|
353
|
+
}
|
|
354
|
+
async share(params) {
|
|
355
|
+
const props = {};
|
|
356
|
+
if (params.item_id) props.item_id = params.item_id;
|
|
357
|
+
if (params.url) props.url = params.url;
|
|
358
|
+
if (params.method) props.method = params.method;
|
|
359
|
+
await this.enqueue({ event_type: "share", properties: props });
|
|
360
|
+
}
|
|
361
|
+
async rate(params) {
|
|
362
|
+
await this.enqueue({
|
|
363
|
+
event_type: "rate",
|
|
364
|
+
properties: {
|
|
365
|
+
item_id: params.item_id,
|
|
366
|
+
rating: String(params.rating),
|
|
367
|
+
...params.max_rating != null ? { max_rating: String(params.max_rating) } : {}
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
async spendCredits(params) {
|
|
372
|
+
await this.enqueue({ event_type: "spend_credits", revenue: params.revenue, currency: params.currency });
|
|
373
|
+
}
|
|
374
|
+
// ─── Flush ─────────────────────────────────────────────
|
|
375
|
+
async flush() {
|
|
376
|
+
if (this.flushTimer) {
|
|
377
|
+
clearTimeout(this.flushTimer);
|
|
378
|
+
this.flushTimer = null;
|
|
379
|
+
}
|
|
380
|
+
if (this.queue.length === 0) return;
|
|
381
|
+
const events = this.queue.splice(0);
|
|
382
|
+
try {
|
|
383
|
+
const result = await this.client.post(
|
|
384
|
+
"/v1/api/analytics/ecommerce/batch",
|
|
385
|
+
{ events }
|
|
386
|
+
);
|
|
387
|
+
if (result.errors && result.errors.length > 0) {
|
|
388
|
+
console.warn("[TolinkuSDK] Ecommerce batch partial failure:", result.errors);
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
this.queue.unshift(...events);
|
|
392
|
+
if (this.queue.length > MAX_QUEUE_SIZE2) {
|
|
393
|
+
this.queue.splice(0, this.queue.length - MAX_QUEUE_SIZE2);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
destroy() {
|
|
398
|
+
this.flushBeacon();
|
|
399
|
+
if (this.flushTimer) {
|
|
400
|
+
clearTimeout(this.flushTimer);
|
|
401
|
+
this.flushTimer = null;
|
|
402
|
+
}
|
|
403
|
+
if (typeof window !== "undefined" && this.unloadHandler) {
|
|
404
|
+
window.removeEventListener("beforeunload", this.unloadHandler);
|
|
405
|
+
this.unloadHandler = null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// ─── Private ───────────────────────────────────────────
|
|
409
|
+
async enqueue(event) {
|
|
410
|
+
const userId = this.getUserId();
|
|
411
|
+
if (userId) event.user_id = userId;
|
|
412
|
+
this.queue.push(event);
|
|
413
|
+
if (this.queue.length === 1 && !this.flushTimer) {
|
|
414
|
+
this.flushTimer = setTimeout(() => {
|
|
415
|
+
this.flushTimer = null;
|
|
416
|
+
this.flush();
|
|
417
|
+
}, FLUSH_INTERVAL_MS2);
|
|
418
|
+
}
|
|
419
|
+
if (this.queue.length >= BATCH_SIZE2) {
|
|
420
|
+
await this.flush();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
flushBeacon() {
|
|
424
|
+
if (this.queue.length === 0) return;
|
|
425
|
+
const events = this.queue.splice(0);
|
|
426
|
+
const url = this.client.baseUrl + "/v1/api/analytics/ecommerce/batch";
|
|
427
|
+
const body = JSON.stringify({ events, apiKey: this.client.key });
|
|
428
|
+
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
429
|
+
navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// ─── Cart ID lifecycle ─────────────────────────────────
|
|
433
|
+
getOrCreateCartId() {
|
|
434
|
+
const existing = this.getCartId();
|
|
435
|
+
if (existing) return existing;
|
|
436
|
+
const cartId = this.generateId();
|
|
437
|
+
this.setCartId(cartId);
|
|
438
|
+
return cartId;
|
|
439
|
+
}
|
|
440
|
+
getCartId() {
|
|
441
|
+
try {
|
|
442
|
+
if (typeof sessionStorage !== "undefined") {
|
|
443
|
+
const stored = sessionStorage.getItem(CART_ID_KEY);
|
|
444
|
+
if (stored) return stored;
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
return this.memoryCartId || void 0;
|
|
449
|
+
}
|
|
450
|
+
setCartId(cartId) {
|
|
451
|
+
this.memoryCartId = cartId;
|
|
452
|
+
try {
|
|
453
|
+
if (typeof sessionStorage !== "undefined") {
|
|
454
|
+
sessionStorage.setItem(CART_ID_KEY, cartId);
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
clearCartId() {
|
|
460
|
+
this.memoryCartId = null;
|
|
461
|
+
try {
|
|
462
|
+
if (typeof sessionStorage !== "undefined") {
|
|
463
|
+
sessionStorage.removeItem(CART_ID_KEY);
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
generateId() {
|
|
469
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
470
|
+
return crypto.randomUUID();
|
|
471
|
+
}
|
|
472
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
473
|
+
const r = Math.random() * 16 | 0;
|
|
474
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
475
|
+
return v.toString(16);
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
278
480
|
// src/referrals.ts
|
|
279
481
|
var Referrals = class {
|
|
280
482
|
constructor(client) {
|
|
@@ -344,9 +546,24 @@ var Deferred = class {
|
|
|
344
546
|
timezone: options.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
345
547
|
language: options.language || navigator.language,
|
|
346
548
|
screen_width: options.screenWidth || window.screen.width,
|
|
347
|
-
screen_height: options.screenHeight || window.screen.height
|
|
549
|
+
screen_height: options.screenHeight || window.screen.height,
|
|
550
|
+
// Separates devices reporting identical logical dimensions.
|
|
551
|
+
device_pixel_ratio: options.devicePixelRatio || window.devicePixelRatio || 1
|
|
348
552
|
});
|
|
349
553
|
} catch (err) {
|
|
554
|
+
const status = err?.statusCode ?? err?.status;
|
|
555
|
+
if (status === 404) {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
if (status === 403) {
|
|
559
|
+
console.warn(
|
|
560
|
+
"[Tolinku] Failed to claim deferred link by signals: HTTP 403.",
|
|
561
|
+
"Check that appspaceId is your Appspace ID (copy it from the dashboard",
|
|
562
|
+
"under Settings), not your subdomain or slug.",
|
|
563
|
+
err
|
|
564
|
+
);
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
350
567
|
console.warn("[Tolinku] Failed to claim deferred link by signals:", err);
|
|
351
568
|
return null;
|
|
352
569
|
}
|
|
@@ -436,11 +653,56 @@ function sanitizeCssColor(value) {
|
|
|
436
653
|
}
|
|
437
654
|
|
|
438
655
|
// src/banners.ts
|
|
656
|
+
var themes = {
|
|
657
|
+
light: {
|
|
658
|
+
bg: "#ffffff",
|
|
659
|
+
border: "",
|
|
660
|
+
shadow: "sm",
|
|
661
|
+
title: { color: "#000000", size: 14, weight: 600 },
|
|
662
|
+
body: { color: "#000000", size: 12, weight: 400 },
|
|
663
|
+
cta: { size: 13, weight: 600, radius: 100 },
|
|
664
|
+
icon: { size: 40, radius: 10 }
|
|
665
|
+
},
|
|
666
|
+
dark: {
|
|
667
|
+
bg: "#1B1B1B",
|
|
668
|
+
border: "",
|
|
669
|
+
shadow: "sm",
|
|
670
|
+
title: { color: "#ffffff", size: 14, weight: 600 },
|
|
671
|
+
body: { color: "#ffffff", size: 12, weight: 400 },
|
|
672
|
+
cta: { size: 13, weight: 600, radius: 100 },
|
|
673
|
+
icon: { size: 40, radius: 10 }
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
var SHADOW_VALUES = {
|
|
677
|
+
none: "none",
|
|
678
|
+
sm: "0 2px 8px rgba(0,0,0,0.15)",
|
|
679
|
+
md: "0 6px 20px rgba(0,0,0,0.18)",
|
|
680
|
+
lg: "0 12px 36px rgba(0,0,0,0.22)"
|
|
681
|
+
};
|
|
682
|
+
function clampInt(val, min, max) {
|
|
683
|
+
if (val === void 0 || val === null || isNaN(val)) return null;
|
|
684
|
+
return Math.max(min, Math.min(max, Math.floor(val)));
|
|
685
|
+
}
|
|
686
|
+
function sanitizeClass(val) {
|
|
687
|
+
if (!val || typeof val !== "string") return "";
|
|
688
|
+
return val.replace(/[^a-zA-Z0-9_\-\s]/g, "").slice(0, 100).trim();
|
|
689
|
+
}
|
|
690
|
+
function isSafeUrl(url) {
|
|
691
|
+
try {
|
|
692
|
+
const parsed = new URL(url, window.location.href);
|
|
693
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
694
|
+
} catch {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
439
698
|
var Banners = class {
|
|
440
699
|
constructor(client) {
|
|
441
700
|
this.client = client;
|
|
442
701
|
this.container = null;
|
|
443
702
|
this.styleEl = null;
|
|
703
|
+
this.currentAnimation = "slide";
|
|
704
|
+
this.currentStyle = "pinned";
|
|
705
|
+
this.currentPosition = "top";
|
|
444
706
|
}
|
|
445
707
|
/** Fetch banner config and show the highest-priority banner */
|
|
446
708
|
async show(options = {}, userId) {
|
|
@@ -458,94 +720,200 @@ var Banners = class {
|
|
|
458
720
|
}
|
|
459
721
|
}
|
|
460
722
|
if (!banner) return;
|
|
461
|
-
|
|
723
|
+
const delay = clampInt(options.delay, 0, 6e4);
|
|
724
|
+
if (delay && delay > 0) {
|
|
725
|
+
setTimeout(() => this.render(config, banner, options), delay);
|
|
726
|
+
} else {
|
|
727
|
+
this.render(config, banner, options);
|
|
728
|
+
}
|
|
462
729
|
}
|
|
463
|
-
/** Remove the banner from the DOM */
|
|
730
|
+
/** Remove the banner from the DOM, respecting the active animation */
|
|
464
731
|
dismiss() {
|
|
465
|
-
if (this.container)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
732
|
+
if (!this.container) return;
|
|
733
|
+
const container = this.container;
|
|
734
|
+
const styleEl = this.styleEl;
|
|
735
|
+
container.classList.remove("tolk-visible");
|
|
736
|
+
if (this.currentStyle === "pinned") {
|
|
737
|
+
document.body.style.removeProperty(this.currentPosition === "top" ? "padding-top" : "padding-bottom");
|
|
738
|
+
}
|
|
739
|
+
const cleanup = () => {
|
|
740
|
+
container.remove();
|
|
741
|
+
styleEl?.remove();
|
|
742
|
+
if (this.container === container) {
|
|
472
743
|
this.container = null;
|
|
473
744
|
this.styleEl = null;
|
|
474
|
-
}
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
if (this.currentAnimation === "none") {
|
|
748
|
+
cleanup();
|
|
749
|
+
} else {
|
|
750
|
+
setTimeout(cleanup, 400);
|
|
475
751
|
}
|
|
476
752
|
}
|
|
477
753
|
render(config, banner, options) {
|
|
478
754
|
if (this.container) {
|
|
479
755
|
this.container.remove();
|
|
480
756
|
this.styleEl?.remove();
|
|
757
|
+
if (this.currentStyle === "pinned") {
|
|
758
|
+
document.body.style.removeProperty(this.currentPosition === "top" ? "padding-top" : "padding-bottom");
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
const themeName = options.theme && themes[options.theme] ? options.theme : "light";
|
|
762
|
+
const theme = themes[themeName];
|
|
763
|
+
let position = "top";
|
|
764
|
+
if (options.position === "top" || options.position === "bottom") position = options.position;
|
|
765
|
+
else if (banner.position === "top" || banner.position === "bottom") position = banner.position;
|
|
766
|
+
let resolvedStyle = "pinned";
|
|
767
|
+
if (options.style === "pinned" || options.style === "floating" || options.style === "stacked") {
|
|
768
|
+
resolvedStyle = options.style;
|
|
769
|
+
} else if (banner.style === "pinned" || banner.style === "floating" || banner.style === "stacked") {
|
|
770
|
+
resolvedStyle = banner.style;
|
|
481
771
|
}
|
|
482
|
-
const
|
|
483
|
-
const
|
|
484
|
-
|
|
772
|
+
const floating = resolvedStyle === "floating";
|
|
773
|
+
const stacked = resolvedStyle === "stacked";
|
|
774
|
+
let animation = options.animation || "slide";
|
|
775
|
+
if (animation === "pop" && !floating && !stacked) animation = "slide";
|
|
776
|
+
this.currentPosition = position;
|
|
777
|
+
this.currentStyle = resolvedStyle;
|
|
778
|
+
this.currentAnimation = animation;
|
|
779
|
+
const serverBg = sanitizeCssColor(banner.background_color);
|
|
780
|
+
const serverText = sanitizeCssColor(banner.text_color);
|
|
781
|
+
const optBg = sanitizeCssColor(options.bg);
|
|
782
|
+
const optBorder = sanitizeCssColor(options.border);
|
|
783
|
+
const optTitleColor = sanitizeCssColor(options.titleColor);
|
|
784
|
+
const optBodyColor = sanitizeCssColor(options.bodyColor);
|
|
785
|
+
const optCtaBg = sanitizeCssColor(options.ctaBg);
|
|
786
|
+
const optCtaColor = sanitizeCssColor(options.ctaColor);
|
|
787
|
+
const bg = optBg || serverBg || theme.bg;
|
|
788
|
+
const border = optBorder || theme.border;
|
|
789
|
+
const titleColor = optTitleColor || serverText || theme.title.color;
|
|
790
|
+
const bodyColor = optBodyColor || serverText || theme.body.color;
|
|
791
|
+
const ctaBg = optCtaBg || theme.cta.bg || titleColor;
|
|
792
|
+
const ctaColor = optCtaColor || theme.cta.color || bg;
|
|
793
|
+
const titleSize = clampInt(options.titleSize, 10, 24) ?? theme.title.size;
|
|
794
|
+
const titleWeight = options.titleWeight ?? theme.title.weight;
|
|
795
|
+
const bodySize = clampInt(options.bodySize, 10, 20) ?? theme.body.size;
|
|
796
|
+
const bodyWeight = options.bodyWeight ?? theme.body.weight;
|
|
797
|
+
const ctaSize = clampInt(options.ctaSize, 10, 18) ?? theme.cta.size;
|
|
798
|
+
const ctaWeight = options.ctaWeight ?? theme.cta.weight;
|
|
799
|
+
const ctaRadius = clampInt(options.ctaRadius, 0, 100) ?? theme.cta.radius;
|
|
800
|
+
const iconSize = clampInt(options.iconSize, 24, 64) ?? theme.icon.size;
|
|
801
|
+
const iconRadius = clampInt(options.iconRadius, 0, 32) ?? theme.icon.radius;
|
|
802
|
+
const optRadius = clampInt(options.radius, 0, 24);
|
|
803
|
+
const serverRadius = typeof banner.radius === "number" && banner.radius >= 0 && banner.radius <= 24 ? banner.radius : null;
|
|
804
|
+
const optMargin = clampInt(options.margin, 0, 24);
|
|
805
|
+
const serverMargin = typeof banner.margin === "number" && banner.margin >= 0 && banner.margin <= 24 ? banner.margin : null;
|
|
806
|
+
const bannerRadius = floating ? optRadius ?? serverRadius ?? 12 : 0;
|
|
807
|
+
const bannerMargin = floating ? optMargin ?? serverMargin ?? 12 : 0;
|
|
808
|
+
const shadowKey = options.shadow && SHADOW_VALUES[options.shadow] ? options.shadow : banner.shadow && SHADOW_VALUES[banner.shadow] ? banner.shadow : theme.shadow;
|
|
809
|
+
const shadow = SHADOW_VALUES[shadowKey];
|
|
810
|
+
const hideIcon = !!options.hideIcon;
|
|
811
|
+
const hideClose = !!options.hideClose;
|
|
812
|
+
const hideBody = !!options.hideBody;
|
|
813
|
+
const customClass = sanitizeClass(options.customClass);
|
|
485
814
|
const ctaText = banner.cta_text || "Open";
|
|
486
|
-
const
|
|
487
|
-
const installUrl = banner.action_url || baseUrl + (config.install_url || "/install");
|
|
815
|
+
const installUrl = banner.action_url || this.client.baseUrl + (config.install_url || "/install");
|
|
488
816
|
const safeTop = position === "top" ? "padding-top: env(safe-area-inset-top, 0px);" : "";
|
|
489
817
|
const safeBottom = position === "bottom" ? "padding-bottom: env(safe-area-inset-bottom, 0px);" : "";
|
|
818
|
+
let containerPosition;
|
|
819
|
+
let containerInsets;
|
|
820
|
+
if (stacked) {
|
|
821
|
+
containerPosition = "position: sticky;";
|
|
822
|
+
containerInsets = `${position}: 0;`;
|
|
823
|
+
} else if (floating) {
|
|
824
|
+
containerPosition = "position: fixed;";
|
|
825
|
+
containerInsets = `left: ${bannerMargin}px; right: ${bannerMargin}px; ${position}: ${bannerMargin}px;`;
|
|
826
|
+
} else {
|
|
827
|
+
containerPosition = "position: fixed;";
|
|
828
|
+
containerInsets = `left: 0; right: 0; ${position}: 0;`;
|
|
829
|
+
}
|
|
830
|
+
const hideOffset = bannerMargin + 60;
|
|
831
|
+
const slideHide = position === "top" ? `translateY(calc(-100% - ${hideOffset}px))` : `translateY(calc(100% + ${hideOffset}px))`;
|
|
832
|
+
let hiddenCss = "";
|
|
833
|
+
let visibleCss = "";
|
|
834
|
+
let transitionCss = "";
|
|
835
|
+
if (animation === "slide") {
|
|
836
|
+
hiddenCss = `transform: ${slideHide};`;
|
|
837
|
+
visibleCss = "transform: translateY(0);";
|
|
838
|
+
transitionCss = "transition: transform 0.35s ease;";
|
|
839
|
+
} else if (animation === "fade") {
|
|
840
|
+
hiddenCss = "opacity: 0;";
|
|
841
|
+
visibleCss = "opacity: 1;";
|
|
842
|
+
transitionCss = "transition: opacity 0.3s ease;";
|
|
843
|
+
} else if (animation === "pop") {
|
|
844
|
+
hiddenCss = "opacity: 0; transform: scale(0.92);";
|
|
845
|
+
visibleCss = "opacity: 1; transform: scale(1);";
|
|
846
|
+
transitionCss = "transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.25s ease;";
|
|
847
|
+
}
|
|
490
848
|
const container = document.createElement("div");
|
|
491
849
|
container.id = "tolinku-banner";
|
|
492
850
|
container.setAttribute("role", "banner");
|
|
493
851
|
container.setAttribute("aria-live", "polite");
|
|
494
852
|
container.dataset.position = position;
|
|
853
|
+
if (customClass) container.className = customClass;
|
|
854
|
+
const borderCss = border ? `border: 1px solid ${border};` : "";
|
|
855
|
+
const radiusCss = bannerRadius > 0 ? `border-radius: ${bannerRadius}px;` : "";
|
|
495
856
|
const style = document.createElement("style");
|
|
496
857
|
style.textContent = `
|
|
497
858
|
#tolinku-banner {
|
|
498
|
-
|
|
499
|
-
${
|
|
500
|
-
left: 0; right: 0;
|
|
859
|
+
${containerPosition}
|
|
860
|
+
${containerInsets}
|
|
501
861
|
z-index: 999999;
|
|
502
862
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
503
|
-
|
|
504
|
-
|
|
863
|
+
${hiddenCss}
|
|
864
|
+
${transitionCss}
|
|
505
865
|
${safeTop}${safeBottom}
|
|
506
866
|
}
|
|
507
|
-
#tolinku-banner.tolk-visible {
|
|
867
|
+
#tolinku-banner.tolk-visible { ${visibleCss} }
|
|
508
868
|
#tolinku-banner .tolk-inner {
|
|
509
869
|
display: flex; align-items: center; gap: 10px;
|
|
510
870
|
padding: 10px 14px;
|
|
511
|
-
background: ${
|
|
512
|
-
|
|
871
|
+
background: ${bg};
|
|
872
|
+
${borderCss}
|
|
873
|
+
${radiusCss}
|
|
874
|
+
box-shadow: ${shadow};
|
|
513
875
|
}
|
|
514
876
|
#tolinku-banner .tolk-close {
|
|
515
877
|
background: none; border: none; font-size: 20px; line-height: 1;
|
|
516
|
-
cursor: pointer; color: ${
|
|
878
|
+
cursor: pointer; color: ${titleColor}; opacity: 0.6; padding: 0 4px; flex-shrink: 0;
|
|
517
879
|
}
|
|
518
880
|
#tolinku-banner .tolk-close:hover { opacity: 1; }
|
|
519
881
|
#tolinku-banner .tolk-icon {
|
|
520
|
-
width:
|
|
882
|
+
width: ${iconSize}px; height: ${iconSize}px; border-radius: ${iconRadius}px;
|
|
883
|
+
flex-shrink: 0; object-fit: cover;
|
|
521
884
|
}
|
|
522
885
|
#tolinku-banner .tolk-text { flex: 1; min-width: 0; }
|
|
523
886
|
#tolinku-banner .tolk-title {
|
|
524
|
-
font-size:
|
|
887
|
+
font-size: ${titleSize}px; font-weight: ${titleWeight}; line-height: 1.3;
|
|
888
|
+
color: ${titleColor}; margin: 0;
|
|
525
889
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
526
890
|
}
|
|
527
891
|
#tolinku-banner .tolk-body {
|
|
528
|
-
font-size:
|
|
892
|
+
font-size: ${bodySize}px; font-weight: ${bodyWeight}; line-height: 1.3;
|
|
893
|
+
color: ${bodyColor}; margin: 0; opacity: 0.75;
|
|
529
894
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
530
895
|
}
|
|
531
896
|
#tolinku-banner .tolk-cta {
|
|
532
|
-
display: inline-block; padding:
|
|
533
|
-
font-size:
|
|
534
|
-
background: ${
|
|
897
|
+
display: inline-block; padding: 0.5em 1.2em; border-radius: ${ctaRadius}px;
|
|
898
|
+
font-size: ${ctaSize}px; font-weight: ${ctaWeight}; line-height: 1.2;
|
|
899
|
+
text-decoration: none; background: ${ctaBg}; color: ${ctaColor};
|
|
900
|
+
flex-shrink: 0; text-align: center;
|
|
535
901
|
}
|
|
536
902
|
`;
|
|
537
903
|
const inner = document.createElement("div");
|
|
538
904
|
inner.className = "tolk-inner";
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
905
|
+
if (!hideClose) {
|
|
906
|
+
const closeBtn = document.createElement("button");
|
|
907
|
+
closeBtn.className = "tolk-close";
|
|
908
|
+
closeBtn.setAttribute("aria-label", "Dismiss banner");
|
|
909
|
+
closeBtn.textContent = "\xD7";
|
|
910
|
+
closeBtn.addEventListener("click", () => {
|
|
911
|
+
saveBannerDismissal(banner.id);
|
|
912
|
+
this.dismiss();
|
|
913
|
+
});
|
|
914
|
+
inner.appendChild(closeBtn);
|
|
915
|
+
}
|
|
916
|
+
if (!hideIcon && config.app_icon && isSafeUrl(config.app_icon)) {
|
|
549
917
|
const icon = document.createElement("img");
|
|
550
918
|
icon.className = "tolk-icon";
|
|
551
919
|
icon.src = config.app_icon;
|
|
@@ -558,7 +926,7 @@ var Banners = class {
|
|
|
558
926
|
titleEl.className = "tolk-title";
|
|
559
927
|
titleEl.textContent = banner.title || config.app_name || "Get the App";
|
|
560
928
|
textWrap.appendChild(titleEl);
|
|
561
|
-
if (banner.body) {
|
|
929
|
+
if (!hideBody && banner.body) {
|
|
562
930
|
const bodyEl = document.createElement("p");
|
|
563
931
|
bodyEl.className = "tolk-body";
|
|
564
932
|
bodyEl.textContent = banner.body;
|
|
@@ -572,29 +940,57 @@ var Banners = class {
|
|
|
572
940
|
inner.appendChild(cta);
|
|
573
941
|
container.appendChild(inner);
|
|
574
942
|
document.head.appendChild(style);
|
|
575
|
-
|
|
943
|
+
if (stacked) {
|
|
944
|
+
const anchor = findStackedAnchor(position, options.anchor);
|
|
945
|
+
if (anchor && anchor.parentNode) {
|
|
946
|
+
if (position === "top") {
|
|
947
|
+
anchor.parentNode.insertBefore(container, anchor);
|
|
948
|
+
} else if (anchor.nextSibling) {
|
|
949
|
+
anchor.parentNode.insertBefore(container, anchor.nextSibling);
|
|
950
|
+
} else {
|
|
951
|
+
anchor.parentNode.appendChild(container);
|
|
952
|
+
}
|
|
953
|
+
} else if (document.body) {
|
|
954
|
+
if (position === "top") {
|
|
955
|
+
document.body.insertBefore(container, document.body.firstChild);
|
|
956
|
+
} else {
|
|
957
|
+
document.body.appendChild(container);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
document.body.appendChild(container);
|
|
962
|
+
}
|
|
576
963
|
this.container = container;
|
|
577
964
|
this.styleEl = style;
|
|
578
965
|
requestAnimationFrame(() => {
|
|
579
966
|
requestAnimationFrame(() => {
|
|
580
967
|
container.classList.add("tolk-visible");
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
968
|
+
if (!floating && !stacked) {
|
|
969
|
+
const bannerHeight = container.offsetHeight + "px";
|
|
970
|
+
if (position === "top") {
|
|
971
|
+
document.body.style.paddingTop = bannerHeight;
|
|
972
|
+
} else {
|
|
973
|
+
document.body.style.paddingBottom = bannerHeight;
|
|
974
|
+
}
|
|
586
975
|
}
|
|
587
976
|
});
|
|
588
977
|
});
|
|
589
978
|
}
|
|
590
979
|
};
|
|
591
|
-
function
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
980
|
+
function findStackedAnchor(position, explicit) {
|
|
981
|
+
if (explicit) {
|
|
982
|
+
try {
|
|
983
|
+
const el = document.querySelector(explicit);
|
|
984
|
+
if (el) return el;
|
|
985
|
+
} catch {
|
|
986
|
+
}
|
|
597
987
|
}
|
|
988
|
+
const selectors = position === "top" ? ["header", '[role="banner"]', "nav", ".header", "#header", ".navbar"] : ["footer", '[role="contentinfo"]', ".footer", "#footer"];
|
|
989
|
+
for (const s of selectors) {
|
|
990
|
+
const el = document.body?.querySelector(s);
|
|
991
|
+
if (el) return el;
|
|
992
|
+
}
|
|
993
|
+
return null;
|
|
598
994
|
}
|
|
599
995
|
|
|
600
996
|
// src/messages.ts
|
|
@@ -855,6 +1251,7 @@ var Tolinku = class {
|
|
|
855
1251
|
}
|
|
856
1252
|
this.client = new HttpClient(resolvedConfig);
|
|
857
1253
|
this.analytics = new Analytics(this.client);
|
|
1254
|
+
this.ecommerce = new Ecommerce(this.client, () => this._userId);
|
|
858
1255
|
this.referrals = new Referrals(this.client);
|
|
859
1256
|
this.deferred = new Deferred(this.client);
|
|
860
1257
|
this.banners = new Banners(this.client);
|
|
@@ -892,13 +1289,14 @@ var Tolinku = class {
|
|
|
892
1289
|
dismissMessage() {
|
|
893
1290
|
this.messages.dismiss();
|
|
894
1291
|
}
|
|
895
|
-
/** Flush any queued analytics events immediately */
|
|
1292
|
+
/** Flush any queued analytics and ecommerce events immediately */
|
|
896
1293
|
async flush() {
|
|
897
|
-
|
|
1294
|
+
await Promise.all([this.analytics.flush(), this.ecommerce.flush()]);
|
|
898
1295
|
}
|
|
899
1296
|
/** Clean up all DOM elements, flush events, and cancel in-flight requests (e.g. before unmounting in SPAs) */
|
|
900
1297
|
destroy() {
|
|
901
1298
|
this.analytics.destroy();
|
|
1299
|
+
this.ecommerce.destroy();
|
|
902
1300
|
this.client.abort();
|
|
903
1301
|
this.banners.dismiss();
|
|
904
1302
|
this.messages.dismiss();
|