@wapitee/typhoonx-react 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wapitee Interactive
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # TyphoonX for React
2
+
3
+ [![npm package](https://img.shields.io/npm/v/@wapitee/typhoonx-react?style=flat-square)](https://www.npmjs.com/package/@wapitee/typhoonx-react)
4
+ [![MIT License](https://img.shields.io/badge/License-MIT-red.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
+
6
+ React context and hook for sending TyphoonX storefront events by hand.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install --save @wapitee/typhoonx-react
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ Wrap the storefront in `<TyphoonXProvider>`:
17
+
18
+ ```tsx
19
+ import {TyphoonXProvider} from '@wapitee/typhoonx-react';
20
+
21
+ export function App() {
22
+ return (
23
+ <TyphoonXProvider
24
+ consent={hasAnalyticsConsent}
25
+ merchantId="TPX-XXXXXX"
26
+ shopId="123456789"
27
+ >
28
+ {/* storefront */}
29
+ </TyphoonXProvider>
30
+ );
31
+ }
32
+ ```
33
+
34
+ Send events from any component inside the provider with `useTyphoonX()`. Calling it outside `<TyphoonXProvider>` throws.
35
+
36
+ ```tsx
37
+ import {useTyphoonX} from '@wapitee/typhoonx-react';
38
+ import {useEffect} from 'react';
39
+
40
+ function ProductPage({product}: {product: Product}) {
41
+ const typhoonx = useTyphoonX();
42
+
43
+ useEffect(() => {
44
+ typhoonx.pageView();
45
+ typhoonx.viewItem({
46
+ currency: 'USD',
47
+ items: [
48
+ {
49
+ itemBrand: product.vendor,
50
+ itemId: product.id,
51
+ itemName: product.title,
52
+ price: product.price,
53
+ quantity: 1,
54
+ },
55
+ ],
56
+ value: product.price,
57
+ });
58
+ }, [typhoonx, product]);
59
+
60
+ return <AddToCartButton product={product} />;
61
+ }
62
+ ```
63
+
64
+ If your site sets a Content-Security-Policy, add `https://spell.typhoonx.io` to `connect-src` so event collection is not blocked.
65
+
66
+ ### Props
67
+
68
+ | Prop | Type | Required | Description |
69
+ | -------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `merchantId` | `string` | yes | TyphoonX merchant ID (`TPX-…`) |
71
+ | `shopId` | `string` | yes | Shop ID |
72
+ | `consent` | `boolean` | yes | Whether the visitor allows data collection. While `false`, events are dropped and nothing is written to cookies or `localStorage` |
73
+ | `cookieDomain` | `string` | no | Cookie `Domain` for a first-party client id. Defaults to the current hostname's apex domain (e.g. `.example.com`) |
74
+
75
+ ### Events
76
+
77
+ | Method | TyphoonX Event |
78
+ | ----------------------------------------------------- | ------------------ |
79
+ | `pageView()` | `page_view` |
80
+ | `viewItem({currency?, items, value})` | `view_item` |
81
+ | `viewItemList({currency?, itemListId, itemListName})` | `view_item_list` |
82
+ | `addToCart({currency?, items, value})` | `add_to_cart` |
83
+ | `removeFromCart({currency?, items, value})` | `remove_from_cart` |
84
+ | `viewCart({currency?, items, value})` | `view_cart` |
85
+ | `search({searchTerm})` | `search` |
86
+
87
+ Each item is `{itemBrand, itemId, itemName, price, quantity}`.
88
+
89
+ Identical calls on the same page within one task, such as effects re-run by React StrictMode, are sent once.
90
+
91
+ ## License
92
+
93
+ [MIT](./LICENSE) © Wapitee Interactive
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { TyphoonXConfig, TyphoonXTracker } from './tracker.js';
3
+ export interface TyphoonXProviderProps extends TyphoonXConfig {
4
+ children?: ReactNode;
5
+ }
6
+ export declare function TyphoonXProvider({ children, consent, cookieDomain, merchantId, shopId, }: TyphoonXProviderProps): import("react").JSX.Element;
7
+ export declare function useTyphoonX(): TyphoonXTracker;
8
+ //# sourceMappingURL=TyphoonXProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TyphoonXProvider.d.ts","sourceRoot":"","sources":["../src/TyphoonXProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAIrC,OAAO,KAAK,EAAc,cAAc,EAAE,eAAe,EAAC,MAAM,cAAc,CAAC;AAG/E,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAID,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,UAAU,EACV,MAAM,GACP,EAAE,qBAAqB,+BAyBvB;AAED,wBAAgB,WAAW,IAAI,eAAe,CAU7C"}
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext, useMemo, useRef } from 'react';
4
+ import { recordFirstVisit } from './first-visit.js';
5
+ import { createTracker } from './tracker.js';
6
+ const TyphoonXContext = createContext(null);
7
+ export function TyphoonXProvider({ children, consent, cookieDomain, merchantId, shopId, }) {
8
+ const history = useRef({ previousUrl: null, url: null });
9
+ const tracker = useMemo(() => {
10
+ // Runs during render, before any child effect can create the client id.
11
+ if (consent && typeof window !== 'undefined') {
12
+ recordFirstVisit();
13
+ }
14
+ return createTracker({
15
+ consent,
16
+ merchantId,
17
+ shopId,
18
+ ...(cookieDomain === undefined ? {} : { cookieDomain }),
19
+ }, history.current);
20
+ }, [consent, cookieDomain, merchantId, shopId]);
21
+ return (_jsx(TyphoonXContext.Provider, { value: tracker, children: children }));
22
+ }
23
+ export function useTyphoonX() {
24
+ const tracker = useContext(TyphoonXContext);
25
+ if (tracker === null) {
26
+ throw new Error('[TyphoonX] useTyphoonX() must be called inside <TyphoonXProvider>.');
27
+ }
28
+ return tracker;
29
+ }
@@ -0,0 +1,5 @@
1
+ /** Whether `__typhoon_client_id` is already set. */
2
+ export declare function hasClientId(): boolean;
3
+ /** Existing `__typhoon_client_id`, or a new UUID cookie on the apex domain. */
4
+ export declare function getOrCreateClientId(cookieDomain?: string): string;
5
+ //# sourceMappingURL=client-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-id.d.ts","sourceRoot":"","sources":["../src/client-id.ts"],"names":[],"mappings":"AAwBA,oDAAoD;AACpD,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAWjE"}
@@ -0,0 +1,41 @@
1
+ import CookieLib from 'universal-cookie';
2
+ const COOKIE = '__typhoon_client_id';
3
+ const MAX_AGE = 31_536_000; // 365 days
4
+ // Runtime default export is the class.
5
+ const Cookies = CookieLib;
6
+ const cookies = new Cookies(null, {
7
+ path: '/',
8
+ sameSite: 'lax',
9
+ maxAge: MAX_AGE,
10
+ });
11
+ /** Whether `__typhoon_client_id` is already set. */
12
+ export function hasClientId() {
13
+ return cookies.get(COOKIE, { doNotParse: true }) !== undefined;
14
+ }
15
+ /** Existing `__typhoon_client_id`, or a new UUID cookie on the apex domain. */
16
+ export function getOrCreateClientId(cookieDomain) {
17
+ const existing = cookies.get(COOKIE, { doNotParse: true });
18
+ if (existing) {
19
+ return existing;
20
+ }
21
+ const id = crypto.randomUUID();
22
+ const domain = cookieDomain ?? apexCookieDomain(window.location.hostname);
23
+ const secure = window.location.protocol === 'https:';
24
+ cookies.set(COOKIE, id, domain ? { domain, secure } : { secure });
25
+ return id;
26
+ }
27
+ function apexCookieDomain(hostname) {
28
+ const labels = hostname.split('.');
29
+ const secure = window.location.protocol === 'https:';
30
+ for (let n = 2; n <= labels.length; n += 1) {
31
+ const domain = `.${labels.slice(-n).join('.')}`;
32
+ const token = crypto.randomUUID();
33
+ cookies.set('__typhoon_d', token, { domain, maxAge: 60, secure });
34
+ const probe = cookies.get('__typhoon_d', { doNotParse: true });
35
+ cookies.remove('__typhoon_d', { domain, secure });
36
+ if (probe === token) {
37
+ return domain;
38
+ }
39
+ }
40
+ return undefined;
41
+ }
@@ -0,0 +1,9 @@
1
+ export interface FirstVisit {
2
+ time: string;
3
+ url: string;
4
+ }
5
+ /** Stores a new visitor's first visit time and URL, keeping any earlier record. */
6
+ export declare function recordFirstVisit(): void;
7
+ /** Reads and clears the stored first visit, if any. */
8
+ export declare function takeFirstVisit(): FirstVisit | undefined;
9
+ //# sourceMappingURL=first-visit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"first-visit.d.ts","sourceRoot":"","sources":["../src/first-visit.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,IAAI,IAAI,CAUvC;AAED,uDAAuD;AACvD,wBAAgB,cAAc,IAAI,UAAU,GAAG,SAAS,CAUvD"}
@@ -0,0 +1,29 @@
1
+ import { hasClientId } from './client-id.js';
2
+ const TIME_KEY = '__typhoon_first_visit_time';
3
+ const URL_KEY = '__typhoon_first_visit_url';
4
+ /** Stores a new visitor's first visit time and URL, keeping any earlier record. */
5
+ export function recordFirstVisit() {
6
+ try {
7
+ if (hasClientId() || window.localStorage.getItem(TIME_KEY) !== null) {
8
+ return;
9
+ }
10
+ window.localStorage.setItem(TIME_KEY, new Date().toISOString());
11
+ window.localStorage.setItem(URL_KEY, window.location.href);
12
+ }
13
+ catch {
14
+ // Do nothing
15
+ }
16
+ }
17
+ /** Reads and clears the stored first visit, if any. */
18
+ export function takeFirstVisit() {
19
+ try {
20
+ const time = window.localStorage.getItem(TIME_KEY);
21
+ const url = window.localStorage.getItem(URL_KEY);
22
+ window.localStorage.removeItem(TIME_KEY);
23
+ window.localStorage.removeItem(URL_KEY);
24
+ return time && url ? { time, url } : undefined;
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
@@ -0,0 +1,4 @@
1
+ export type { TyphoonXConfig, TyphoonXItem, TyphoonXItemListEvent, TyphoonXItemsEvent, TyphoonXSearchEvent, TyphoonXTracker, } from './tracker.js';
2
+ export type { TyphoonXProviderProps } from './TyphoonXProvider.js';
3
+ export { TyphoonXProvider, useTyphoonX } from './TyphoonXProvider.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAC,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAC,gBAAgB,EAAE,WAAW,EAAC,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { TyphoonXProvider, useTyphoonX } from './TyphoonXProvider.js';
@@ -0,0 +1,7 @@
1
+ export interface MetaPixelIds {
2
+ fbc?: string;
3
+ fbp?: string;
4
+ }
5
+ /** Meta Pixel `_fbp` / `_fbc` cookies, with `_fbc` falling back to `url`'s `fbclid`. */
6
+ export declare function getMetaPixelIds(url: string): MetaPixelIds;
7
+ //# sourceMappingURL=meta-pixel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta-pixel.d.ts","sourceRoot":"","sources":["../src/meta-pixel.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAQzD"}
@@ -0,0 +1,23 @@
1
+ import CookieLib from 'universal-cookie';
2
+ // Runtime default export is the class.
3
+ const cookies = new CookieLib();
4
+ /** Meta Pixel `_fbp` / `_fbc` cookies, with `_fbc` falling back to `url`'s `fbclid`. */
5
+ export function getMetaPixelIds(url) {
6
+ const fbp = cookies.get('_fbp', { doNotParse: true });
7
+ const fbc = cookies.get('_fbc', { doNotParse: true }) ?? fbcFromUrl(url);
8
+ return {
9
+ ...(fbc === undefined ? {} : { fbc }),
10
+ ...(fbp === undefined ? {} : { fbp }),
11
+ };
12
+ }
13
+ function fbcFromUrl(url) {
14
+ let fbclid;
15
+ try {
16
+ fbclid = new URL(url).searchParams.get('fbclid');
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ // Meta specifies subdomain index 1 for an `_fbc` built outside the Pixel.
22
+ return fbclid ? `fb.1.${String(Date.now())}.${fbclid}` : undefined;
23
+ }
@@ -0,0 +1,43 @@
1
+ export interface TyphoonXConfig {
2
+ /** Whether the visitor allows data collection. Nothing is sent or stored while `false`. */
3
+ consent: boolean;
4
+ cookieDomain?: string;
5
+ merchantId: string;
6
+ shopId: string;
7
+ }
8
+ export interface TyphoonXItem {
9
+ itemBrand: string;
10
+ itemId: string;
11
+ itemName: string;
12
+ price: number | string;
13
+ quantity: number;
14
+ }
15
+ export interface TyphoonXItemsEvent {
16
+ currency?: string;
17
+ items: TyphoonXItem[];
18
+ value: number | string;
19
+ }
20
+ export interface TyphoonXItemListEvent {
21
+ currency?: string;
22
+ itemListId: string;
23
+ itemListName: string;
24
+ }
25
+ export interface TyphoonXSearchEvent {
26
+ searchTerm: string;
27
+ }
28
+ export interface TyphoonXTracker {
29
+ addToCart: (event: TyphoonXItemsEvent) => void;
30
+ pageView: () => void;
31
+ removeFromCart: (event: TyphoonXItemsEvent) => void;
32
+ search: (event: TyphoonXSearchEvent) => void;
33
+ viewCart: (event: TyphoonXItemsEvent) => void;
34
+ viewItem: (event: TyphoonXItemsEvent) => void;
35
+ viewItemList: (event: TyphoonXItemListEvent) => void;
36
+ }
37
+ /** Current and previous page URLs, kept across trackers so consent changes don't reset the referrer. */
38
+ export interface PageHistory {
39
+ previousUrl: string | null;
40
+ url: string | null;
41
+ }
42
+ export declare function createTracker({ consent, cookieDomain, merchantId, shopId }: TyphoonXConfig, history: PageHistory): TyphoonXTracker;
43
+ //# sourceMappingURL=tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../src/tracker.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,2FAA2F;IAC3F,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,cAAc,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACpD,MAAM,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C,YAAY,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CACtD;AAED,wGAAwG;AACxG,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AA+CD,wBAAgB,aAAa,CAC3B,EAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAC,EAAE,cAAc,EAC3D,OAAO,EAAE,WAAW,GACnB,eAAe,CAwGjB"}
@@ -0,0 +1,97 @@
1
+ import { getOrCreateClientId } from './client-id.js';
2
+ import { takeFirstVisit } from './first-visit.js';
3
+ import { getMetaPixelIds } from './meta-pixel.js';
4
+ const COLLECT_ENDPOINT = 'https://spell.typhoonx.io/api/v1/receive';
5
+ const ignore = () => undefined;
6
+ const NOOP_TRACKER = {
7
+ addToCart: ignore,
8
+ pageView: ignore,
9
+ removeFromCart: ignore,
10
+ search: ignore,
11
+ viewCart: ignore,
12
+ viewItem: ignore,
13
+ viewItemList: ignore,
14
+ };
15
+ export function createTracker({ consent, cookieDomain, merchantId, shopId }, history) {
16
+ if (!consent) {
17
+ return NOOP_TRACKER;
18
+ }
19
+ const referrerFor = (url) => {
20
+ if (history.url !== url) {
21
+ history.previousUrl = history.url;
22
+ history.url = url;
23
+ }
24
+ return history.previousUrl ?? document.referrer;
25
+ };
26
+ const send = (payload) => {
27
+ navigator.sendBeacon(COLLECT_ENDPOINT, new Blob([JSON.stringify(payload)], { type: 'application/json' }));
28
+ };
29
+ const shared = (currency) => {
30
+ const url = window.location.href;
31
+ return {
32
+ client_id: getOrCreateClientId(cookieDomain),
33
+ merchant_id: merchantId,
34
+ referrer: referrerFor(url),
35
+ request_page_url: url,
36
+ shop_id: shopId,
37
+ timestamp: new Date().toISOString(),
38
+ user_agent: window.navigator.userAgent,
39
+ ...(currency === undefined ? {} : { currency }),
40
+ ...getMetaPixelIds(url),
41
+ };
42
+ };
43
+ const trackedThisTask = new Set();
44
+ // StrictMode re-runs effects synchronously, so identical calls within one task are the same event.
45
+ const dedupe = (event, track) => (...args) => {
46
+ const key = JSON.stringify([event, window.location.href, args]);
47
+ if (trackedThisTask.has(key))
48
+ return;
49
+ trackedThisTask.add(key);
50
+ queueMicrotask(() => trackedThisTask.delete(key));
51
+ track(...args);
52
+ };
53
+ const itemsEvent = (event) => dedupe(event, ({ currency, items, value }) => {
54
+ send({
55
+ ...shared(currency),
56
+ event,
57
+ items: items.map((item) => ({
58
+ item_brand: item.itemBrand,
59
+ item_id: item.itemId,
60
+ item_name: item.itemName,
61
+ price: String(item.price),
62
+ quantity: item.quantity,
63
+ })),
64
+ value: String(value),
65
+ });
66
+ });
67
+ return {
68
+ addToCart: itemsEvent('add_to_cart'),
69
+ pageView: dedupe('page_view', () => {
70
+ const payload = shared();
71
+ send({ ...payload, event: 'page_view' });
72
+ const firstVisit = takeFirstVisit();
73
+ if (firstVisit === undefined)
74
+ return;
75
+ send({
76
+ ...payload,
77
+ event: 'first_visit',
78
+ request_page_url: firstVisit.url,
79
+ timestamp: firstVisit.time,
80
+ });
81
+ }),
82
+ removeFromCart: itemsEvent('remove_from_cart'),
83
+ search: dedupe('search', ({ searchTerm }) => {
84
+ send({ ...shared(), event: 'search', search_term: searchTerm });
85
+ }),
86
+ viewCart: itemsEvent('view_cart'),
87
+ viewItem: itemsEvent('view_item'),
88
+ viewItemList: dedupe('view_item_list', ({ currency, itemListId, itemListName }) => {
89
+ send({
90
+ ...shared(currency),
91
+ event: 'view_item_list',
92
+ item_list_id: itemListId,
93
+ item_list_name: itemListName,
94
+ });
95
+ }),
96
+ };
97
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@wapitee/typhoonx-react",
3
+ "version": "0.0.0",
4
+ "description": "TyphoonX storefront events for React",
5
+ "keywords": [
6
+ "typhoonx",
7
+ "analytics",
8
+ "react",
9
+ "storefront"
10
+ ],
11
+ "homepage": "https://github.com/Wapitee-Interactive-Marketing-Limited/typhoonx-js/tree/main/packages/typhoonx-react",
12
+ "bugs": {
13
+ "url": "https://github.com/Wapitee-Interactive-Marketing-Limited/typhoonx-js/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/Wapitee-Interactive-Marketing-Limited/typhoonx-js.git",
18
+ "directory": "packages/typhoonx-react"
19
+ },
20
+ "license": "MIT",
21
+ "author": "jay@wapitee.io",
22
+ "sideEffects": false,
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "files": [
33
+ "LICENSE",
34
+ "README.md",
35
+ "dist"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.json"
39
+ },
40
+ "dependencies": {
41
+ "universal-cookie": "^8.1.2"
42
+ },
43
+ "devDependencies": {
44
+ "@types/react": "^18.3.31",
45
+ "react": "^18.3.1",
46
+ "typescript": "^6.0.3"
47
+ },
48
+ "peerDependencies": {
49
+ "react": "^18 || ^19"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "provenance": true,
54
+ "registry": "https://registry.npmjs.org"
55
+ }
56
+ }