@wapitee/typhoonx-hydrogen 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 +21 -27
- package/dist/TyphoonX.js +62 -21
- package/dist/client-id.d.ts +16 -0
- package/dist/client-id.d.ts.map +1 -1
- package/dist/client-id.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TyphoonX for Shopify Hydrogen
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@wapitee/typhoonx-hydrogen)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Hydrogen `useAnalytics` subscriber that sends TyphoonX storefront events.
|
|
6
7
|
|
|
7
8
|
## Install
|
|
8
9
|
|
|
9
10
|
```bash
|
|
10
|
-
|
|
11
|
+
npm install --save @wapitee/typhoonx-hydrogen
|
|
11
12
|
```
|
|
12
13
|
|
|
13
|
-
Peer dependencies: `react` ^18.3.1 and `@shopify/hydrogen` >=2025.5.1. Node.js 20 or later.
|
|
14
|
-
|
|
15
14
|
## Usage
|
|
16
15
|
|
|
17
16
|
```tsx
|
|
17
|
+
// app/root.tsx
|
|
18
18
|
import {Analytics} from '@shopify/hydrogen';
|
|
19
19
|
import TyphoonX from '@wapitee/typhoonx-hydrogen';
|
|
20
20
|
|
|
@@ -28,31 +28,25 @@ export function App() {
|
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### Props
|
|
31
|
+
If your Shopify Hydrogen project sets a Content-Security-Policy, add `https://spell.typhoonx.io` to `connect-src` so event collection is not blocked.
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
```javascript
|
|
34
|
+
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
|
|
35
|
+
shop: {
|
|
36
|
+
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
|
|
37
|
+
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
|
|
38
|
+
},
|
|
39
|
+
connectSrc: ['https://spell.typhoonx.io'], // add this line
|
|
40
|
+
});
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
###
|
|
46
|
-
|
|
47
|
-
| Hydrogen | TyphoonX |
|
|
48
|
-
| ----------------------- | ------------- |
|
|
49
|
-
| `page_viewed` | `page_view` |
|
|
50
|
-
| `product_viewed` | `view_item` |
|
|
51
|
-
| `product_added_to_cart` | `add_to_cart` |
|
|
52
|
-
|
|
53
|
-
Beacons go to `https://spell.typhoonx.io/api/v1/receive`. Checkout and purchase are out of scope.
|
|
43
|
+
### Props
|
|
54
44
|
|
|
55
|
-
|
|
45
|
+
| Prop | Type | Required | Description |
|
|
46
|
+
| -------------- | -------- | -------- | ------------------------------------------------------------------------ |
|
|
47
|
+
| `merchantId` | `string` | yes | TyphoonX merchant ID (`TPX-…`) |
|
|
48
|
+
| `shopId` | `string` | yes | Shopify store ID |
|
|
49
|
+
| `cookieDomain` | `string` | no | Cookie `Domain` for a first-party client id. Omit for a host-only cookie |
|
|
56
50
|
|
|
57
51
|
## License
|
|
58
52
|
|
package/dist/TyphoonX.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { parseGid, useAnalytics } from '@shopify/hydrogen';
|
|
2
|
+
import { flattenConnection, parseGid, useAnalytics } from '@shopify/hydrogen';
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { getOrCreateClientId } from './client-id.js';
|
|
5
5
|
const COLLECT_ENDPOINT = 'https://spell.typhoonx.io/api/v1/receive';
|
|
@@ -44,30 +44,29 @@ function TyphoonXClient({ cookieDomain, merchantId, shopId }) {
|
|
|
44
44
|
user_agent: window.navigator.userAgent,
|
|
45
45
|
...(currency === undefined ? {} : { currency }),
|
|
46
46
|
});
|
|
47
|
-
subscribe('
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
event: 'page_view',
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
subscribe('product_viewed', (data) => {
|
|
54
|
-
const product = data.products[0];
|
|
55
|
-
if (product === undefined) {
|
|
47
|
+
subscribe('cart_viewed', (data) => {
|
|
48
|
+
const { cart } = data;
|
|
49
|
+
if (!cart?.lines) {
|
|
56
50
|
return;
|
|
57
51
|
}
|
|
52
|
+
const cartLines = flattenConnection(cart.lines);
|
|
58
53
|
send({
|
|
59
54
|
...shared(data.url, data.shop?.currency),
|
|
60
|
-
event: '
|
|
61
|
-
items:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
event: 'view_cart',
|
|
56
|
+
items: cartLines.map((line) => ({
|
|
57
|
+
item_brand: line.merchandise.product.vendor,
|
|
58
|
+
item_id: parseGid(line.merchandise.product.id).id,
|
|
59
|
+
item_name: line.merchandise.product.title,
|
|
60
|
+
price: line.merchandise.price.amount,
|
|
61
|
+
quantity: line.quantity,
|
|
62
|
+
})),
|
|
63
|
+
value: cart.cost.totalAmount.amount,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
subscribe('page_viewed', (data) => {
|
|
67
|
+
send({
|
|
68
|
+
...shared(data.url),
|
|
69
|
+
event: 'page_view',
|
|
71
70
|
});
|
|
72
71
|
});
|
|
73
72
|
subscribe('product_added_to_cart', (data) => {
|
|
@@ -90,6 +89,48 @@ function TyphoonXClient({ cookieDomain, merchantId, shopId }) {
|
|
|
90
89
|
value: currentLine.cost.totalAmount.amount,
|
|
91
90
|
});
|
|
92
91
|
});
|
|
92
|
+
subscribe('product_removed_from_cart', (data) => {
|
|
93
|
+
const { currentLine, prevLine } = data;
|
|
94
|
+
const quantity = (prevLine?.quantity ?? 0) - (currentLine?.quantity ?? 0);
|
|
95
|
+
if (prevLine === undefined || quantity <= 0) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
send({
|
|
99
|
+
...shared(window.location.href, data.shop?.currency),
|
|
100
|
+
event: 'remove_from_cart',
|
|
101
|
+
items: [
|
|
102
|
+
{
|
|
103
|
+
item_brand: prevLine.merchandise.product.vendor,
|
|
104
|
+
item_id: parseGid(prevLine.merchandise.product.id).id,
|
|
105
|
+
item_name: prevLine.merchandise.product.title,
|
|
106
|
+
price: prevLine.merchandise.price.amount,
|
|
107
|
+
quantity,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
value: String(Number(prevLine.cost.totalAmount.amount)
|
|
111
|
+
- Number(currentLine?.cost.totalAmount.amount ?? 0)),
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
subscribe('product_viewed', (data) => {
|
|
115
|
+
const product = data.products[0];
|
|
116
|
+
if (product === undefined) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
send({
|
|
120
|
+
...shared(data.url, data.shop?.currency),
|
|
121
|
+
event: 'view_item',
|
|
122
|
+
items: [
|
|
123
|
+
{
|
|
124
|
+
item_brand: product.vendor,
|
|
125
|
+
item_id: parseGid(product.id).id,
|
|
126
|
+
item_name: product.title,
|
|
127
|
+
price: product.price,
|
|
128
|
+
quantity: 1,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
value: product.price,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
93
134
|
ready();
|
|
94
135
|
return () => {
|
|
95
136
|
cancelled = true;
|
package/dist/client-id.d.ts
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the TyphoonX client id from the `__typhoon_client_id` cookie, or
|
|
3
|
+
* creates a new UUID and writes it when the cookie is missing.
|
|
4
|
+
*
|
|
5
|
+
* The cookie is scoped to `Path=/`, uses `SameSite=Lax`, and expires after
|
|
6
|
+
* one year. When `cookieDomain` is set, that value is used as the cookie
|
|
7
|
+
* `Domain` so the id can be shared across subdomains. The `Secure` flag is
|
|
8
|
+
* added automatically on HTTPS pages.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* This helper must run in the browser. It reads and writes `document.cookie`.
|
|
12
|
+
*
|
|
13
|
+
* @param cookieDomain - Optional cookie `Domain` attribute, typically the
|
|
14
|
+
* storefront apex domain.
|
|
15
|
+
* @returns The existing or newly created client id.
|
|
16
|
+
*/
|
|
1
17
|
export declare function getOrCreateClientId(cookieDomain?: string): string;
|
|
2
18
|
//# sourceMappingURL=client-id.d.ts.map
|
package/dist/client-id.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-id.d.ts","sourceRoot":"","sources":["../src/client-id.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client-id.d.ts","sourceRoot":"","sources":["../src/client-id.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CA8BjE"}
|
package/dist/client-id.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
+
/** Cookie name that stores the anonymous TyphoonX client identifier. */
|
|
1
2
|
const COOKIE = '__typhoon_client_id';
|
|
3
|
+
/** Cookie lifetime in seconds (365 days). */
|
|
2
4
|
const MAX_AGE = 31_536_000;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the TyphoonX client id from the `__typhoon_client_id` cookie, or
|
|
7
|
+
* creates a new UUID and writes it when the cookie is missing.
|
|
8
|
+
*
|
|
9
|
+
* The cookie is scoped to `Path=/`, uses `SameSite=Lax`, and expires after
|
|
10
|
+
* one year. When `cookieDomain` is set, that value is used as the cookie
|
|
11
|
+
* `Domain` so the id can be shared across subdomains. The `Secure` flag is
|
|
12
|
+
* added automatically on HTTPS pages.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* This helper must run in the browser. It reads and writes `document.cookie`.
|
|
16
|
+
*
|
|
17
|
+
* @param cookieDomain - Optional cookie `Domain` attribute, typically the
|
|
18
|
+
* storefront apex domain.
|
|
19
|
+
* @returns The existing or newly created client id.
|
|
20
|
+
*/
|
|
3
21
|
export function getOrCreateClientId(cookieDomain) {
|
|
4
22
|
const prefix = `${COOKIE}=`;
|
|
5
23
|
const existing = document.cookie
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wapitee/typhoonx-hydrogen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "TyphoonX Hydrogen Analytics subscriber",
|
|
5
5
|
"homepage": "https://github.com/Wapitee-Interactive-Marketing-Limited/typhoonx-js/tree/main/packages/typhoonx-hydrogen",
|
|
6
6
|
"bugs": {
|