notify-me-wl 1.4.2 → 1.4.3

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.
Files changed (4) hide show
  1. package/CLAUDE.md +199 -0
  2. package/README.md +8 -0
  3. package/index.js +53 -3
  4. package/package.json +1 -1
package/CLAUDE.md ADDED
@@ -0,0 +1,199 @@
1
+ # CLAUDE.md — The Wishlist Company (TWC / Wishlist.io)
2
+
3
+ This file gives Claude Code persistent context about the TWC platform so every session starts informed. Update it as the platform evolves.
4
+
5
+ ---
6
+
7
+ ## This Repository
8
+
9
+ **Repo:** `notify-me_coming-soon-wl-widget`
10
+ **GitHub:** https://github.com/The-Wishlist-Co/notify-me_coming-soon-wl-widget
11
+
12
+ Customer-facing widget for 'Notify Me' and 'Coming Soon' wishlist functionality. Embedded on retailer product pages.
13
+
14
+ ---
15
+
16
+ ---
17
+
18
+ ## Platform Overview
19
+
20
+ **Wishlist.io** is a SaaS retail platform focused on capturing and activating consumer intent data, primarily in fashion retail. It surfaces wishlist and engagement data to frontline retail staff and powers personalised outreach via messaging templates.
21
+
22
+ **GitHub org:** https://github.com/The-Wishlist-Co
23
+
24
+ ---
25
+
26
+ ## Service Architecture
27
+
28
+ Microservices stack deployed on **AWS EKS**, service mesh via **Istio + Consul**. Netflix OSS for gateway/resilience. All services are **Spring Boot / JHipster**, Java 13.
29
+
30
+ | Service | Role |
31
+ |---|---|
32
+ | `twcgateway` | API gateway (Zuul) |
33
+ | `wsservice` | Wishlist core service |
34
+ | `customerservice` | Customer profile service |
35
+ | `productsvc` | Product catalogue service |
36
+ | `eventcollector` | Event ingestion |
37
+ | `inventorysvc` | Inventory service |
38
+ | `merchantsvc` | Merchant/retailer management |
39
+ | `ordersvc` | Order service |
40
+
41
+ **Observability:** OpenTelemetry + Datadog. Logs via Filebeat → Elasticsearch (`twc-ops` cluster, ECK on EKS, Elasticsearch 8.3.3, 4-node).
42
+
43
+ ---
44
+
45
+ ## Critical Infrastructure Constraints
46
+
47
+ ### Java / JVM (ALL services)
48
+
49
+ All services run Java 13 in containers and **cannot auto-detect CPU limits correctly via cgroups**. Every service deployment MUST include:
50
+
51
+ ```
52
+ -XX:ActiveProcessorCount=N
53
+ ```
54
+
55
+ in `JAVA_OPTS`, where N matches the container's CPU allocation. Omitting this causes Undertow thread pool over-sizing → Consul health check failures → Hystrix circuit breaker trips → 500 SHORTCIRCUIT errors at the gateway.
56
+
57
+ Affected services (apply to all eight): `twcgateway`, `wsservice`, `customerservice`, `productsvc`, `eventcollector`, `inventorysvc`, `merchantsvc`, `ordersvc`.
58
+
59
+ ### Elasticsearch / ECK
60
+
61
+ - Cluster: `twc-ops`, 4 nodes on EKS via ECK operator
62
+ - **Memory:** each node must be configured with **4Gi** (`-Xms2g -Xmx2g`). Nodes with 2Gi will OOM-kill under load.
63
+ - **ILM:** Filebeat data stream indices require an ILM policy with **3-day retention** to prevent disk exhaustion.
64
+ - Yellow cluster state will block ECK rolling restarts — resolve unassigned shards before patching the CR.
65
+
66
+ ### EKS Scaling Lambda
67
+
68
+ A Lambda (`eks_node_scaleup`) has hardcoded node group values and will override manual EKS scaling changes. Do not rely on manual scaling being durable — any fix must go into the Lambda source.
69
+
70
+ ---
71
+
72
+ ## Data Architecture
73
+
74
+ ### ClickHouse (Analytics / Reporting)
75
+
76
+ Used for conversion reporting and analytics. All ETL queries follow strict conventions — see Query Conventions below.
77
+
78
+ ### DynamoDB (Operational)
79
+
80
+ Key tables:
81
+
82
+ | Table | PK | SK | Notes |
83
+ |---|---|---|---|
84
+ | `TWCTEMPLATE_TOPIC` | `tenantId` | `topicId` | Soft-delete via `active` flag |
85
+ | `MESSAGE` | — | — | `messageTopic` stored directly at send time — single source of truth |
86
+
87
+ `messageTopic` is captured on the MESSAGE record at send time and is **not** looked up from `TWCTEMPLATE_TOPIC` at query time. This is intentional.
88
+
89
+ ---
90
+
91
+ ## ClickHouse Query Conventions
92
+
93
+ These conventions are non-negotiable. All new queries must follow them.
94
+
95
+ ### Named Queries (Conversion Reporting Dashboard)
96
+
97
+ | Query ID | Description |
98
+ |---|---|
99
+ | `CONV_KPI_SUMMARY` | Top-level KPI summary |
100
+ | `CONV_TREND_DAILY` | Daily trend over period |
101
+ | `CONV_BREAKDOWN_STORE` | Breakdown by store |
102
+ | `CONV_BREAKDOWN_STAFF` | Breakdown by staff |
103
+ | `CONV_BREAKDOWN_TOPIC` | Breakdown by message topic |
104
+ | `CONV_BREAKDOWN_TEMPLATE_TAG` | Breakdown by template tag |
105
+ | `CONV_BREAKDOWN_CUSTOMER_TAG` | Breakdown by customer tag |
106
+ | `CONV_BREAKDOWN_CHANNEL` | Breakdown by channel |
107
+
108
+ ### API Parameter Naming Convention
109
+
110
+ | Parameter | Maps to |
111
+ |---|---|
112
+ | `value1` | channel |
113
+ | `value2` | topic |
114
+ | `value3` | templateTag |
115
+ | `value4` | customerTag |
116
+ | `param5` | storeRefs (array) |
117
+ | `param6` | staffRefs (array) |
118
+
119
+ ### Filter Syntax Rules
120
+
121
+ **`storeRef` and `staffRef` are ALWAYS multi-select.** These are never single-value filters.
122
+
123
+ ```sql
124
+ -- ✅ CORRECT — always use IN with empty() guard
125
+ AND (empty({param5: Array(String)}) OR storeRef IN {param5: Array(String)})
126
+ AND (empty({param6: Array(String)}) OR staffRef IN {param6: Array(String)})
127
+
128
+ -- ❌ WRONG — never use equality for store or staff
129
+ AND storeRef = {param5: String}
130
+ AND staffRef = {param6: String}
131
+ ```
132
+
133
+ All other filters (`value1`–`value4`) follow the same array pattern if they are multi-select dropdowns.
134
+
135
+ ---
136
+
137
+ ## Integrations
138
+
139
+ ### Shopify
140
+
141
+ - Custom pixel integration on `twc-fashion-demo.myshopify.com` (pixel `87425273`)
142
+ - Pixels require `analytics.subscribe('init', ...)` capture and `Content-Type: application/json` header
143
+ - App updates auto-deploy to all merchants unless new OAuth permission scopes are required
144
+
145
+ ### Gorgias
146
+
147
+ Backend proxy integration surfacing support ticket data on customer profile screens.
148
+
149
+ - Per-customer credentials stored encrypted (AES-256) in `gorgias_credentials` table
150
+ - Ticket data cached in `gorgias_ticket_cache`
151
+ - Cache TTLs: 15 min (summary), 2 min (full ticket list)
152
+ - Five API endpoints for credential management and ticket retrieval
153
+
154
+ ### Emarsys
155
+
156
+ Marketing automation platform integration. Customer data synced from TWC to Emarsys for campaign targeting. Webhook updates received via `emarsys-webhook-updates` Lambda.
157
+
158
+ ### Newstore
159
+
160
+ POS platform integration. TWC builds plugins/webviews for the Newstore POS environment. Webhooks received via `twc-newstore-webhook`. Proxy via `infinityAPI` for Triquestra Infinity POS.
161
+
162
+ ---
163
+
164
+ ## Development Conventions
165
+
166
+ ### General
167
+
168
+ - Full-stack ownership: data modelling, API design, backend, infra/DevOps, frontend
169
+ - Prefer explicit over implicit — document intent in code comments for complex query logic
170
+ - All schema changes should consider soft-delete patterns (see `TWCTEMPLATE_TOPIC` above)
171
+ - Default branch: `main`
172
+ - Feature branches cut from `main`, kebab-case description (e.g. `suppression-change`)
173
+ - PR back to `main` when complete
174
+
175
+ ### Code Review Checklist (for Claude Code to apply)
176
+
177
+ Before suggesting or approving any change, verify:
178
+
179
+ - [ ] All ClickHouse queries touching store/staff use `IN` + `empty()` guard, not `=`
180
+ - [ ] Any new or modified Spring Boot service includes `-XX:ActiveProcessorCount` in JAVA_OPTS config
181
+ - [ ] DynamoDB table designs include `tenantId` as PK for all multi-tenant tables
182
+ - [ ] New Elasticsearch indices have an ILM policy assigned
183
+ - [ ] API parameter names follow the `value1`–`value4` / `param5`/`param6` convention
184
+
185
+ ---
186
+
187
+ ## Key External References
188
+
189
+ - Anthropic Claude Code docs: https://docs.claude.com/en/docs/claude-code/overview
190
+ - ECK operator docs: https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html
191
+ - Shopify custom pixels: https://shopify.dev/docs/api/web-pixels-api
192
+
193
+ ---
194
+
195
+ *Last updated: May 2026. Keep this file current as the platform evolves — it is the primary context source for all Claude Code sessions.*
196
+
197
+ ## References
198
+ - **Engineering Standards:** https://github.com/The-Wishlist-Co/twc-api-specs/blob/main/standards/TWC-STANDARDS.md
199
+ - **API Specifications:** https://github.com/The-Wishlist-Co/twc-api-specs/blob/main/apis/
package/README.md CHANGED
@@ -29,6 +29,14 @@ Add a button to your HTML where you want the widget to appear. The button should
29
29
 
