@reblu/site-client 5.20.0 → 5.22.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/CHANGELOG.md +12 -0
- package/README.md +30 -1
- package/dist/index.cjs +226 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -3
- package/dist/index.d.ts +95 -3
- package/dist/index.js +220 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _reblu_site_contracts from '@reblu/site-contracts';
|
|
2
|
-
import { AcademyCourseListQuery, PaginationMeta, PageType, PagesListSort, ShopSort, AcademyCheckoutErrorCode, TrustBadgeType, ContactFormInput } from '@reblu/site-contracts';
|
|
2
|
+
import { AcademyCourseListQuery, PaginationMeta, PageType, PagesListSort, ShopSort, AcademyCheckoutErrorCode, TrustBadgeType, ContactFormInput, LeadAttribution, LeadTouch } from '@reblu/site-contracts';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Cache adapter — the seam that severs the SDK from any framework's caching
|
|
@@ -333,6 +333,11 @@ interface ReviewListParams {
|
|
|
333
333
|
/** At most this many reviews (1..100). Omit for all of them. */
|
|
334
334
|
limit?: number;
|
|
335
335
|
}
|
|
336
|
+
/** Options for {@link makeReviewsNamespace}'s `featured` (REB-1272). */
|
|
337
|
+
interface FeaturedReviewsParams {
|
|
338
|
+
/** At most this many featured reviews (1..100). Omit for all of them. */
|
|
339
|
+
limit?: number;
|
|
340
|
+
}
|
|
336
341
|
|
|
337
342
|
/**
|
|
338
343
|
* Stripe Checkout redirect-origin guard (REB-836, defence in depth).
|
|
@@ -392,6 +397,13 @@ interface ContactFormMetadataOptions {
|
|
|
392
397
|
location?: PageLocation | null;
|
|
393
398
|
/** Submission time. @default new Date() */
|
|
394
399
|
now?: Date;
|
|
400
|
+
/**
|
|
401
|
+
* REB-1366 — the lead's origin (first and last touch). Defaults to what the
|
|
402
|
+
* page's store holds (`getLeadAttributionStore()`, filled by
|
|
403
|
+
* `LeadAttributionCapture` from `@reblu/site-next/consent`); `null` sends
|
|
404
|
+
* none. Omitted from the body when there is nothing to send.
|
|
405
|
+
*/
|
|
406
|
+
attribution?: LeadAttribution | null;
|
|
395
407
|
}
|
|
396
408
|
/**
|
|
397
409
|
* Path of the page the visitor is on — `location.pathname`, never with a query
|
|
@@ -401,7 +413,8 @@ declare function currentPagePath(location?: PageLocation | null): string;
|
|
|
401
413
|
/**
|
|
402
414
|
* The `metadata` for `contactForm.submit()` / `POST /api/site/contact-form`:
|
|
403
415
|
* `fromPage` is the current page path, `currentUrl` the full href (base-api
|
|
404
|
-
* reads the UTMs from it), `timestamp` the submission time
|
|
416
|
+
* reads the UTMs from it), `timestamp` the submission time, and `attribution`
|
|
417
|
+
* the visit's first/last touch when the page recorded any (REB-1366).
|
|
405
418
|
*
|
|
406
419
|
* ```ts
|
|
407
420
|
* body: JSON.stringify({ ...fields, captcha, metadata: buildContactFormMetadata() })
|
|
@@ -409,6 +422,84 @@ declare function currentPagePath(location?: PageLocation | null): string;
|
|
|
409
422
|
*/
|
|
410
423
|
declare function buildContactFormMetadata(options?: ContactFormMetadataOptions): ContactFormMetadata;
|
|
411
424
|
|
|
425
|
+
/**
|
|
426
|
+
* REB-1366 — where a lead came from, captured in the browser.
|
|
427
|
+
*
|
|
428
|
+
* Each full page load yields at most one RAW touch (`leadTouchFromVisit`):
|
|
429
|
+
* the `utm_*` params, the NAMES of ad click ids (never their values), the
|
|
430
|
+
* external referrer's HOST (never the full URL) and the landing path. The
|
|
431
|
+
* store keeps the FIRST touch and the LAST non-direct one. base-api decides
|
|
432
|
+
* the channel (campaign, search, social, referral, direct, email) from these
|
|
433
|
+
* raw inputs, so this module never classifies — one classifier, server-side.
|
|
434
|
+
*
|
|
435
|
+
* Consent (owner, 25-Oct-2026): memory only until the visitor grants the
|
|
436
|
+
* `analytics` cookie category; after that, a first-party cookie kept 90 days.
|
|
437
|
+
* Denying or withdrawing removes the cookie; memory still serves the page the
|
|
438
|
+
* visitor is on. Nothing here holds personal data: values that look like an
|
|
439
|
+
* email are dropped and every value is capped.
|
|
440
|
+
*
|
|
441
|
+
* Dependency-free on purpose: it runs on every page of every SITE.
|
|
442
|
+
*/
|
|
443
|
+
/** The first-party cookie the touches are kept in after `analytics` consent. */
|
|
444
|
+
declare const LEAD_ATTRIBUTION_COOKIE_NAME = "reblu_attribution";
|
|
445
|
+
/** 90 days (owner decision, 25-Oct-2026). */
|
|
446
|
+
declare const LEAD_ATTRIBUTION_MAX_AGE_SECONDS: number;
|
|
447
|
+
/** Ad click-id params whose NAMES are sent (base-api's list). */
|
|
448
|
+
declare const LEAD_ATTRIBUTION_CLICK_IDS: readonly ["gclid", "gbraid", "wbraid", "dclid", "msclkid", "ttclid", "li_fat_id", "fbclid"];
|
|
449
|
+
/** One page load: the URL it opened and the referrer it came from. */
|
|
450
|
+
interface LeadVisit {
|
|
451
|
+
/** @default globalThis.location.href */
|
|
452
|
+
href?: string;
|
|
453
|
+
/** @default globalThis.document.referrer */
|
|
454
|
+
referrer?: string | null;
|
|
455
|
+
/** @default new Date() */
|
|
456
|
+
now?: Date;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* The raw touch of one page load, or `null` when the load is not a new visit:
|
|
460
|
+
* a referrer on the SITE's own host (navigation), or a return from a payment
|
|
461
|
+
* or login flow. A load with no referrer and no campaign is a DIRECT touch
|
|
462
|
+
* (landing path and time only).
|
|
463
|
+
*/
|
|
464
|
+
declare function leadTouchFromVisit(visit?: LeadVisit): LeadTouch | null;
|
|
465
|
+
/** Where the store keeps its cookie (`document.cookie` by default; a fake in tests). */
|
|
466
|
+
interface LeadAttributionCookieJar {
|
|
467
|
+
read(): string | null;
|
|
468
|
+
write(value: string, maxAgeSeconds: number): void;
|
|
469
|
+
remove(): void;
|
|
470
|
+
}
|
|
471
|
+
interface LeadAttributionStore {
|
|
472
|
+
/** Records the touch of the current page load (`leadTouchFromVisit`). */
|
|
473
|
+
recordVisit(visit?: LeadVisit): void;
|
|
474
|
+
/** Keeps `touch` as first (if none yet) and as last (if not direct). Null is ignored. */
|
|
475
|
+
record(touch: LeadTouch | null): void;
|
|
476
|
+
/**
|
|
477
|
+
* The visitor's `analytics` consent. `true` reads the stored touches, merges
|
|
478
|
+
* them with this page's and writes the cookie; `false` removes the cookie.
|
|
479
|
+
* Until called, the store is memory-only and never touches the cookie.
|
|
480
|
+
*/
|
|
481
|
+
setConsent(granted: boolean): void;
|
|
482
|
+
/** The touches to send with a lead, or `undefined` when there are none. */
|
|
483
|
+
get(): LeadAttribution | undefined;
|
|
484
|
+
}
|
|
485
|
+
interface LeadAttributionStoreOptions {
|
|
486
|
+
/** @default the `document.cookie` jar; `null` keeps the store memory-only. */
|
|
487
|
+
cookies?: LeadAttributionCookieJar | null;
|
|
488
|
+
/** @default () => new Date() */
|
|
489
|
+
now?: () => Date;
|
|
490
|
+
}
|
|
491
|
+
declare function createLeadAttributionStore(options?: LeadAttributionStoreOptions): LeadAttributionStore;
|
|
492
|
+
/**
|
|
493
|
+
* The page's one store, shared by the capture (`LeadAttributionCapture` from
|
|
494
|
+
* `@reblu/site-next/consent`) and `buildContactFormMetadata()`. Kept on
|
|
495
|
+
* `globalThis` so two bundled copies of this package still share it. On the
|
|
496
|
+
* server (no `window`) every call gets a new, empty store: one process serves
|
|
497
|
+
* every visitor, so nothing may be shared there.
|
|
498
|
+
*/
|
|
499
|
+
declare function getLeadAttributionStore(): LeadAttributionStore;
|
|
500
|
+
/** Drops the shared store (tests; a fresh one is created on the next call). */
|
|
501
|
+
declare function resetLeadAttributionStore(): void;
|
|
502
|
+
|
|
412
503
|
interface RebluClientConfig {
|
|
413
504
|
/** Absolute origin of the Reblu SITE API, e.g. `https://api.reblu.app`. */
|
|
414
505
|
baseUrl: string;
|
|
@@ -512,9 +603,10 @@ declare function createRebluClient(config: RebluClientConfig): {
|
|
|
512
603
|
};
|
|
513
604
|
reviews: {
|
|
514
605
|
list: (params?: ReviewListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.Review>>;
|
|
606
|
+
featured: (params?: FeaturedReviewsParams, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.FeaturedReviews>;
|
|
515
607
|
};
|
|
516
608
|
};
|
|
517
609
|
/** The assembled client returned by {@link createRebluClient}. */
|
|
518
610
|
type RebluClient = ReturnType<typeof createRebluClient>;
|
|
519
611
|
|
|
520
|
-
export { AcademyCheckoutError, type AcademyCheckoutErrorDetails, type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, AmbiguousPageSlugError, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ContactFormMetadata, type ContactFormMetadataOptions, DEFAULT_CHECKOUT_REDIRECT_HOSTS, type FeaturedPagesOptions, type ListResult, type PageBySlugOptions, type PageLocation, type PageSlugCandidate, type PagesListParams, PlainCacheAdapter, RETRY_AFTER_CAP_MS, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type RebluMutationOptions, type RequestScope, type ReviewListParams, type ShopProductsParams, type TrustBadgeListParams, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createRebluClient, currentPagePath, isSafeCheckoutRedirectUrl, visitorIpFromHeaders };
|
|
612
|
+
export { AcademyCheckoutError, type AcademyCheckoutErrorDetails, type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, AmbiguousPageSlugError, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ContactFormMetadata, type ContactFormMetadataOptions, DEFAULT_CHECKOUT_REDIRECT_HOSTS, type FeaturedPagesOptions, type FeaturedReviewsParams, LEAD_ATTRIBUTION_CLICK_IDS, LEAD_ATTRIBUTION_COOKIE_NAME, LEAD_ATTRIBUTION_MAX_AGE_SECONDS, type LeadAttributionCookieJar, type LeadAttributionStore, type LeadAttributionStoreOptions, type LeadVisit, type ListResult, type PageBySlugOptions, type PageLocation, type PageSlugCandidate, type PagesListParams, PlainCacheAdapter, RETRY_AFTER_CAP_MS, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type RebluMutationOptions, type RequestScope, type ReviewListParams, type ShopProductsParams, type TrustBadgeListParams, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createLeadAttributionStore, createRebluClient, currentPagePath, getLeadAttributionStore, isSafeCheckoutRedirectUrl, leadTouchFromVisit, resetLeadAttributionStore, visitorIpFromHeaders };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _reblu_site_contracts from '@reblu/site-contracts';
|
|
2
|
-
import { AcademyCourseListQuery, PaginationMeta, PageType, PagesListSort, ShopSort, AcademyCheckoutErrorCode, TrustBadgeType, ContactFormInput } from '@reblu/site-contracts';
|
|
2
|
+
import { AcademyCourseListQuery, PaginationMeta, PageType, PagesListSort, ShopSort, AcademyCheckoutErrorCode, TrustBadgeType, ContactFormInput, LeadAttribution, LeadTouch } from '@reblu/site-contracts';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Cache adapter — the seam that severs the SDK from any framework's caching
|
|
@@ -333,6 +333,11 @@ interface ReviewListParams {
|
|
|
333
333
|
/** At most this many reviews (1..100). Omit for all of them. */
|
|
334
334
|
limit?: number;
|
|
335
335
|
}
|
|
336
|
+
/** Options for {@link makeReviewsNamespace}'s `featured` (REB-1272). */
|
|
337
|
+
interface FeaturedReviewsParams {
|
|
338
|
+
/** At most this many featured reviews (1..100). Omit for all of them. */
|
|
339
|
+
limit?: number;
|
|
340
|
+
}
|
|
336
341
|
|
|
337
342
|
/**
|
|
338
343
|
* Stripe Checkout redirect-origin guard (REB-836, defence in depth).
|
|
@@ -392,6 +397,13 @@ interface ContactFormMetadataOptions {
|
|
|
392
397
|
location?: PageLocation | null;
|
|
393
398
|
/** Submission time. @default new Date() */
|
|
394
399
|
now?: Date;
|
|
400
|
+
/**
|
|
401
|
+
* REB-1366 — the lead's origin (first and last touch). Defaults to what the
|
|
402
|
+
* page's store holds (`getLeadAttributionStore()`, filled by
|
|
403
|
+
* `LeadAttributionCapture` from `@reblu/site-next/consent`); `null` sends
|
|
404
|
+
* none. Omitted from the body when there is nothing to send.
|
|
405
|
+
*/
|
|
406
|
+
attribution?: LeadAttribution | null;
|
|
395
407
|
}
|
|
396
408
|
/**
|
|
397
409
|
* Path of the page the visitor is on — `location.pathname`, never with a query
|
|
@@ -401,7 +413,8 @@ declare function currentPagePath(location?: PageLocation | null): string;
|
|
|
401
413
|
/**
|
|
402
414
|
* The `metadata` for `contactForm.submit()` / `POST /api/site/contact-form`:
|
|
403
415
|
* `fromPage` is the current page path, `currentUrl` the full href (base-api
|
|
404
|
-
* reads the UTMs from it), `timestamp` the submission time
|
|
416
|
+
* reads the UTMs from it), `timestamp` the submission time, and `attribution`
|
|
417
|
+
* the visit's first/last touch when the page recorded any (REB-1366).
|
|
405
418
|
*
|
|
406
419
|
* ```ts
|
|
407
420
|
* body: JSON.stringify({ ...fields, captcha, metadata: buildContactFormMetadata() })
|
|
@@ -409,6 +422,84 @@ declare function currentPagePath(location?: PageLocation | null): string;
|
|
|
409
422
|
*/
|
|
410
423
|
declare function buildContactFormMetadata(options?: ContactFormMetadataOptions): ContactFormMetadata;
|
|
411
424
|
|
|
425
|
+
/**
|
|
426
|
+
* REB-1366 — where a lead came from, captured in the browser.
|
|
427
|
+
*
|
|
428
|
+
* Each full page load yields at most one RAW touch (`leadTouchFromVisit`):
|
|
429
|
+
* the `utm_*` params, the NAMES of ad click ids (never their values), the
|
|
430
|
+
* external referrer's HOST (never the full URL) and the landing path. The
|
|
431
|
+
* store keeps the FIRST touch and the LAST non-direct one. base-api decides
|
|
432
|
+
* the channel (campaign, search, social, referral, direct, email) from these
|
|
433
|
+
* raw inputs, so this module never classifies — one classifier, server-side.
|
|
434
|
+
*
|
|
435
|
+
* Consent (owner, 25-Oct-2026): memory only until the visitor grants the
|
|
436
|
+
* `analytics` cookie category; after that, a first-party cookie kept 90 days.
|
|
437
|
+
* Denying or withdrawing removes the cookie; memory still serves the page the
|
|
438
|
+
* visitor is on. Nothing here holds personal data: values that look like an
|
|
439
|
+
* email are dropped and every value is capped.
|
|
440
|
+
*
|
|
441
|
+
* Dependency-free on purpose: it runs on every page of every SITE.
|
|
442
|
+
*/
|
|
443
|
+
/** The first-party cookie the touches are kept in after `analytics` consent. */
|
|
444
|
+
declare const LEAD_ATTRIBUTION_COOKIE_NAME = "reblu_attribution";
|
|
445
|
+
/** 90 days (owner decision, 25-Oct-2026). */
|
|
446
|
+
declare const LEAD_ATTRIBUTION_MAX_AGE_SECONDS: number;
|
|
447
|
+
/** Ad click-id params whose NAMES are sent (base-api's list). */
|
|
448
|
+
declare const LEAD_ATTRIBUTION_CLICK_IDS: readonly ["gclid", "gbraid", "wbraid", "dclid", "msclkid", "ttclid", "li_fat_id", "fbclid"];
|
|
449
|
+
/** One page load: the URL it opened and the referrer it came from. */
|
|
450
|
+
interface LeadVisit {
|
|
451
|
+
/** @default globalThis.location.href */
|
|
452
|
+
href?: string;
|
|
453
|
+
/** @default globalThis.document.referrer */
|
|
454
|
+
referrer?: string | null;
|
|
455
|
+
/** @default new Date() */
|
|
456
|
+
now?: Date;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* The raw touch of one page load, or `null` when the load is not a new visit:
|
|
460
|
+
* a referrer on the SITE's own host (navigation), or a return from a payment
|
|
461
|
+
* or login flow. A load with no referrer and no campaign is a DIRECT touch
|
|
462
|
+
* (landing path and time only).
|
|
463
|
+
*/
|
|
464
|
+
declare function leadTouchFromVisit(visit?: LeadVisit): LeadTouch | null;
|
|
465
|
+
/** Where the store keeps its cookie (`document.cookie` by default; a fake in tests). */
|
|
466
|
+
interface LeadAttributionCookieJar {
|
|
467
|
+
read(): string | null;
|
|
468
|
+
write(value: string, maxAgeSeconds: number): void;
|
|
469
|
+
remove(): void;
|
|
470
|
+
}
|
|
471
|
+
interface LeadAttributionStore {
|
|
472
|
+
/** Records the touch of the current page load (`leadTouchFromVisit`). */
|
|
473
|
+
recordVisit(visit?: LeadVisit): void;
|
|
474
|
+
/** Keeps `touch` as first (if none yet) and as last (if not direct). Null is ignored. */
|
|
475
|
+
record(touch: LeadTouch | null): void;
|
|
476
|
+
/**
|
|
477
|
+
* The visitor's `analytics` consent. `true` reads the stored touches, merges
|
|
478
|
+
* them with this page's and writes the cookie; `false` removes the cookie.
|
|
479
|
+
* Until called, the store is memory-only and never touches the cookie.
|
|
480
|
+
*/
|
|
481
|
+
setConsent(granted: boolean): void;
|
|
482
|
+
/** The touches to send with a lead, or `undefined` when there are none. */
|
|
483
|
+
get(): LeadAttribution | undefined;
|
|
484
|
+
}
|
|
485
|
+
interface LeadAttributionStoreOptions {
|
|
486
|
+
/** @default the `document.cookie` jar; `null` keeps the store memory-only. */
|
|
487
|
+
cookies?: LeadAttributionCookieJar | null;
|
|
488
|
+
/** @default () => new Date() */
|
|
489
|
+
now?: () => Date;
|
|
490
|
+
}
|
|
491
|
+
declare function createLeadAttributionStore(options?: LeadAttributionStoreOptions): LeadAttributionStore;
|
|
492
|
+
/**
|
|
493
|
+
* The page's one store, shared by the capture (`LeadAttributionCapture` from
|
|
494
|
+
* `@reblu/site-next/consent`) and `buildContactFormMetadata()`. Kept on
|
|
495
|
+
* `globalThis` so two bundled copies of this package still share it. On the
|
|
496
|
+
* server (no `window`) every call gets a new, empty store: one process serves
|
|
497
|
+
* every visitor, so nothing may be shared there.
|
|
498
|
+
*/
|
|
499
|
+
declare function getLeadAttributionStore(): LeadAttributionStore;
|
|
500
|
+
/** Drops the shared store (tests; a fresh one is created on the next call). */
|
|
501
|
+
declare function resetLeadAttributionStore(): void;
|
|
502
|
+
|
|
412
503
|
interface RebluClientConfig {
|
|
413
504
|
/** Absolute origin of the Reblu SITE API, e.g. `https://api.reblu.app`. */
|
|
414
505
|
baseUrl: string;
|
|
@@ -512,9 +603,10 @@ declare function createRebluClient(config: RebluClientConfig): {
|
|
|
512
603
|
};
|
|
513
604
|
reviews: {
|
|
514
605
|
list: (params?: ReviewListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.Review>>;
|
|
606
|
+
featured: (params?: FeaturedReviewsParams, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.FeaturedReviews>;
|
|
515
607
|
};
|
|
516
608
|
};
|
|
517
609
|
/** The assembled client returned by {@link createRebluClient}. */
|
|
518
610
|
type RebluClient = ReturnType<typeof createRebluClient>;
|
|
519
611
|
|
|
520
|
-
export { AcademyCheckoutError, type AcademyCheckoutErrorDetails, type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, AmbiguousPageSlugError, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ContactFormMetadata, type ContactFormMetadataOptions, DEFAULT_CHECKOUT_REDIRECT_HOSTS, type FeaturedPagesOptions, type ListResult, type PageBySlugOptions, type PageLocation, type PageSlugCandidate, type PagesListParams, PlainCacheAdapter, RETRY_AFTER_CAP_MS, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type RebluMutationOptions, type RequestScope, type ReviewListParams, type ShopProductsParams, type TrustBadgeListParams, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createRebluClient, currentPagePath, isSafeCheckoutRedirectUrl, visitorIpFromHeaders };
|
|
612
|
+
export { AcademyCheckoutError, type AcademyCheckoutErrorDetails, type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, AmbiguousPageSlugError, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ContactFormMetadata, type ContactFormMetadataOptions, DEFAULT_CHECKOUT_REDIRECT_HOSTS, type FeaturedPagesOptions, type FeaturedReviewsParams, LEAD_ATTRIBUTION_CLICK_IDS, LEAD_ATTRIBUTION_COOKIE_NAME, LEAD_ATTRIBUTION_MAX_AGE_SECONDS, type LeadAttributionCookieJar, type LeadAttributionStore, type LeadAttributionStoreOptions, type LeadVisit, type ListResult, type PageBySlugOptions, type PageLocation, type PageSlugCandidate, type PagesListParams, PlainCacheAdapter, RETRY_AFTER_CAP_MS, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type RebluMutationOptions, type RequestScope, type ReviewListParams, type ShopProductsParams, type TrustBadgeListParams, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createLeadAttributionStore, createRebluClient, currentPagePath, getLeadAttributionStore, isSafeCheckoutRedirectUrl, leadTouchFromVisit, resetLeadAttributionStore, visitorIpFromHeaders };
|
package/dist/index.js
CHANGED
|
@@ -679,7 +679,14 @@ function makeReviewsNamespace(ctx) {
|
|
|
679
679
|
{ cachePolicy: cache }
|
|
680
680
|
);
|
|
681
681
|
}
|
|
682
|
-
|
|
682
|
+
function featured(params = {}, cache) {
|
|
683
|
+
return requestOne(
|
|
684
|
+
ctx,
|
|
685
|
+
buildUrl("/api/site/reviews/featured", { limit: params.limit }),
|
|
686
|
+
{ cachePolicy: cache }
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
return { list, featured };
|
|
683
690
|
}
|
|
684
691
|
|
|
685
692
|
// src/domains/trust-badges.ts
|
|
@@ -715,6 +722,214 @@ function makeWithdrawalRequestsNamespace(ctx) {
|
|
|
715
722
|
};
|
|
716
723
|
}
|
|
717
724
|
|
|
725
|
+
// src/lead-attribution.ts
|
|
726
|
+
var LEAD_ATTRIBUTION_COOKIE_NAME = "reblu_attribution";
|
|
727
|
+
var LEAD_ATTRIBUTION_MAX_AGE_SECONDS = 90 * 24 * 60 * 60;
|
|
728
|
+
var LEAD_ATTRIBUTION_CLICK_IDS = [
|
|
729
|
+
"gclid",
|
|
730
|
+
"gbraid",
|
|
731
|
+
"wbraid",
|
|
732
|
+
"dclid",
|
|
733
|
+
"msclkid",
|
|
734
|
+
"ttclid",
|
|
735
|
+
"li_fat_id",
|
|
736
|
+
"fbclid"
|
|
737
|
+
];
|
|
738
|
+
var UTM_KEYS = ["source", "medium", "campaign", "content", "term"];
|
|
739
|
+
var VALUE_MAX_LENGTH = 100;
|
|
740
|
+
var PATH_MAX_LENGTH = 200;
|
|
741
|
+
var SHAPE_VERSION = 1;
|
|
742
|
+
var CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
|
|
743
|
+
var HOST = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/;
|
|
744
|
+
var IGNORED_HOSTS = [
|
|
745
|
+
"checkout.stripe.com",
|
|
746
|
+
"billing.stripe.com",
|
|
747
|
+
"paypal.com",
|
|
748
|
+
"accounts.google.com"
|
|
749
|
+
];
|
|
750
|
+
function cleanValue(value, max = VALUE_MAX_LENGTH) {
|
|
751
|
+
if (typeof value !== "string" || CONTROL_CHARS.test(value)) return void 0;
|
|
752
|
+
const clean = value.trim().slice(0, max);
|
|
753
|
+
return clean && !clean.includes("@") ? clean : void 0;
|
|
754
|
+
}
|
|
755
|
+
function cleanHost(value) {
|
|
756
|
+
const host = cleanValue(value)?.toLowerCase();
|
|
757
|
+
return host && HOST.test(host) ? host : void 0;
|
|
758
|
+
}
|
|
759
|
+
function cleanPath(value) {
|
|
760
|
+
const path = cleanValue(value, PATH_MAX_LENGTH)?.split(/[?#]/, 1)[0];
|
|
761
|
+
return path && path.startsWith("/") && !path.startsWith("//") ? path : void 0;
|
|
762
|
+
}
|
|
763
|
+
function cleanAt(value) {
|
|
764
|
+
return typeof value === "string" && !Number.isNaN(Date.parse(value)) ? value : void 0;
|
|
765
|
+
}
|
|
766
|
+
var withoutWww = (host) => host.replace(/^www\./, "");
|
|
767
|
+
var matchesDomain = (host, domain) => host === domain || host.endsWith(`.${domain}`);
|
|
768
|
+
function sanitizeTouch(raw) {
|
|
769
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
770
|
+
const input = raw;
|
|
771
|
+
const touch = {};
|
|
772
|
+
const rawUtm = input.utm;
|
|
773
|
+
if (typeof rawUtm === "object" && rawUtm !== null) {
|
|
774
|
+
const utm = {};
|
|
775
|
+
for (const key of UTM_KEYS) {
|
|
776
|
+
const value = cleanValue(rawUtm[key]);
|
|
777
|
+
if (value) utm[key] = value;
|
|
778
|
+
}
|
|
779
|
+
if (Object.keys(utm).length > 0) touch.utm = utm;
|
|
780
|
+
}
|
|
781
|
+
const rawClickIds = input.clickIds;
|
|
782
|
+
if (Array.isArray(rawClickIds)) {
|
|
783
|
+
const known = LEAD_ATTRIBUTION_CLICK_IDS.filter((id) => rawClickIds.includes(id));
|
|
784
|
+
if (known.length > 0) touch.clickIds = known;
|
|
785
|
+
}
|
|
786
|
+
const referrer = cleanHost(input.referrer);
|
|
787
|
+
if (referrer) touch.referrer = referrer;
|
|
788
|
+
const landingPath = cleanPath(input.landingPath);
|
|
789
|
+
if (landingPath) touch.landingPath = landingPath;
|
|
790
|
+
const at = cleanAt(input.at);
|
|
791
|
+
if (at) touch.at = at;
|
|
792
|
+
return Object.keys(touch).length > 0 ? touch : void 0;
|
|
793
|
+
}
|
|
794
|
+
function isDirect(touch) {
|
|
795
|
+
return !touch.utm && !touch.clickIds && !touch.referrer;
|
|
796
|
+
}
|
|
797
|
+
function leadTouchFromVisit(visit = {}) {
|
|
798
|
+
const g = globalThis;
|
|
799
|
+
const href = visit.href ?? g.location?.href;
|
|
800
|
+
const referrer = visit.referrer === void 0 ? g.document?.referrer : visit.referrer;
|
|
801
|
+
let url;
|
|
802
|
+
try {
|
|
803
|
+
url = new URL(href ?? "");
|
|
804
|
+
} catch {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
const raw = {};
|
|
808
|
+
const utm = {};
|
|
809
|
+
for (const key of UTM_KEYS) {
|
|
810
|
+
const value = url.searchParams.get(`utm_${key}`);
|
|
811
|
+
if (value) utm[key] = value;
|
|
812
|
+
}
|
|
813
|
+
if (Object.keys(utm).length > 0) raw.utm = utm;
|
|
814
|
+
const clickIds = LEAD_ATTRIBUTION_CLICK_IDS.filter((id) => url.searchParams.has(id));
|
|
815
|
+
if (clickIds.length > 0) raw.clickIds = clickIds;
|
|
816
|
+
let referrerHost;
|
|
817
|
+
try {
|
|
818
|
+
referrerHost = referrer ? new URL(referrer).hostname.toLowerCase() : void 0;
|
|
819
|
+
} catch {
|
|
820
|
+
}
|
|
821
|
+
const ownHost = withoutWww(url.hostname.toLowerCase());
|
|
822
|
+
const external = referrerHost && withoutWww(referrerHost) !== ownHost && !IGNORED_HOSTS.some((domain) => matchesDomain(referrerHost, domain));
|
|
823
|
+
if (external) raw.referrer = referrerHost;
|
|
824
|
+
if (referrerHost && !external && !raw.utm && !raw.clickIds) return null;
|
|
825
|
+
raw.landingPath = url.pathname;
|
|
826
|
+
raw.at = (visit.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
827
|
+
return sanitizeTouch(raw) ?? null;
|
|
828
|
+
}
|
|
829
|
+
function documentCookieJar() {
|
|
830
|
+
const doc = globalThis.document;
|
|
831
|
+
if (!doc) return null;
|
|
832
|
+
const name = LEAD_ATTRIBUTION_COOKIE_NAME;
|
|
833
|
+
return {
|
|
834
|
+
read() {
|
|
835
|
+
const match = doc.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
|
|
836
|
+
if (!match) return null;
|
|
837
|
+
try {
|
|
838
|
+
return decodeURIComponent(match[1]);
|
|
839
|
+
} catch {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
write(value, maxAgeSeconds) {
|
|
844
|
+
const https = globalThis.location?.protocol === "https:";
|
|
845
|
+
doc.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAgeSeconds}; SameSite=Lax${https ? "; Secure" : ""}`;
|
|
846
|
+
},
|
|
847
|
+
remove() {
|
|
848
|
+
doc.cookie = `${name}=; path=/; max-age=0; SameSite=Lax`;
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
var atOf = (touch) => touch?.at ? Date.parse(touch.at) : Number.NaN;
|
|
853
|
+
function createLeadAttributionStore(options = {}) {
|
|
854
|
+
const cookies = options.cookies === void 0 ? documentCookieJar() : options.cookies;
|
|
855
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
856
|
+
let first;
|
|
857
|
+
let last;
|
|
858
|
+
let granted = false;
|
|
859
|
+
const fresh = (touch) => {
|
|
860
|
+
const at = atOf(touch);
|
|
861
|
+
return touch && now().getTime() - at < LEAD_ATTRIBUTION_MAX_AGE_SECONDS * 1e3 ? touch : void 0;
|
|
862
|
+
};
|
|
863
|
+
const persist = () => {
|
|
864
|
+
if (!granted || !cookies) return;
|
|
865
|
+
const state = { v: SHAPE_VERSION, first: fresh(first), last: fresh(last) };
|
|
866
|
+
try {
|
|
867
|
+
if (state.first || state.last) {
|
|
868
|
+
cookies.write(JSON.stringify(state), LEAD_ATTRIBUTION_MAX_AGE_SECONDS);
|
|
869
|
+
} else {
|
|
870
|
+
cookies.remove();
|
|
871
|
+
}
|
|
872
|
+
} catch {
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
const loadStored = () => {
|
|
876
|
+
let stored;
|
|
877
|
+
try {
|
|
878
|
+
const raw = cookies?.read();
|
|
879
|
+
const parsed = raw ? JSON.parse(raw) : void 0;
|
|
880
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
881
|
+
stored = parsed;
|
|
882
|
+
}
|
|
883
|
+
} catch {
|
|
884
|
+
}
|
|
885
|
+
const storedFirst = fresh(sanitizeTouch(stored?.first));
|
|
886
|
+
const storedLast = fresh(sanitizeTouch(stored?.last));
|
|
887
|
+
if (storedFirst && !(atOf(first) < atOf(storedFirst))) first = storedFirst;
|
|
888
|
+
if (storedLast && !(atOf(last) > atOf(storedLast))) last = storedLast;
|
|
889
|
+
};
|
|
890
|
+
const store = {
|
|
891
|
+
recordVisit(visit) {
|
|
892
|
+
store.record(leadTouchFromVisit(visit));
|
|
893
|
+
},
|
|
894
|
+
record(touch) {
|
|
895
|
+
if (!touch) return;
|
|
896
|
+
if (!fresh(first)) first = touch;
|
|
897
|
+
if (!isDirect(touch)) last = touch;
|
|
898
|
+
persist();
|
|
899
|
+
},
|
|
900
|
+
setConsent(next) {
|
|
901
|
+
granted = next;
|
|
902
|
+
if (!cookies) return;
|
|
903
|
+
if (granted) {
|
|
904
|
+
loadStored();
|
|
905
|
+
persist();
|
|
906
|
+
} else {
|
|
907
|
+
try {
|
|
908
|
+
cookies.remove();
|
|
909
|
+
} catch {
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
get() {
|
|
914
|
+
const f = fresh(first);
|
|
915
|
+
const l = fresh(last);
|
|
916
|
+
if (!f && !l) return void 0;
|
|
917
|
+
return { ...f && { first: f }, ...l && { last: l } };
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
return store;
|
|
921
|
+
}
|
|
922
|
+
var GLOBAL_KEY = "__rebluLeadAttributionStore";
|
|
923
|
+
function getLeadAttributionStore() {
|
|
924
|
+
const g = globalThis;
|
|
925
|
+
if (typeof g.window === "undefined") return createLeadAttributionStore({ cookies: null });
|
|
926
|
+
g[GLOBAL_KEY] ??= createLeadAttributionStore();
|
|
927
|
+
return g[GLOBAL_KEY];
|
|
928
|
+
}
|
|
929
|
+
function resetLeadAttributionStore() {
|
|
930
|
+
delete globalThis[GLOBAL_KEY];
|
|
931
|
+
}
|
|
932
|
+
|
|
718
933
|
// src/contact-form-metadata.ts
|
|
719
934
|
function browserLocation() {
|
|
720
935
|
const location = globalThis.location;
|
|
@@ -726,11 +941,13 @@ function currentPagePath(location = browserLocation()) {
|
|
|
726
941
|
}
|
|
727
942
|
function buildContactFormMetadata(options = {}) {
|
|
728
943
|
const location = options.location === void 0 ? browserLocation() : options.location;
|
|
944
|
+
const attribution = options.attribution === void 0 ? getLeadAttributionStore().get() : options.attribution;
|
|
729
945
|
return {
|
|
730
946
|
fromPage: options.fromPage ?? currentPagePath(location),
|
|
731
947
|
timestamp: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
732
948
|
currentUrl: location?.href ?? "",
|
|
733
|
-
leadSource: options.leadSource ?? { platform: "organic" }
|
|
949
|
+
leadSource: options.leadSource ?? { platform: "organic" },
|
|
950
|
+
...attribution && { attribution }
|
|
734
951
|
};
|
|
735
952
|
}
|
|
736
953
|
|
|
@@ -768,6 +985,6 @@ function createRebluClient(config) {
|
|
|
768
985
|
};
|
|
769
986
|
}
|
|
770
987
|
|
|
771
|
-
export { AcademyCheckoutError, AmbiguousPageSlugError, ApiError, DEFAULT_CHECKOUT_REDIRECT_HOSTS, PlainCacheAdapter, RETRY_AFTER_CAP_MS, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createRebluClient, currentPagePath, isSafeCheckoutRedirectUrl, visitorIpFromHeaders };
|
|
988
|
+
export { AcademyCheckoutError, AmbiguousPageSlugError, ApiError, DEFAULT_CHECKOUT_REDIRECT_HOSTS, LEAD_ATTRIBUTION_CLICK_IDS, LEAD_ATTRIBUTION_COOKIE_NAME, LEAD_ATTRIBUTION_MAX_AGE_SECONDS, PlainCacheAdapter, RETRY_AFTER_CAP_MS, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createLeadAttributionStore, createRebluClient, currentPagePath, getLeadAttributionStore, isSafeCheckoutRedirectUrl, leadTouchFromVisit, resetLeadAttributionStore, visitorIpFromHeaders };
|
|
772
989
|
//# sourceMappingURL=index.js.map
|
|
773
990
|
//# sourceMappingURL=index.js.map
|