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