30
30
  - `data-fields`: A JSON array specifying the fields to include in the form. Possible values are `"mobile"`, `"firstName"`, and `"lastName"`.
31
31
  - `data-type`: Specifies the type of the widget. Possible values are `"notify-me"` and `"coming-soon"`.
32
+ - `data-auth`: Auth mode. `"token"` (default) uses the bundled server-issued access token. `"proxy"` uses the Shopify App Proxy at `/apps/twc-sdk/auth/token` to obtain a tenant-scoped token (requires the customer to be logged in to the storefront).
33
+ - `data-tenant`: The TWC tenant sent as the `X-Twc-Tenant` header. Used in both auth modes. Falls back to the bundled `TENANT_ID` if omitted.
34
+
35
+ ### Proxy auth mode
36
+
37
+ When `data-auth="proxy"` is set, the widget does not use the bundled access token. Instead, on form submit it lazily fetches a tenant-scoped access token from the same-origin Shopify App Proxy endpoint `/apps/twc-sdk/auth/token` (Shopify signs and forwards the request). The token is cached for the page session and reused across submissions.
38
+
39
+ Because the proxy only issues a token for a logged-in customer, if the token cannot be obtained the popup stays open and shows an inline "Please log in to your account to continue." message; submission is blocked until a token is available.
32
40
 
33
41
  ### Example
34
42
 
package/index.js CHANGED
@@ -3,6 +3,38 @@ const ACCESS_TOKEN =
3
3
  'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJRWlJkS3JabXJmMEk3WkhXRUtqNWRLTEhQanFubWJFeV9iNmpSbHdya1drIn0.eyJleHAiOjE3MjMwMzI3NzIsImlhdCI6MTcyMzAyOTE3MiwianRpIjoiNDJiNGYwM2QtMDNiNS00NmZlLTk5YmItZDQ2NTdhNjk5NGNiIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmF1LWF3cy50aGV3aXNobGlzdC5pby9hdXRoL3JlYWxtcy90d2NNYWluIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjI2YTIyNTAyLWRhNzQtNDhkMC1iZWFiLTgzY2E0YTlmMDdlOSIsInR5cCI6IkJlYXJlciIsImF6cCI6InR3Yy1wb3MtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjQxYWQwMDc4LWVmYTItNGVjMi1hM2I2LTIzYjBkNDM3YjEzOCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9sb2NhbGhvc3QiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInR3Yy1wb3MtdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidHdjLXN0b3JlLW93bmVyIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InRlbmFudGlkIHN0b3JlIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInRlbmFudGlkIjoidmlrdG9yaWEtd29vZHMiLCJuYW1lIjoiTWF0dCBIYW1wc2hpcmUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJtYXR0QHRoZXdpc2hsaXN0LmlvIiwic3RvcmUiOiIyMDUiLCJnaXZlbl9uYW1lIjoiTWF0dCIsImZhbWlseV9uYW1lIjoiSGFtcHNoaXJlIiwiZW1haWwiOiJtYXR0QHRoZXdpc2hsaXN0LmlvIn0.iZkMgwH74njjXUWvImkJomHYr91Lr8ZGrZGwslEGcV3vbNuoNc5CocvNWW476o-LoSh-LsKf-MLiYN1XvOuPDF3fGoGCEbMh6_M0RJcrhVWogkj81fx4ukvDPCFIjgoDCV9WIuehV9dsSWa7E0irZeE6MUVhLwRIaTzKtxgzUUKrAqBtI_HKpyo8TUGQBiYlrc85QFUyuoKbKg-QaRn_SObRLDB8ooIBJvIlgklXQt1ZYBM2HUOc5L1bAQwfzcrWEvl6eYiQHXCSPqS0rPGoaGC6v5ydBo9VMxtVHGladDHLrO3Gt2BnIGMBoYrKTAmt7j0KABwPyB3CmAIwj_pOBQ'; // Replace `ACCESS_TOKEN` with your actual access token to authenticate API requests.
4
4
  const TENANT_ID = 'victoria-woods'; // Replace `TENANT_ID` with your actual tenant ID.
5
5
 
6
+ // Session-cached access token obtained from the Shopify App Proxy (proxy auth mode).
7
+ let cachedProxyToken = null;
8
+
9
+ // Fetch a tenant-scoped access token from the Shopify App Proxy.
10
+ // The path is relative/same-origin, so Shopify's App Proxy layer appends and
11
+ // signs `shop`, `logged_in_customer_id`, `timestamp`, and `signature`.
12
+ // Returns the access token string, or null on failure (e.g. customer not logged in).
13
+ async function getProxyAccessToken() {
14
+ if (cachedProxyToken) return cachedProxyToken;
15
+ try {
16
+ const response = await fetch('/apps/twc-sdk/auth/token', {
17
+ method: 'GET',
18
+ headers: { Accept: 'application/json' },
19
+ });
20
+ const body = await response.json().catch(() => null);
21
+ if (
22
+ response.ok &&
23
+ body &&
24
+ body.success &&
25
+ body.data &&
26
+ body.data.access_token
27
+ ) {
28
+ cachedProxyToken = body.data.access_token;
29
+ return cachedProxyToken;
30
+ }
31
+ return null;
32
+ } catch (error) {
33
+ console.error('Error fetching proxy access token:', error);
34
+ return null;
35
+ }
36
+ }
37
+
6
38
  const COUNTRY_CODES = [
7
39
  { code: 'AF', name: 'Afghanistan' }, { code: 'AX', name: 'Åland Islands' },
8
40
  { code: 'AL', name: 'Albania' }, { code: 'DZ', name: 'Algeria' },
@@ -298,6 +330,7 @@ document.addEventListener('DOMContentLoaded', function () {
298
330
  <h3 id="popup-title"></h3>
299
331
  <span id="popup-close">&times;</span>
300
332
  <div id="popup-text"></div>
333
+ <div id="popup-login-msg" style="display:none; color:#d80606; font-size:14px; margin:10px 0;">Please log in to your account to continue.</div>
301
334
  <form id="popup-form">
302
335
  <div class="custom-select">
303
336
  <select name="select-size" required>
@@ -316,6 +349,7 @@ document.addEventListener('DOMContentLoaded', function () {
316
349
  const popupOpenButton = document.getElementById('popup-open');
317
350
  const popupTitle = document.getElementById('popup-title');
318
351
  const popupText = document.getElementById('popup-text');
352
+ const loginMsg = document.getElementById('popup-login-msg');
319
353
  const sizeSelect = document.querySelector("select[name='select-size']");
320
354
  const form = overlay.querySelector('#popup-form');
321
355
 
@@ -333,6 +367,10 @@ document.addEventListener('DOMContentLoaded', function () {
333
367
  popupOpenButton.getAttribute('data-fields') || '["email"]',
334
368
  );
335
369
  const type = popupOpenButton.getAttribute('data-type') || 'notify-me';
370
+ // Auth mode: 'token' (default, hardcoded ACCESS_TOKEN) or 'proxy' (Shopify App Proxy).
371
+ const authMode = popupOpenButton.getAttribute('data-auth') || 'token';
372
+ // Tenant for X-Twc-Tenant header; used in both auth modes.
373
+ const tenant = popupOpenButton.getAttribute('data-tenant') || TENANT_ID;
336
374
  const typeConfig = {
337
375
  'notify-me': {
338
376
  text: 'Register to receive a notification as soon as this item is back in stock',
@@ -418,7 +456,7 @@ document.addEventListener('DOMContentLoaded', function () {
418
456
 
419
457
  // Form submission handler
420
458
  if (form) {
421
- form.addEventListener('submit', function (event) {
459
+ form.addEventListener('submit', async function (event) {
422
460
  event.preventDefault();
423
461
 
424
462
  const selectedSize = sizeSelect.value;
@@ -472,14 +510,26 @@ document.addEventListener('DOMContentLoaded', function () {
472
510
 
473
511
  if (resolvedMarketId) formData.marketId = resolvedMarketId;
474
512
 
513
+ // Resolve the Authorization token based on auth mode.
514
+ let authToken = ACCESS_TOKEN;
515
+ if (authMode === 'proxy') {
516
+ const proxyToken = await getProxyAccessToken();
517
+ if (!proxyToken) {
518
+ loginMsg.style.display = 'block';
519
+ return;
520
+ }
521
+ loginMsg.style.display = 'none';
522
+ authToken = `Bearer ${proxyToken}`;
523
+ }
524
+
475
525
  let url = `https://api.au-sandbox.thewishlist.io/services/wsservice/api/wishlist/items/customerInterest`;
476
526
 
477
527
  fetch(url, {
478
528
  method: 'POST',
479
529
  headers: {
480
530
  'Content-Type': 'application/json',
481
- Authorization: ACCESS_TOKEN,
482
- 'X-Twc-Tenant': TENANT_ID,
531
+ Authorization: authToken,
532
+ 'X-Twc-Tenant': tenant,
483
533
  },
484
534
  body: JSON.stringify(formData),
485
535
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notify-me-wl",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"