@fstacksoftware/chatwoot-embed 1.2.0 → 1.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/dist/index.cjs CHANGED
@@ -123,6 +123,11 @@ var seedSession = (token) => {
123
123
  if (isInIframe()) storage.set(`${STORE_PREFIX}cw_conversation`, token);
124
124
  else document.cookie = `cw_conversation=${token}; path=/; max-age=31536000; SameSite=Lax`;
125
125
  };
126
+ var readCurrentSession = () => {
127
+ const stored = storage.get(`${STORE_PREFIX}cw_conversation`);
128
+ if (stored) return stored;
129
+ return /(?:^|;\s*)cw_conversation=([^;]+)/.exec(document.cookie)?.[1] ?? null;
130
+ };
126
131
  var getChatwoot = () => isBrowser() ? window.$chatwoot : void 0;
127
132
  var loadChatwoot = (opts) => {
128
133
  if (!isBrowser() || !opts.baseUrl || !opts.websiteToken) return;
@@ -140,7 +145,12 @@ var loadChatwoot = (opts) => {
140
145
  }
141
146
  };
142
147
  installCookieShim();
143
- if (opts.sessionToken) seedSession(opts.sessionToken);
148
+ if (opts.sessionToken) {
149
+ seedSession(opts.sessionToken);
150
+ } else {
151
+ const current = readCurrentSession();
152
+ if (current) tokenListener(current);
153
+ }
144
154
  window.chatwootSettings = { position: "right", type: "standard", ...opts.settings };
145
155
  const script = document.createElement("script");
146
156
  script.id = "chatwoot-sdk";
@@ -163,7 +173,11 @@ var onChatwootReady = (callback) => {
163
173
  var identifyChatwoot = (identifier, user, attributes) => {
164
174
  const cw = getChatwoot();
165
175
  if (!cw || !identifier) return;
166
- cw.setUser(identifier, user);
176
+ try {
177
+ cw.setUser(identifier, user);
178
+ } catch {
179
+ return;
180
+ }
167
181
  if (attributes && Object.keys(attributes).length) cw.setCustomAttributes(attributes);
168
182
  };
169
183
  var openChat = () => getChatwoot()?.toggle("open");
@@ -178,6 +192,7 @@ var resetChatwoot = () => getChatwoot()?.reset();
178
192
 
179
193
  // src/react.ts
180
194
  var import_react = require("react");
195
+ var hasUserFields = (u) => !!u && Object.values(u).some(Boolean);
181
196
  function useChatwoot({
182
197
  boot,
183
198
  user,
@@ -201,7 +216,7 @@ function useChatwoot({
201
216
  const userKey = JSON.stringify(user ?? {});
202
217
  const attrKey = JSON.stringify(attributes ?? {});
203
218
  (0, import_react.useEffect)(() => {
204
- if (disabled || !boot?.identifier) return;
219
+ if (disabled || !boot?.identifier || !hasUserFields(user)) return;
205
220
  return onChatwootReady(
206
221
  () => identifyChatwoot(boot.identifier, { ...user, identifier_hash: boot.identifierHash || void 0 }, attributes)
207
222
  );
@@ -213,23 +228,30 @@ function ChatwootWidget(props) {
213
228
  }
214
229
 
215
230
  // src/server.ts
216
- var decodeJwt = (token) => {
231
+ var decodeJwtPart = (part) => {
217
232
  try {
218
- const b64 = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
233
+ const b64 = part.replace(/-/g, "+").replace(/_/g, "/");
219
234
  const json = decodeURIComponent(
220
235
  atob(b64.padEnd(b64.length + (4 - b64.length % 4) % 4, "=")).split("").map((c) => "%" + c.charCodeAt(0).toString(16).padStart(2, "0")).join("")
221
236
  );
222
- return JSON.parse(json);
237
+ const obj = JSON.parse(json);
238
+ return obj && typeof obj === "object" ? obj : null;
223
239
  } catch {
224
240
  return null;
225
241
  }
226
242
  };
243
+ var decodeJwt = (token) => {
244
+ const [h, p] = token.split(".");
245
+ if (!h || !p || typeof decodeJwtPart(h)?.alg !== "string") return null;
246
+ return decodeJwtPart(p);
247
+ };
227
248
  var hmacSha256Hex = async (key, message) => {
228
249
  const enc = new TextEncoder();
229
250
  const k = await globalThis.crypto.subtle.importKey("raw", enc.encode(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
230
251
  const sig = await globalThis.crypto.subtle.sign("HMAC", k, enc.encode(message));
231
252
  return Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, "0")).join("");
232
253
  };
254
+ var NULL_TTL_MS = 6e4;
233
255
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
234
256
  function createChatwootServer(config) {
235
257
  const base = config.baseUrl.replace(/\/$/, "");
@@ -242,7 +264,7 @@ function createChatwootServer(config) {
242
264
  const cacheSet = (id, token) => {
243
265
  if (!ttl) return;
244
266
  if (cache.size > 5e3) cache.clear();
245
- cache.set(id, { token, until: Date.now() + ttl });
267
+ cache.set(id, { token, until: Date.now() + (token ? ttl : Math.min(ttl, NULL_TTL_MS)) });
246
268
  };
247
269
  const api = async (path, init = {}) => {
248
270
  try {
@@ -275,7 +297,7 @@ function createChatwootServer(config) {
275
297
  if (hit && hit.until > Date.now() && (!hit.token || isUsableToken(hit.token))) return hit.token;
276
298
  const raw = (await findContact(identifier))?.custom_attributes?.[attr];
277
299
  const token = typeof raw === "string" && isUsableToken(raw) ? raw : null;
278
- if (token) cacheSet(identifier, token);
300
+ cacheSet(identifier, token);
279
301
  return token;
280
302
  };
281
303
  const saveInitSession = async (identifier, token) => {
@@ -287,9 +309,14 @@ function createChatwootServer(config) {
287
309
  contact = await findContact(identifier);
288
310
  }
289
311
  if (!contact) return { ok: false, reason: "contact_not_found" };
290
- const full = await api(`/contacts/${contact.id}`);
291
- const sources = full?.payload?.contact_inboxes?.map((ci) => ci.source_id);
292
- if (sources && !sources.includes(payload.source_id)) return { ok: false, reason: "token_not_owned" };
312
+ let owned = false;
313
+ for (let i = 0; i < 3 && !owned; i++) {
314
+ if (i) await sleep(1500);
315
+ const full = await api(`/contacts/${contact.id}`);
316
+ const sources = full?.payload?.contact_inboxes?.map((ci) => ci.source_id);
317
+ owned = !sources || sources.includes(payload.source_id);
318
+ }
319
+ if (!owned) return { ok: false, reason: "token_not_owned" };
293
320
  if (contact.custom_attributes?.[attr] === token) {
294
321
  cacheSet(identifier, token);
295
322
  return { ok: true, reason: "unchanged" };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/react.ts","../src/server.ts"],"sourcesContent":["// 1 entry duy nhất: import { ... } from '@fstacksoftware/chatwoot-embed'\nexport * from \"./client\";\nexport * from \"./react\";\nexport * from \"./server\";\nexport type * from \"./types\";\n","// Client: chạy trên trình duyệt\nimport type { ChatwootInstance, ChatwootSettings, ChatwootUser } from './types';\n\n\nconst STORE_PREFIX = 'cwcookie:';\nconst CW_COOKIE_RE = /^\\s*(cw_conversation|cw_user_[A-Za-z0-9]+)=([^;]*)/;\n\nlet shimInstalled = false;\nlet tokenListener: ((token: string) => void) | undefined;\n\nconst isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';\n\nconst storage = {\n\tget(k: string) {\n\t\ttry { return localStorage.getItem(k); } catch { return null; }\n\t},\n\tset(k: string, v: string) {\n\t\ttry { localStorage.setItem(k, v); } catch { /* blocked */ }\n\t},\n\tremove(k: string) {\n\t\ttry { localStorage.removeItem(k); } catch { /* blocked */ }\n\t},\n\tkeys(prefix: string) {\n\t\tconst out: string[] = [];\n\t\ttry {\n\t\t\tfor (let i = 0; i < localStorage.length; i++) {\n\t\t\t\tconst k = localStorage.key(i);\n\t\t\t\tif (k?.startsWith(prefix)) out.push(k.slice(prefix.length));\n\t\t\t}\n\t\t} catch { /* blocked */ }\n\t\treturn out;\n\t},\n};\n\nexport const isInIframe = () => {\n\tif (!isBrowser()) return false;\n\ttry {\n\t\treturn window.self !== window.top;\n\t} catch {\n\t\treturn true;\n\t}\n};\n\n/**\n * Trong iframe khác site (Shopify admin), cookie SameSite=Lax của SDK bị chặn -> mỗi lần F5 là phiên mới.\n * Shim document.cookie cho key cw_*: ghi -> localStorage, đọc -> từ localStorage.\n */\nexport const installCookieShim = () => {\n\tif (!isBrowser() || shimInstalled || !isInIframe()) return;\n\tconst native = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');\n\tif (!native?.get || !native?.set) return;\n\tconst nGet = native.get;\n\tconst nSet = native.set;\n\n\tObject.defineProperty(document, 'cookie', {\n\t\tconfigurable: true,\n\t\tget() {\n\t\t\tlet value: string = nGet.call(document);\n\t\t\tfor (const name of storage.keys(STORE_PREFIX)) {\n\t\t\t\tif (new RegExp(`(^|;\\\\s*)${name}=`).test(value)) continue;\n\t\t\t\tconst v = storage.get(STORE_PREFIX + name);\n\t\t\t\tif (v) value = value ? `${value}; ${name}=${v}` : `${name}=${v}`;\n\t\t\t}\n\t\t\treturn value;\n\t\t},\n\t\tset(raw: string) {\n\t\t\tconst m = CW_COOKIE_RE.exec(raw);\n\t\t\tif (m) {\n\t\t\t\tconst [, name, val] = m;\n\t\t\t\tconst expired = !val || /expires=Thu, 01 Jan 1970/i.test(raw) || /max-age=(0|-)/i.test(raw);\n\t\t\t\tif (expired) {\n\t\t\t\t\tstorage.remove(STORE_PREFIX + name);\n\t\t\t\t} else {\n\t\t\t\t\tconst changed = storage.get(STORE_PREFIX + name) !== val;\n\t\t\t\t\tstorage.set(STORE_PREFIX + name, val);\n\t\t\t\t\tif (changed && name === 'cw_conversation') tokenListener?.(val);\n\t\t\t\t}\n\t\t\t}\n\t\t\tnSet.call(document, raw);\n\t\t},\n\t});\n\tshimInstalled = true;\n};\n\nconst seedSession = (token: string) => {\n\tif (isInIframe()) storage.set(`${STORE_PREFIX}cw_conversation`, token);\n\telse document.cookie = `cw_conversation=${token}; path=/; max-age=31536000; SameSite=Lax`;\n};\n\nexport type LoadChatwootOptions = {\n\tbaseUrl: string;\n\twebsiteToken: string;\n\t/** Token phiên đã lưu (init_session) -> mở lại đúng phiên/cuộc chat cũ */\n\tsessionToken?: string | null;\n\t/** Gọi khi Chatwoot cấp token phiên mới */\n\tonSessionToken?: (token: string) => void;\n\t/** Nếu set, tự POST { token } tới endpoint này khi có token mới (vd: /api/chatwoot-session) */\n\tsessionEndpoint?: string;\n\tsettings?: ChatwootSettings;\n};\n\nexport const getChatwoot = (): ChatwootInstance | undefined => (isBrowser() ? window.$chatwoot : undefined);\n\nexport const loadChatwoot = (opts: LoadChatwootOptions) => {\n\tif (!isBrowser() || !opts.baseUrl || !opts.websiteToken) return;\n\tif (window.chatwootSDK || document.getElementById('chatwoot-sdk')) return;\n\n\tconst baseUrl = opts.baseUrl.replace(/\\/$/, '');\n\ttokenListener = (token) => {\n\t\topts.onSessionToken?.(token);\n\t\tif (opts.sessionEndpoint) {\n\t\t\tfetch(opts.sessionEndpoint, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ token }),\n\t\t\t}).catch(() => {});\n\t\t}\n\t};\n\n\tinstallCookieShim(); // phải trước khi inject sdk.js\n\tif (opts.sessionToken) seedSession(opts.sessionToken);\n\n\twindow.chatwootSettings = { position: 'right', type: 'standard', ...opts.settings };\n\tconst script = document.createElement('script');\n\tscript.id = 'chatwoot-sdk';\n\tscript.src = `${baseUrl}/packs/js/sdk.js`;\n\tscript.async = true;\n\tscript.onload = () => window.chatwootSDK?.run({ websiteToken: opts.websiteToken, baseUrl });\n\tdocument.body.appendChild(script);\n};\n\n/** Chạy callback khi widget sẵn sàng. Trả về hàm huỷ đăng ký. */\nexport const onChatwootReady = (callback: () => void) => {\n\tif (!isBrowser()) return () => {};\n\tif (window.$chatwoot?.hasLoaded) {\n\t\tcallback();\n\t\treturn () => {};\n\t}\n\twindow.addEventListener('chatwoot:ready', callback);\n\treturn () => window.removeEventListener('chatwoot:ready', callback);\n};\n\nexport const identifyChatwoot = (\n\tidentifier: string,\n\tuser: ChatwootUser,\n\tattributes?: Record<string, unknown>\n) => {\n\tconst cw = getChatwoot();\n\tif (!cw || !identifier) return;\n\tcw.setUser(identifier, user);\n\tif (attributes && Object.keys(attributes).length) cw.setCustomAttributes(attributes);\n};\n\nexport const openChat = () => getChatwoot()?.toggle('open');\nexport const closeChat = () => getChatwoot()?.toggle('close');\nexport const toggleChat = () => {\n\tconst cw = getChatwoot();\n\tif (cw) cw.toggle(cw.isOpen ? 'close' : 'open');\n};\nexport const setChatVisible = (visible: boolean) => getChatwoot()?.toggleBubbleVisibility(visible ? 'show' : 'hide');\nexport const setChatAttributes = (attrs: Record<string, unknown>) => getChatwoot()?.setCustomAttributes(attrs);\nexport const resetChatwoot = () => getChatwoot()?.reset();\n","import { useEffect, useRef } from 'react';\nimport { identifyChatwoot, loadChatwoot, onChatwootReady } from './client';\nimport type { ChatwootBootData, ChatwootSettings, ChatwootUser } from './types';\n\n\n\nexport type UseChatwootOptions = {\n\t/** Từ server: createChatwootServer(...).getBootData(shop) */\n\tboot: ChatwootBootData | null | undefined;\n\tuser?: Omit<ChatwootUser, 'identifier_hash'>;\n\tattributes?: Record<string, unknown>;\n\t/** Endpoint lưu init_session. Mặc định /api/chatwoot-session. false = tắt */\n\tsessionEndpoint?: string | false;\n\tsettings?: ChatwootSettings;\n\t/** true = không load widget (vd: support agent) */\n\tdisabled?: boolean;\n};\n\nexport function useChatwoot({\n\tboot,\n\tuser,\n\tattributes,\n\tsessionEndpoint = '/api/chatwoot-session',\n\tsettings,\n\tdisabled,\n}: UseChatwootOptions) {\n\tconst loaded = useRef(false);\n\n\tuseEffect(() => {\n\t\tif (disabled || !boot || loaded.current) return;\n\t\tloaded.current = true;\n\t\tloadChatwoot({\n\t\t\tbaseUrl: boot.baseUrl,\n\t\t\twebsiteToken: boot.websiteToken,\n\t\t\tsessionToken: boot.initSession,\n\t\t\tsessionEndpoint: sessionEndpoint || undefined,\n\t\t\tsettings,\n\t\t});\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [disabled, boot?.baseUrl, boot?.websiteToken]);\n\n\tconst userKey = JSON.stringify(user ?? {});\n\tconst attrKey = JSON.stringify(attributes ?? {});\n\n\tuseEffect(() => {\n\t\tif (disabled || !boot?.identifier) return;\n\t\treturn onChatwootReady(() =>\n\t\t\tidentifyChatwoot(boot.identifier, { ...user, identifier_hash: boot.identifierHash || undefined }, attributes)\n\t\t);\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [disabled, boot?.identifier, boot?.identifierHash, userKey, attrKey]);\n}\n\n/** Component tiện dụng: <ChatwootWidget boot={...} user={...} /> */\nexport function ChatwootWidget(props: UseChatwootOptions) {\n\tuseChatwoot(props);\n\treturn null;\n}\n","// Server-side helpers (gọi trong loader/action). Dùng Web Crypto -> không phụ thuộc node:crypto,\n// nên package chỉ cần 1 entry. apiToken/hmacToken chỉ được truyền ở code server (.server.ts).\nimport type { ChatwootBootData } from './types';\n\nexport type ChatwootServerConfig = {\n\tbaseUrl: string;\n\twebsiteToken: string;\n\t/** Access Token của agent dùng cho API (nên tạo agent riêng) */\n\tapiToken: string;\n\taccountId?: number | string;\n\t/** Inbox → Configuration → Identity Validation */\n\thmacToken?: string;\n\t/** Tên custom attribute lưu phiên. Mặc định: init_session */\n\tsessionAttribute?: string;\n\ttimeoutMs?: number;\n\t/** Cache init_session trong RAM theo identifier. Mặc định 10 phút. 0 = tắt */\n\tcacheTtlMs?: number;\n\tlogger?: Pick<Console, 'error'>;\n};\n\ntype CwContact = {\n\tid: number;\n\tidentifier: string | null;\n\tcustom_attributes?: Record<string, unknown>;\n\tcontact_inboxes?: { source_id: string }[];\n};\n\ntype JwtPayload = { source_id?: string; inbox_id?: number; exp?: number };\n\nexport type SaveResult = {\n\tok: boolean;\n\treason: 'saved' | 'unchanged' | 'invalid_token' | 'contact_not_found' | 'token_not_owned' | 'update_failed';\n};\n\nconst decodeJwt = (token: string): JwtPayload | null => {\n\ttry {\n\t\tconst b64 = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');\n\t\tconst json = decodeURIComponent(\n\t\t\tatob(b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '='))\n\t\t\t\t.split('')\n\t\t\t\t.map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))\n\t\t\t\t.join('')\n\t\t);\n\t\treturn JSON.parse(json);\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst hmacSha256Hex = async (key: string, message: string) => {\n\tconst enc = new TextEncoder();\n\tconst k = await globalThis.crypto.subtle.importKey('raw', enc.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);\n\tconst sig = await globalThis.crypto.subtle.sign('HMAC', k, enc.encode(message));\n\treturn Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, '0')).join('');\n};\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport function createChatwootServer(config: ChatwootServerConfig) {\n\tconst base = config.baseUrl.replace(/\\/$/, '');\n\tconst account = config.accountId ?? 1;\n\tconst attr = config.sessionAttribute ?? 'init_session';\n\tconst timeout = config.timeoutMs ?? 5000;\n\tconst log = config.logger ?? console;\n\tconst ttl = config.cacheTtlMs ?? 10 * 60_000;\n\tconst cache = new Map<string, { token: string | null; until: number }>();\n\tconst cacheSet = (id: string, token: string | null) => {\n\t\tif (!ttl) return;\n\t\tif (cache.size > 5000) cache.clear(); // chặn phình RAM\n\t\tcache.set(id, { token, until: Date.now() + ttl });\n\t};\n\n\tconst api = async <T>(path: string, init: RequestInit = {}): Promise<T | null> => {\n\t\ttry {\n\t\t\tconst res = await fetch(`${base}/api/v1/accounts/${account}${path}`, {\n\t\t\t\t...init,\n\t\t\t\theaders: { 'Content-Type': 'application/json', api_access_token: config.apiToken, ...(init.headers ?? {}) },\n\t\t\t\tsignal: AbortSignal.timeout(timeout),\n\t\t\t});\n\t\t\tif (!res.ok) {\n\t\t\t\tlog.error(`[chatwoot] ${init.method ?? 'GET'} ${path} -> ${res.status}`);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn (await res.json()) as T;\n\t\t} catch (e) {\n\t\t\tlog.error(`[chatwoot] ${path}: ${(e as Error).message}`);\n\t\t\treturn null;\n\t\t}\n\t};\n\n\tconst identifierHash = async (identifier: string) =>\n\t\tconfig.hmacToken && identifier ? hmacSha256Hex(config.hmacToken, identifier) : '';\n\n\tconst isUsableToken = (token: string) => {\n\t\tconst p = decodeJwt(token);\n\t\treturn !!p?.source_id && (!p.exp || p.exp * 1000 > Date.now() + 86_400_000);\n\t};\n\n\tconst findContact = async (identifier: string) => {\n\t\tconst data = await api<{ payload: CwContact[] }>(`/contacts/search?q=${encodeURIComponent(identifier)}`);\n\t\treturn data?.payload?.find((c) => c.identifier === identifier) ?? null;\n\t};\n\n\tconst getInitSession = async (identifier: string) => {\n\t\tconst hit = cache.get(identifier);\n\t\tif (hit && hit.until > Date.now() && (!hit.token || isUsableToken(hit.token))) return hit.token;\n\n\t\tconst raw = (await findContact(identifier))?.custom_attributes?.[attr];\n\t\tconst token = typeof raw === 'string' && isUsableToken(raw) ? raw : null;\n\t\tif (token) cacheSet(identifier, token); // không cache null: shop mới sẽ có token ngay sau lần chat đầu\n\t\treturn token;\n\t};\n\n\tconst saveInitSession = async (identifier: string, token: string): Promise<SaveResult> => {\n\t\tconst payload = decodeJwt(token);\n\t\tif (!payload?.source_id) return { ok: false, reason: 'invalid_token' };\n\n\t\tlet contact: CwContact | null = null;\n\t\tfor (let i = 0; i < 6 && !contact; i++) {\n\t\t\tif (i) await sleep(1000);\n\t\t\tcontact = await findContact(identifier);\n\t\t}\n\t\tif (!contact) return { ok: false, reason: 'contact_not_found' };\n\n\t\tconst full = await api<{ payload: CwContact }>(`/contacts/${contact.id}`);\n\t\tconst sources = full?.payload?.contact_inboxes?.map((ci) => ci.source_id);\n\t\tif (sources && !sources.includes(payload.source_id)) return { ok: false, reason: 'token_not_owned' };\n\t\tif (contact.custom_attributes?.[attr] === token) {\n\t\t\tcacheSet(identifier, token);\n\t\t\treturn { ok: true, reason: 'unchanged' };\n\t\t}\n\n\t\tconst updated = await api(`/contacts/${contact.id}`, {\n\t\t\tmethod: 'PUT',\n\t\t\tbody: JSON.stringify({ custom_attributes: { ...(contact.custom_attributes ?? {}), [attr]: token } }),\n\t\t});\n\t\tif (updated) cacheSet(identifier, token);\n\t\treturn updated ? { ok: true, reason: 'saved' } : { ok: false, reason: 'update_failed' };\n\t};\n\n\t/** Gọi trong loader, trả về cho client */\n\tconst getBootData = async (identifier: string): Promise<ChatwootBootData> => ({\n\t\tbaseUrl: base,\n\t\twebsiteToken: config.websiteToken,\n\t\tidentifier,\n\t\tidentifierHash: await identifierHash(identifier),\n\t\tinitSession: await getInitSession(identifier),\n\t});\n\n\t/** Dùng trong route action: return chatwoot.handleSessionRequest(request, session.shop) */\n\tconst handleSessionRequest = async (request: Request, identifier: string) => {\n\t\tconst body = (await request.json().catch(() => ({}))) as { token?: unknown };\n\t\tif (typeof body.token !== 'string' || body.token.split('.').length !== 3) {\n\t\t\treturn Response.json({ ok: false, reason: 'invalid_token' }, { status: 400 });\n\t\t}\n\t\tconst result = await saveInitSession(identifier, body.token);\n\t\treturn Response.json(result, { status: result.ok ? 200 : 422 });\n\t};\n\n\tconst clearCache = (identifier?: string) => (identifier ? cache.delete(identifier) : cache.clear());\n\n\treturn { identifierHash, findContact, clearCache, getInitSession, saveInitSession, getBootData, handleSessionRequest };\n}\n\nexport type ChatwootServer = ReturnType<typeof createChatwootServer>;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/react.ts","../src/server.ts"],"sourcesContent":["// 1 entry duy nhất: import { ... } from '@fstacksoftware/chatwoot-embed'\nexport * from \"./client\";\nexport * from \"./react\";\nexport * from \"./server\";\nexport type * from \"./types\";\n","// Client: chạy trên trình duyệt\r\nimport type { ChatwootInstance, ChatwootSettings, ChatwootUser } from './types';\r\n\r\n\r\nconst STORE_PREFIX = 'cwcookie:';\r\nconst CW_COOKIE_RE = /^\\s*(cw_conversation|cw_user_[A-Za-z0-9]+)=([^;]*)/;\r\n\r\nlet shimInstalled = false;\r\nlet tokenListener: ((token: string) => void) | undefined;\r\n\r\nconst isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';\r\n\r\nconst storage = {\r\n\tget(k: string) {\r\n\t\ttry { return localStorage.getItem(k); } catch { return null; }\r\n\t},\r\n\tset(k: string, v: string) {\r\n\t\ttry { localStorage.setItem(k, v); } catch { /* blocked */ }\r\n\t},\r\n\tremove(k: string) {\r\n\t\ttry { localStorage.removeItem(k); } catch { /* blocked */ }\r\n\t},\r\n\tkeys(prefix: string) {\r\n\t\tconst out: string[] = [];\r\n\t\ttry {\r\n\t\t\tfor (let i = 0; i < localStorage.length; i++) {\r\n\t\t\t\tconst k = localStorage.key(i);\r\n\t\t\t\tif (k?.startsWith(prefix)) out.push(k.slice(prefix.length));\r\n\t\t\t}\r\n\t\t} catch { /* blocked */ }\r\n\t\treturn out;\r\n\t},\r\n};\r\n\r\nexport const isInIframe = () => {\r\n\tif (!isBrowser()) return false;\r\n\ttry {\r\n\t\treturn window.self !== window.top;\r\n\t} catch {\r\n\t\treturn true;\r\n\t}\r\n};\r\n\r\n/**\r\n * Trong iframe khác site (Shopify admin), cookie SameSite=Lax của SDK bị chặn -> mỗi lần F5 là phiên mới.\r\n * Shim document.cookie cho key cw_*: ghi -> localStorage, đọc -> từ localStorage.\r\n */\r\nexport const installCookieShim = () => {\r\n\tif (!isBrowser() || shimInstalled || !isInIframe()) return;\r\n\tconst native = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');\r\n\tif (!native?.get || !native?.set) return;\r\n\tconst nGet = native.get;\r\n\tconst nSet = native.set;\r\n\r\n\tObject.defineProperty(document, 'cookie', {\r\n\t\tconfigurable: true,\r\n\t\tget() {\r\n\t\t\tlet value: string = nGet.call(document);\r\n\t\t\tfor (const name of storage.keys(STORE_PREFIX)) {\r\n\t\t\t\tif (new RegExp(`(^|;\\\\s*)${name}=`).test(value)) continue;\r\n\t\t\t\tconst v = storage.get(STORE_PREFIX + name);\r\n\t\t\t\tif (v) value = value ? `${value}; ${name}=${v}` : `${name}=${v}`;\r\n\t\t\t}\r\n\t\t\treturn value;\r\n\t\t},\r\n\t\tset(raw: string) {\r\n\t\t\tconst m = CW_COOKIE_RE.exec(raw);\r\n\t\t\tif (m) {\r\n\t\t\t\tconst [, name, val] = m;\r\n\t\t\t\tconst expired = !val || /expires=Thu, 01 Jan 1970/i.test(raw) || /max-age=(0|-)/i.test(raw);\r\n\t\t\t\tif (expired) {\r\n\t\t\t\t\tstorage.remove(STORE_PREFIX + name);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst changed = storage.get(STORE_PREFIX + name) !== val;\r\n\t\t\t\t\tstorage.set(STORE_PREFIX + name, val);\r\n\t\t\t\t\tif (changed && name === 'cw_conversation') tokenListener?.(val);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tnSet.call(document, raw);\r\n\t\t},\r\n\t});\r\n\tshimInstalled = true;\r\n};\r\n\r\nconst seedSession = (token: string) => {\r\n\tif (isInIframe()) storage.set(`${STORE_PREFIX}cw_conversation`, token);\r\n\telse document.cookie = `cw_conversation=${token}; path=/; max-age=31536000; SameSite=Lax`;\r\n};\r\n\r\nconst readCurrentSession = (): string | null => {\r\n\tconst stored = storage.get(`${STORE_PREFIX}cw_conversation`);\r\n\tif (stored) return stored;\r\n\treturn /(?:^|;\\s*)cw_conversation=([^;]+)/.exec(document.cookie)?.[1] ?? null;\r\n};\r\n\r\nexport type LoadChatwootOptions = {\r\n\tbaseUrl: string;\r\n\twebsiteToken: string;\r\n\t/** Token phiên đã lưu (init_session) -> mở lại đúng phiên/cuộc chat cũ */\r\n\tsessionToken?: string | null;\r\n\t/** Gọi khi Chatwoot cấp token phiên mới */\r\n\tonSessionToken?: (token: string) => void;\r\n\t/** Nếu set, tự POST { token } tới endpoint này khi có token mới (vd: /api/chatwoot-session) */\r\n\tsessionEndpoint?: string;\r\n\tsettings?: ChatwootSettings;\r\n};\r\n\r\nexport const getChatwoot = (): ChatwootInstance | undefined => (isBrowser() ? window.$chatwoot : undefined);\r\n\r\nexport const loadChatwoot = (opts: LoadChatwootOptions) => {\r\n\tif (!isBrowser() || !opts.baseUrl || !opts.websiteToken) return;\r\n\tif (window.chatwootSDK || document.getElementById('chatwoot-sdk')) return;\r\n\r\n\tconst baseUrl = opts.baseUrl.replace(/\\/$/, '');\r\n\ttokenListener = (token) => {\r\n\t\topts.onSessionToken?.(token);\r\n\t\tif (opts.sessionEndpoint) {\r\n\t\t\tfetch(opts.sessionEndpoint, {\r\n\t\t\t\tmethod: 'POST',\r\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\r\n\t\t\t\tbody: JSON.stringify({ token }),\r\n\t\t\t}).catch(() => {});\r\n\t\t}\r\n\t};\r\n\r\n\tinstallCookieShim(); // phải trước khi inject sdk.js\r\n\tif (opts.sessionToken) {\r\n\t\tseedSession(opts.sessionToken);\r\n\t} else {\r\n\t\t// Server chưa có init_session (shop mới / admin xoá) nhưng browser còn phiên cũ:\r\n\t\t// cookie không đổi nên listener sẽ không tự chạy -> đẩy lên server ngay để lưu lại.\r\n\t\tconst current = readCurrentSession();\r\n\t\tif (current) tokenListener(current);\r\n\t}\r\n\r\n\twindow.chatwootSettings = { position: 'right', type: 'standard', ...opts.settings };\r\n\tconst script = document.createElement('script');\r\n\tscript.id = 'chatwoot-sdk';\r\n\tscript.src = `${baseUrl}/packs/js/sdk.js`;\r\n\tscript.async = true;\r\n\tscript.onload = () => window.chatwootSDK?.run({ websiteToken: opts.websiteToken, baseUrl });\r\n\tdocument.body.appendChild(script);\r\n};\r\n\r\n/** Chạy callback khi widget sẵn sàng. Trả về hàm huỷ đăng ký. */\r\nexport const onChatwootReady = (callback: () => void) => {\r\n\tif (!isBrowser()) return () => {};\r\n\tif (window.$chatwoot?.hasLoaded) {\r\n\t\tcallback();\r\n\t\treturn () => {};\r\n\t}\r\n\twindow.addEventListener('chatwoot:ready', callback);\r\n\treturn () => window.removeEventListener('chatwoot:ready', callback);\r\n};\r\n\r\nexport const identifyChatwoot = (\r\n\tidentifier: string,\r\n\tuser: ChatwootUser,\r\n\tattributes?: Record<string, unknown>\r\n) => {\r\n\tconst cw = getChatwoot();\r\n\tif (!cw || !identifier) return;\r\n\ttry {\r\n\t\tcw.setUser(identifier, user);\r\n\t} catch {\r\n\t\treturn; // SDK ném lỗi khi user không có trường nào hợp lệ\r\n\t}\r\n\tif (attributes && Object.keys(attributes).length) cw.setCustomAttributes(attributes);\r\n};\r\n\r\nexport const openChat = () => getChatwoot()?.toggle('open');\r\nexport const closeChat = () => getChatwoot()?.toggle('close');\r\nexport const toggleChat = () => {\r\n\tconst cw = getChatwoot();\r\n\tif (cw) cw.toggle(cw.isOpen ? 'close' : 'open');\r\n};\r\nexport const setChatVisible = (visible: boolean) => getChatwoot()?.toggleBubbleVisibility(visible ? 'show' : 'hide');\r\nexport const setChatAttributes = (attrs: Record<string, unknown>) => getChatwoot()?.setCustomAttributes(attrs);\r\nexport const resetChatwoot = () => getChatwoot()?.reset();\r\n","import { useEffect, useRef } from 'react';\r\nimport { identifyChatwoot, loadChatwoot, onChatwootReady } from './client';\r\nimport type { ChatwootBootData, ChatwootSettings, ChatwootUser } from './types';\r\n\r\n\r\n\r\nconst hasUserFields = (u?: object) => !!u && Object.values(u).some(Boolean);\r\n\r\nexport type UseChatwootOptions = {\r\n\t/** Từ server: createChatwootServer(...).getBootData(shop) */\r\n\tboot: ChatwootBootData | null | undefined;\r\n\tuser?: Omit<ChatwootUser, 'identifier_hash'>;\r\n\tattributes?: Record<string, unknown>;\r\n\t/** Endpoint lưu init_session. Mặc định /api/chatwoot-session. false = tắt */\r\n\tsessionEndpoint?: string | false;\r\n\tsettings?: ChatwootSettings;\r\n\t/** true = không load widget (vd: support agent) */\r\n\tdisabled?: boolean;\r\n};\r\n\r\nexport function useChatwoot({\r\n\tboot,\r\n\tuser,\r\n\tattributes,\r\n\tsessionEndpoint = '/api/chatwoot-session',\r\n\tsettings,\r\n\tdisabled,\r\n}: UseChatwootOptions) {\r\n\tconst loaded = useRef(false);\r\n\r\n\tuseEffect(() => {\r\n\t\tif (disabled || !boot || loaded.current) return;\r\n\t\tloaded.current = true;\r\n\t\tloadChatwoot({\r\n\t\t\tbaseUrl: boot.baseUrl,\r\n\t\t\twebsiteToken: boot.websiteToken,\r\n\t\t\tsessionToken: boot.initSession,\r\n\t\t\tsessionEndpoint: sessionEndpoint || undefined,\r\n\t\t\tsettings,\r\n\t\t});\r\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\r\n\t}, [disabled, boot?.baseUrl, boot?.websiteToken]);\r\n\r\n\tconst userKey = JSON.stringify(user ?? {});\r\n\tconst attrKey = JSON.stringify(attributes ?? {});\r\n\r\n\tuseEffect(() => {\r\n\t\tif (disabled || !boot?.identifier || !hasUserFields(user)) return;\r\n\t\treturn onChatwootReady(() =>\r\n\t\t\tidentifyChatwoot(boot.identifier, { ...user, identifier_hash: boot.identifierHash || undefined }, attributes)\r\n\t\t);\r\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\r\n\t}, [disabled, boot?.identifier, boot?.identifierHash, userKey, attrKey]);\r\n}\r\n\r\n/** Component tiện dụng: <ChatwootWidget boot={...} user={...} /> */\r\nexport function ChatwootWidget(props: UseChatwootOptions) {\r\n\tuseChatwoot(props);\r\n\treturn null;\r\n}\r\n","// Server-side helpers (gọi trong loader/action). Dùng Web Crypto -> không phụ thuộc node:crypto,\r\n// nên package chỉ cần 1 entry. apiToken/hmacToken chỉ được truyền ở code server (.server.ts).\r\nimport type { ChatwootBootData } from './types';\r\n\r\nexport type ChatwootServerConfig = {\r\n\tbaseUrl: string;\r\n\twebsiteToken: string;\r\n\t/** Access Token của agent dùng cho API (nên tạo agent riêng) */\r\n\tapiToken: string;\r\n\taccountId?: number | string;\r\n\t/** Inbox → Configuration → Identity Validation */\r\n\thmacToken?: string;\r\n\t/** Tên custom attribute lưu phiên. Mặc định: init_session */\r\n\tsessionAttribute?: string;\r\n\ttimeoutMs?: number;\r\n\t/** Cache init_session trong RAM theo identifier. Mặc định 10 phút. 0 = tắt */\r\n\tcacheTtlMs?: number;\r\n\tlogger?: Pick<Console, 'error'>;\r\n};\r\n\r\ntype CwContact = {\r\n\tid: number;\r\n\tidentifier: string | null;\r\n\tcustom_attributes?: Record<string, unknown>;\r\n\tcontact_inboxes?: { source_id: string }[];\r\n};\r\n\r\ntype JwtPayload = { source_id?: string; inbox_id?: number; exp?: number };\r\n\r\nexport type SaveResult = {\r\n\tok: boolean;\r\n\treason: 'saved' | 'unchanged' | 'invalid_token' | 'contact_not_found' | 'token_not_owned' | 'update_failed';\r\n};\r\n\r\nconst decodeJwtPart = (part: string): Record<string, unknown> | null => {\r\n\ttry {\r\n\t\tconst b64 = part.replace(/-/g, '+').replace(/_/g, '/');\r\n\t\tconst json = decodeURIComponent(\r\n\t\t\tatob(b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '='))\r\n\t\t\t\t.split('')\r\n\t\t\t\t.map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))\r\n\t\t\t\t.join('')\r\n\t\t);\r\n\t\tconst obj = JSON.parse(json);\r\n\t\treturn obj && typeof obj === 'object' ? obj : null;\r\n\t} catch {\r\n\t\treturn null;\r\n\t}\r\n};\r\n\r\n// Chỉ đọc (không verify chữ ký): kiểm header là JWT thật (có alg) rồi mới tin payload.\r\nconst decodeJwt = (token: string): JwtPayload | null => {\r\n\tconst [h, p] = token.split('.');\r\n\tif (!h || !p || typeof decodeJwtPart(h)?.alg !== 'string') return null;\r\n\treturn decodeJwtPart(p) as JwtPayload | null;\r\n};\r\n\r\nconst hmacSha256Hex = async (key: string, message: string) => {\r\n\tconst enc = new TextEncoder();\r\n\tconst k = await globalThis.crypto.subtle.importKey('raw', enc.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);\r\n\tconst sig = await globalThis.crypto.subtle.sign('HMAC', k, enc.encode(message));\r\n\treturn Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, '0')).join('');\r\n};\r\n\r\nconst NULL_TTL_MS = 60_000;\r\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\r\n\r\nexport function createChatwootServer(config: ChatwootServerConfig) {\r\n\tconst base = config.baseUrl.replace(/\\/$/, '');\r\n\tconst account = config.accountId ?? 1;\r\n\tconst attr = config.sessionAttribute ?? 'init_session';\r\n\tconst timeout = config.timeoutMs ?? 5000;\r\n\tconst log = config.logger ?? console;\r\n\tconst ttl = config.cacheTtlMs ?? 10 * 60_000;\r\n\tconst cache = new Map<string, { token: string | null; until: number }>();\r\n\tconst cacheSet = (id: string, token: string | null) => {\r\n\t\tif (!ttl) return;\r\n\t\tif (cache.size > 5000) cache.clear(); // chặn phình RAM\r\n\t\t// \"chưa có phiên\" chỉ cache ngắn để shop vừa chat xong sớm nhận được token\r\n\t\tcache.set(id, { token, until: Date.now() + (token ? ttl : Math.min(ttl, NULL_TTL_MS)) });\r\n\t};\r\n\r\n\tconst api = async <T>(path: string, init: RequestInit = {}): Promise<T | null> => {\r\n\t\ttry {\r\n\t\t\tconst res = await fetch(`${base}/api/v1/accounts/${account}${path}`, {\r\n\t\t\t\t...init,\r\n\t\t\t\theaders: { 'Content-Type': 'application/json', api_access_token: config.apiToken, ...(init.headers ?? {}) },\r\n\t\t\t\tsignal: AbortSignal.timeout(timeout),\r\n\t\t\t});\r\n\t\t\tif (!res.ok) {\r\n\t\t\t\tlog.error(`[chatwoot] ${init.method ?? 'GET'} ${path} -> ${res.status}`);\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t\treturn (await res.json()) as T;\r\n\t\t} catch (e) {\r\n\t\t\tlog.error(`[chatwoot] ${path}: ${(e as Error).message}`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\t};\r\n\r\n\tconst identifierHash = async (identifier: string) =>\r\n\t\tconfig.hmacToken && identifier ? hmacSha256Hex(config.hmacToken, identifier) : '';\r\n\r\n\tconst isUsableToken = (token: string) => {\r\n\t\tconst p = decodeJwt(token);\r\n\t\treturn !!p?.source_id && (!p.exp || p.exp * 1000 > Date.now() + 86_400_000);\r\n\t};\r\n\r\n\tconst findContact = async (identifier: string) => {\r\n\t\tconst data = await api<{ payload: CwContact[] }>(`/contacts/search?q=${encodeURIComponent(identifier)}`);\r\n\t\treturn data?.payload?.find((c) => c.identifier === identifier) ?? null;\r\n\t};\r\n\r\n\tconst getInitSession = async (identifier: string) => {\r\n\t\tconst hit = cache.get(identifier);\r\n\t\tif (hit && hit.until > Date.now() && (!hit.token || isUsableToken(hit.token))) return hit.token;\r\n\r\n\t\tconst raw = (await findContact(identifier))?.custom_attributes?.[attr];\r\n\t\tconst token = typeof raw === 'string' && isUsableToken(raw) ? raw : null;\r\n\t\tcacheSet(identifier, token);\r\n\t\treturn token;\r\n\t};\r\n\r\n\tconst saveInitSession = async (identifier: string, token: string): Promise<SaveResult> => {\r\n\t\tconst payload = decodeJwt(token);\r\n\t\tif (!payload?.source_id) return { ok: false, reason: 'invalid_token' };\r\n\r\n\t\tlet contact: CwContact | null = null;\r\n\t\tfor (let i = 0; i < 6 && !contact; i++) {\r\n\t\t\tif (i) await sleep(1000);\r\n\t\t\tcontact = await findContact(identifier);\r\n\t\t}\r\n\t\tif (!contact) return { ok: false, reason: 'contact_not_found' };\r\n\r\n\t\t// contact_inboxes có thể chưa kịp cập nhật source_id của token mới -> thử lại vài lần\r\n\t\tlet owned = false;\r\n\t\tfor (let i = 0; i < 3 && !owned; i++) {\r\n\t\t\tif (i) await sleep(1500);\r\n\t\t\tconst full = await api<{ payload: CwContact }>(`/contacts/${contact.id}`);\r\n\t\t\tconst sources = full?.payload?.contact_inboxes?.map((ci) => ci.source_id);\r\n\t\t\towned = !sources || sources.includes(payload.source_id);\r\n\t\t}\r\n\t\tif (!owned) return { ok: false, reason: 'token_not_owned' };\r\n\t\tif (contact.custom_attributes?.[attr] === token) {\r\n\t\t\tcacheSet(identifier, token);\r\n\t\t\treturn { ok: true, reason: 'unchanged' };\r\n\t\t}\r\n\r\n\t\tconst updated = await api(`/contacts/${contact.id}`, {\r\n\t\t\tmethod: 'PUT',\r\n\t\t\tbody: JSON.stringify({ custom_attributes: { ...(contact.custom_attributes ?? {}), [attr]: token } }),\r\n\t\t});\r\n\t\tif (updated) cacheSet(identifier, token);\r\n\t\treturn updated ? { ok: true, reason: 'saved' } : { ok: false, reason: 'update_failed' };\r\n\t};\r\n\r\n\t/** Gọi trong loader, trả về cho client */\r\n\tconst getBootData = async (identifier: string): Promise<ChatwootBootData> => ({\r\n\t\tbaseUrl: base,\r\n\t\twebsiteToken: config.websiteToken,\r\n\t\tidentifier,\r\n\t\tidentifierHash: await identifierHash(identifier),\r\n\t\tinitSession: await getInitSession(identifier),\r\n\t});\r\n\r\n\t/** Dùng trong route action: return chatwoot.handleSessionRequest(request, session.shop) */\r\n\tconst handleSessionRequest = async (request: Request, identifier: string) => {\r\n\t\tconst body = (await request.json().catch(() => ({}))) as { token?: unknown };\r\n\t\tif (typeof body.token !== 'string' || body.token.split('.').length !== 3) {\r\n\t\t\treturn Response.json({ ok: false, reason: 'invalid_token' }, { status: 400 });\r\n\t\t}\r\n\t\tconst result = await saveInitSession(identifier, body.token);\r\n\t\treturn Response.json(result, { status: result.ok ? 200 : 422 });\r\n\t};\r\n\r\n\tconst clearCache = (identifier?: string) => (identifier ? cache.delete(identifier) : cache.clear());\r\n\r\n\treturn { identifierHash, findContact, clearCache, getInitSession, saveInitSession, getBootData, handleSessionRequest };\r\n}\r\n\r\nexport type ChatwootServer = ReturnType<typeof createChatwootServer>;\r\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -83,6 +83,11 @@ var seedSession = (token) => {
83
83
  if (isInIframe()) storage.set(`${STORE_PREFIX}cw_conversation`, token);
84
84
  else document.cookie = `cw_conversation=${token}; path=/; max-age=31536000; SameSite=Lax`;
85
85
  };
86
+ var readCurrentSession = () => {
87
+ const stored = storage.get(`${STORE_PREFIX}cw_conversation`);
88
+ if (stored) return stored;
89
+ return /(?:^|;\s*)cw_conversation=([^;]+)/.exec(document.cookie)?.[1] ?? null;
90
+ };
86
91
  var getChatwoot = () => isBrowser() ? window.$chatwoot : void 0;
87
92
  var loadChatwoot = (opts) => {
88
93
  if (!isBrowser() || !opts.baseUrl || !opts.websiteToken) return;
@@ -100,7 +105,12 @@ var loadChatwoot = (opts) => {
100
105
  }
101
106
  };
102
107
  installCookieShim();
103
- if (opts.sessionToken) seedSession(opts.sessionToken);
108
+ if (opts.sessionToken) {
109
+ seedSession(opts.sessionToken);
110
+ } else {
111
+ const current = readCurrentSession();
112
+ if (current) tokenListener(current);
113
+ }
104
114
  window.chatwootSettings = { position: "right", type: "standard", ...opts.settings };
105
115
  const script = document.createElement("script");
106
116
  script.id = "chatwoot-sdk";
@@ -123,7 +133,11 @@ var onChatwootReady = (callback) => {
123
133
  var identifyChatwoot = (identifier, user, attributes) => {
124
134
  const cw = getChatwoot();
125
135
  if (!cw || !identifier) return;
126
- cw.setUser(identifier, user);
136
+ try {
137
+ cw.setUser(identifier, user);
138
+ } catch {
139
+ return;
140
+ }
127
141
  if (attributes && Object.keys(attributes).length) cw.setCustomAttributes(attributes);
128
142
  };
129
143
  var openChat = () => getChatwoot()?.toggle("open");
@@ -138,6 +152,7 @@ var resetChatwoot = () => getChatwoot()?.reset();
138
152
 
139
153
  // src/react.ts
140
154
  import { useEffect, useRef } from "react";
155
+ var hasUserFields = (u) => !!u && Object.values(u).some(Boolean);
141
156
  function useChatwoot({
142
157
  boot,
143
158
  user,
@@ -161,7 +176,7 @@ function useChatwoot({
161
176
  const userKey = JSON.stringify(user ?? {});
162
177
  const attrKey = JSON.stringify(attributes ?? {});
163
178
  useEffect(() => {
164
- if (disabled || !boot?.identifier) return;
179
+ if (disabled || !boot?.identifier || !hasUserFields(user)) return;
165
180
  return onChatwootReady(
166
181
  () => identifyChatwoot(boot.identifier, { ...user, identifier_hash: boot.identifierHash || void 0 }, attributes)
167
182
  );
@@ -173,23 +188,30 @@ function ChatwootWidget(props) {
173
188
  }
174
189
 
175
190
  // src/server.ts
176
- var decodeJwt = (token) => {
191
+ var decodeJwtPart = (part) => {
177
192
  try {
178
- const b64 = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
193
+ const b64 = part.replace(/-/g, "+").replace(/_/g, "/");
179
194
  const json = decodeURIComponent(
180
195
  atob(b64.padEnd(b64.length + (4 - b64.length % 4) % 4, "=")).split("").map((c) => "%" + c.charCodeAt(0).toString(16).padStart(2, "0")).join("")
181
196
  );
182
- return JSON.parse(json);
197
+ const obj = JSON.parse(json);
198
+ return obj && typeof obj === "object" ? obj : null;
183
199
  } catch {
184
200
  return null;
185
201
  }
186
202
  };
203
+ var decodeJwt = (token) => {
204
+ const [h, p] = token.split(".");
205
+ if (!h || !p || typeof decodeJwtPart(h)?.alg !== "string") return null;
206
+ return decodeJwtPart(p);
207
+ };
187
208
  var hmacSha256Hex = async (key, message) => {
188
209
  const enc = new TextEncoder();
189
210
  const k = await globalThis.crypto.subtle.importKey("raw", enc.encode(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
190
211
  const sig = await globalThis.crypto.subtle.sign("HMAC", k, enc.encode(message));
191
212
  return Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, "0")).join("");
192
213
  };
214
+ var NULL_TTL_MS = 6e4;
193
215
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
194
216
  function createChatwootServer(config) {
195
217
  const base = config.baseUrl.replace(/\/$/, "");
@@ -202,7 +224,7 @@ function createChatwootServer(config) {
202
224
  const cacheSet = (id, token) => {
203
225
  if (!ttl) return;
204
226
  if (cache.size > 5e3) cache.clear();
205
- cache.set(id, { token, until: Date.now() + ttl });
227
+ cache.set(id, { token, until: Date.now() + (token ? ttl : Math.min(ttl, NULL_TTL_MS)) });
206
228
  };
207
229
  const api = async (path, init = {}) => {
208
230
  try {
@@ -235,7 +257,7 @@ function createChatwootServer(config) {
235
257
  if (hit && hit.until > Date.now() && (!hit.token || isUsableToken(hit.token))) return hit.token;
236
258
  const raw = (await findContact(identifier))?.custom_attributes?.[attr];
237
259
  const token = typeof raw === "string" && isUsableToken(raw) ? raw : null;
238
- if (token) cacheSet(identifier, token);
260
+ cacheSet(identifier, token);
239
261
  return token;
240
262
  };
241
263
  const saveInitSession = async (identifier, token) => {
@@ -247,9 +269,14 @@ function createChatwootServer(config) {
247
269
  contact = await findContact(identifier);
248
270
  }
249
271
  if (!contact) return { ok: false, reason: "contact_not_found" };
250
- const full = await api(`/contacts/${contact.id}`);
251
- const sources = full?.payload?.contact_inboxes?.map((ci) => ci.source_id);
252
- if (sources && !sources.includes(payload.source_id)) return { ok: false, reason: "token_not_owned" };
272
+ let owned = false;
273
+ for (let i = 0; i < 3 && !owned; i++) {
274
+ if (i) await sleep(1500);
275
+ const full = await api(`/contacts/${contact.id}`);
276
+ const sources = full?.payload?.contact_inboxes?.map((ci) => ci.source_id);
277
+ owned = !sources || sources.includes(payload.source_id);
278
+ }
279
+ if (!owned) return { ok: false, reason: "token_not_owned" };
253
280
  if (contact.custom_attributes?.[attr] === token) {
254
281
  cacheSet(identifier, token);
255
282
  return { ok: true, reason: "unchanged" };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/react.ts","../src/server.ts"],"sourcesContent":["// Client: chạy trên trình duyệt\nimport type { ChatwootInstance, ChatwootSettings, ChatwootUser } from './types';\n\n\nconst STORE_PREFIX = 'cwcookie:';\nconst CW_COOKIE_RE = /^\\s*(cw_conversation|cw_user_[A-Za-z0-9]+)=([^;]*)/;\n\nlet shimInstalled = false;\nlet tokenListener: ((token: string) => void) | undefined;\n\nconst isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';\n\nconst storage = {\n\tget(k: string) {\n\t\ttry { return localStorage.getItem(k); } catch { return null; }\n\t},\n\tset(k: string, v: string) {\n\t\ttry { localStorage.setItem(k, v); } catch { /* blocked */ }\n\t},\n\tremove(k: string) {\n\t\ttry { localStorage.removeItem(k); } catch { /* blocked */ }\n\t},\n\tkeys(prefix: string) {\n\t\tconst out: string[] = [];\n\t\ttry {\n\t\t\tfor (let i = 0; i < localStorage.length; i++) {\n\t\t\t\tconst k = localStorage.key(i);\n\t\t\t\tif (k?.startsWith(prefix)) out.push(k.slice(prefix.length));\n\t\t\t}\n\t\t} catch { /* blocked */ }\n\t\treturn out;\n\t},\n};\n\nexport const isInIframe = () => {\n\tif (!isBrowser()) return false;\n\ttry {\n\t\treturn window.self !== window.top;\n\t} catch {\n\t\treturn true;\n\t}\n};\n\n/**\n * Trong iframe khác site (Shopify admin), cookie SameSite=Lax của SDK bị chặn -> mỗi lần F5 là phiên mới.\n * Shim document.cookie cho key cw_*: ghi -> localStorage, đọc -> từ localStorage.\n */\nexport const installCookieShim = () => {\n\tif (!isBrowser() || shimInstalled || !isInIframe()) return;\n\tconst native = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');\n\tif (!native?.get || !native?.set) return;\n\tconst nGet = native.get;\n\tconst nSet = native.set;\n\n\tObject.defineProperty(document, 'cookie', {\n\t\tconfigurable: true,\n\t\tget() {\n\t\t\tlet value: string = nGet.call(document);\n\t\t\tfor (const name of storage.keys(STORE_PREFIX)) {\n\t\t\t\tif (new RegExp(`(^|;\\\\s*)${name}=`).test(value)) continue;\n\t\t\t\tconst v = storage.get(STORE_PREFIX + name);\n\t\t\t\tif (v) value = value ? `${value}; ${name}=${v}` : `${name}=${v}`;\n\t\t\t}\n\t\t\treturn value;\n\t\t},\n\t\tset(raw: string) {\n\t\t\tconst m = CW_COOKIE_RE.exec(raw);\n\t\t\tif (m) {\n\t\t\t\tconst [, name, val] = m;\n\t\t\t\tconst expired = !val || /expires=Thu, 01 Jan 1970/i.test(raw) || /max-age=(0|-)/i.test(raw);\n\t\t\t\tif (expired) {\n\t\t\t\t\tstorage.remove(STORE_PREFIX + name);\n\t\t\t\t} else {\n\t\t\t\t\tconst changed = storage.get(STORE_PREFIX + name) !== val;\n\t\t\t\t\tstorage.set(STORE_PREFIX + name, val);\n\t\t\t\t\tif (changed && name === 'cw_conversation') tokenListener?.(val);\n\t\t\t\t}\n\t\t\t}\n\t\t\tnSet.call(document, raw);\n\t\t},\n\t});\n\tshimInstalled = true;\n};\n\nconst seedSession = (token: string) => {\n\tif (isInIframe()) storage.set(`${STORE_PREFIX}cw_conversation`, token);\n\telse document.cookie = `cw_conversation=${token}; path=/; max-age=31536000; SameSite=Lax`;\n};\n\nexport type LoadChatwootOptions = {\n\tbaseUrl: string;\n\twebsiteToken: string;\n\t/** Token phiên đã lưu (init_session) -> mở lại đúng phiên/cuộc chat cũ */\n\tsessionToken?: string | null;\n\t/** Gọi khi Chatwoot cấp token phiên mới */\n\tonSessionToken?: (token: string) => void;\n\t/** Nếu set, tự POST { token } tới endpoint này khi có token mới (vd: /api/chatwoot-session) */\n\tsessionEndpoint?: string;\n\tsettings?: ChatwootSettings;\n};\n\nexport const getChatwoot = (): ChatwootInstance | undefined => (isBrowser() ? window.$chatwoot : undefined);\n\nexport const loadChatwoot = (opts: LoadChatwootOptions) => {\n\tif (!isBrowser() || !opts.baseUrl || !opts.websiteToken) return;\n\tif (window.chatwootSDK || document.getElementById('chatwoot-sdk')) return;\n\n\tconst baseUrl = opts.baseUrl.replace(/\\/$/, '');\n\ttokenListener = (token) => {\n\t\topts.onSessionToken?.(token);\n\t\tif (opts.sessionEndpoint) {\n\t\t\tfetch(opts.sessionEndpoint, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ token }),\n\t\t\t}).catch(() => {});\n\t\t}\n\t};\n\n\tinstallCookieShim(); // phải trước khi inject sdk.js\n\tif (opts.sessionToken) seedSession(opts.sessionToken);\n\n\twindow.chatwootSettings = { position: 'right', type: 'standard', ...opts.settings };\n\tconst script = document.createElement('script');\n\tscript.id = 'chatwoot-sdk';\n\tscript.src = `${baseUrl}/packs/js/sdk.js`;\n\tscript.async = true;\n\tscript.onload = () => window.chatwootSDK?.run({ websiteToken: opts.websiteToken, baseUrl });\n\tdocument.body.appendChild(script);\n};\n\n/** Chạy callback khi widget sẵn sàng. Trả về hàm huỷ đăng ký. */\nexport const onChatwootReady = (callback: () => void) => {\n\tif (!isBrowser()) return () => {};\n\tif (window.$chatwoot?.hasLoaded) {\n\t\tcallback();\n\t\treturn () => {};\n\t}\n\twindow.addEventListener('chatwoot:ready', callback);\n\treturn () => window.removeEventListener('chatwoot:ready', callback);\n};\n\nexport const identifyChatwoot = (\n\tidentifier: string,\n\tuser: ChatwootUser,\n\tattributes?: Record<string, unknown>\n) => {\n\tconst cw = getChatwoot();\n\tif (!cw || !identifier) return;\n\tcw.setUser(identifier, user);\n\tif (attributes && Object.keys(attributes).length) cw.setCustomAttributes(attributes);\n};\n\nexport const openChat = () => getChatwoot()?.toggle('open');\nexport const closeChat = () => getChatwoot()?.toggle('close');\nexport const toggleChat = () => {\n\tconst cw = getChatwoot();\n\tif (cw) cw.toggle(cw.isOpen ? 'close' : 'open');\n};\nexport const setChatVisible = (visible: boolean) => getChatwoot()?.toggleBubbleVisibility(visible ? 'show' : 'hide');\nexport const setChatAttributes = (attrs: Record<string, unknown>) => getChatwoot()?.setCustomAttributes(attrs);\nexport const resetChatwoot = () => getChatwoot()?.reset();\n","import { useEffect, useRef } from 'react';\nimport { identifyChatwoot, loadChatwoot, onChatwootReady } from './client';\nimport type { ChatwootBootData, ChatwootSettings, ChatwootUser } from './types';\n\n\n\nexport type UseChatwootOptions = {\n\t/** Từ server: createChatwootServer(...).getBootData(shop) */\n\tboot: ChatwootBootData | null | undefined;\n\tuser?: Omit<ChatwootUser, 'identifier_hash'>;\n\tattributes?: Record<string, unknown>;\n\t/** Endpoint lưu init_session. Mặc định /api/chatwoot-session. false = tắt */\n\tsessionEndpoint?: string | false;\n\tsettings?: ChatwootSettings;\n\t/** true = không load widget (vd: support agent) */\n\tdisabled?: boolean;\n};\n\nexport function useChatwoot({\n\tboot,\n\tuser,\n\tattributes,\n\tsessionEndpoint = '/api/chatwoot-session',\n\tsettings,\n\tdisabled,\n}: UseChatwootOptions) {\n\tconst loaded = useRef(false);\n\n\tuseEffect(() => {\n\t\tif (disabled || !boot || loaded.current) return;\n\t\tloaded.current = true;\n\t\tloadChatwoot({\n\t\t\tbaseUrl: boot.baseUrl,\n\t\t\twebsiteToken: boot.websiteToken,\n\t\t\tsessionToken: boot.initSession,\n\t\t\tsessionEndpoint: sessionEndpoint || undefined,\n\t\t\tsettings,\n\t\t});\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [disabled, boot?.baseUrl, boot?.websiteToken]);\n\n\tconst userKey = JSON.stringify(user ?? {});\n\tconst attrKey = JSON.stringify(attributes ?? {});\n\n\tuseEffect(() => {\n\t\tif (disabled || !boot?.identifier) return;\n\t\treturn onChatwootReady(() =>\n\t\t\tidentifyChatwoot(boot.identifier, { ...user, identifier_hash: boot.identifierHash || undefined }, attributes)\n\t\t);\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [disabled, boot?.identifier, boot?.identifierHash, userKey, attrKey]);\n}\n\n/** Component tiện dụng: <ChatwootWidget boot={...} user={...} /> */\nexport function ChatwootWidget(props: UseChatwootOptions) {\n\tuseChatwoot(props);\n\treturn null;\n}\n","// Server-side helpers (gọi trong loader/action). Dùng Web Crypto -> không phụ thuộc node:crypto,\n// nên package chỉ cần 1 entry. apiToken/hmacToken chỉ được truyền ở code server (.server.ts).\nimport type { ChatwootBootData } from './types';\n\nexport type ChatwootServerConfig = {\n\tbaseUrl: string;\n\twebsiteToken: string;\n\t/** Access Token của agent dùng cho API (nên tạo agent riêng) */\n\tapiToken: string;\n\taccountId?: number | string;\n\t/** Inbox → Configuration → Identity Validation */\n\thmacToken?: string;\n\t/** Tên custom attribute lưu phiên. Mặc định: init_session */\n\tsessionAttribute?: string;\n\ttimeoutMs?: number;\n\t/** Cache init_session trong RAM theo identifier. Mặc định 10 phút. 0 = tắt */\n\tcacheTtlMs?: number;\n\tlogger?: Pick<Console, 'error'>;\n};\n\ntype CwContact = {\n\tid: number;\n\tidentifier: string | null;\n\tcustom_attributes?: Record<string, unknown>;\n\tcontact_inboxes?: { source_id: string }[];\n};\n\ntype JwtPayload = { source_id?: string; inbox_id?: number; exp?: number };\n\nexport type SaveResult = {\n\tok: boolean;\n\treason: 'saved' | 'unchanged' | 'invalid_token' | 'contact_not_found' | 'token_not_owned' | 'update_failed';\n};\n\nconst decodeJwt = (token: string): JwtPayload | null => {\n\ttry {\n\t\tconst b64 = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');\n\t\tconst json = decodeURIComponent(\n\t\t\tatob(b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '='))\n\t\t\t\t.split('')\n\t\t\t\t.map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))\n\t\t\t\t.join('')\n\t\t);\n\t\treturn JSON.parse(json);\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst hmacSha256Hex = async (key: string, message: string) => {\n\tconst enc = new TextEncoder();\n\tconst k = await globalThis.crypto.subtle.importKey('raw', enc.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);\n\tconst sig = await globalThis.crypto.subtle.sign('HMAC', k, enc.encode(message));\n\treturn Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, '0')).join('');\n};\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport function createChatwootServer(config: ChatwootServerConfig) {\n\tconst base = config.baseUrl.replace(/\\/$/, '');\n\tconst account = config.accountId ?? 1;\n\tconst attr = config.sessionAttribute ?? 'init_session';\n\tconst timeout = config.timeoutMs ?? 5000;\n\tconst log = config.logger ?? console;\n\tconst ttl = config.cacheTtlMs ?? 10 * 60_000;\n\tconst cache = new Map<string, { token: string | null; until: number }>();\n\tconst cacheSet = (id: string, token: string | null) => {\n\t\tif (!ttl) return;\n\t\tif (cache.size > 5000) cache.clear(); // chặn phình RAM\n\t\tcache.set(id, { token, until: Date.now() + ttl });\n\t};\n\n\tconst api = async <T>(path: string, init: RequestInit = {}): Promise<T | null> => {\n\t\ttry {\n\t\t\tconst res = await fetch(`${base}/api/v1/accounts/${account}${path}`, {\n\t\t\t\t...init,\n\t\t\t\theaders: { 'Content-Type': 'application/json', api_access_token: config.apiToken, ...(init.headers ?? {}) },\n\t\t\t\tsignal: AbortSignal.timeout(timeout),\n\t\t\t});\n\t\t\tif (!res.ok) {\n\t\t\t\tlog.error(`[chatwoot] ${init.method ?? 'GET'} ${path} -> ${res.status}`);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn (await res.json()) as T;\n\t\t} catch (e) {\n\t\t\tlog.error(`[chatwoot] ${path}: ${(e as Error).message}`);\n\t\t\treturn null;\n\t\t}\n\t};\n\n\tconst identifierHash = async (identifier: string) =>\n\t\tconfig.hmacToken && identifier ? hmacSha256Hex(config.hmacToken, identifier) : '';\n\n\tconst isUsableToken = (token: string) => {\n\t\tconst p = decodeJwt(token);\n\t\treturn !!p?.source_id && (!p.exp || p.exp * 1000 > Date.now() + 86_400_000);\n\t};\n\n\tconst findContact = async (identifier: string) => {\n\t\tconst data = await api<{ payload: CwContact[] }>(`/contacts/search?q=${encodeURIComponent(identifier)}`);\n\t\treturn data?.payload?.find((c) => c.identifier === identifier) ?? null;\n\t};\n\n\tconst getInitSession = async (identifier: string) => {\n\t\tconst hit = cache.get(identifier);\n\t\tif (hit && hit.until > Date.now() && (!hit.token || isUsableToken(hit.token))) return hit.token;\n\n\t\tconst raw = (await findContact(identifier))?.custom_attributes?.[attr];\n\t\tconst token = typeof raw === 'string' && isUsableToken(raw) ? raw : null;\n\t\tif (token) cacheSet(identifier, token); // không cache null: shop mới sẽ có token ngay sau lần chat đầu\n\t\treturn token;\n\t};\n\n\tconst saveInitSession = async (identifier: string, token: string): Promise<SaveResult> => {\n\t\tconst payload = decodeJwt(token);\n\t\tif (!payload?.source_id) return { ok: false, reason: 'invalid_token' };\n\n\t\tlet contact: CwContact | null = null;\n\t\tfor (let i = 0; i < 6 && !contact; i++) {\n\t\t\tif (i) await sleep(1000);\n\t\t\tcontact = await findContact(identifier);\n\t\t}\n\t\tif (!contact) return { ok: false, reason: 'contact_not_found' };\n\n\t\tconst full = await api<{ payload: CwContact }>(`/contacts/${contact.id}`);\n\t\tconst sources = full?.payload?.contact_inboxes?.map((ci) => ci.source_id);\n\t\tif (sources && !sources.includes(payload.source_id)) return { ok: false, reason: 'token_not_owned' };\n\t\tif (contact.custom_attributes?.[attr] === token) {\n\t\t\tcacheSet(identifier, token);\n\t\t\treturn { ok: true, reason: 'unchanged' };\n\t\t}\n\n\t\tconst updated = await api(`/contacts/${contact.id}`, {\n\t\t\tmethod: 'PUT',\n\t\t\tbody: JSON.stringify({ custom_attributes: { ...(contact.custom_attributes ?? {}), [attr]: token } }),\n\t\t});\n\t\tif (updated) cacheSet(identifier, token);\n\t\treturn updated ? { ok: true, reason: 'saved' } : { ok: false, reason: 'update_failed' };\n\t};\n\n\t/** Gọi trong loader, trả về cho client */\n\tconst getBootData = async (identifier: string): Promise<ChatwootBootData> => ({\n\t\tbaseUrl: base,\n\t\twebsiteToken: config.websiteToken,\n\t\tidentifier,\n\t\tidentifierHash: await identifierHash(identifier),\n\t\tinitSession: await getInitSession(identifier),\n\t});\n\n\t/** Dùng trong route action: return chatwoot.handleSessionRequest(request, session.shop) */\n\tconst handleSessionRequest = async (request: Request, identifier: string) => {\n\t\tconst body = (await request.json().catch(() => ({}))) as { token?: unknown };\n\t\tif (typeof body.token !== 'string' || body.token.split('.').length !== 3) {\n\t\t\treturn Response.json({ ok: false, reason: 'invalid_token' }, { status: 400 });\n\t\t}\n\t\tconst result = await saveInitSession(identifier, body.token);\n\t\treturn Response.json(result, { status: result.ok ? 200 : 422 });\n\t};\n\n\tconst clearCache = (identifier?: string) => (identifier ? cache.delete(identifier) : cache.clear());\n\n\treturn { identifierHash, findContact, clearCache, getInitSession, saveInitSession, getBootData, handleSessionRequest };\n}\n\nexport type ChatwootServer = ReturnType<typeof createChatwootServer>;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/react.ts","../src/server.ts"],"sourcesContent":["// Client: chạy trên trình duyệt\r\nimport type { ChatwootInstance, ChatwootSettings, ChatwootUser } from './types';\r\n\r\n\r\nconst STORE_PREFIX = 'cwcookie:';\r\nconst CW_COOKIE_RE = /^\\s*(cw_conversation|cw_user_[A-Za-z0-9]+)=([^;]*)/;\r\n\r\nlet shimInstalled = false;\r\nlet tokenListener: ((token: string) => void) | undefined;\r\n\r\nconst isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';\r\n\r\nconst storage = {\r\n\tget(k: string) {\r\n\t\ttry { return localStorage.getItem(k); } catch { return null; }\r\n\t},\r\n\tset(k: string, v: string) {\r\n\t\ttry { localStorage.setItem(k, v); } catch { /* blocked */ }\r\n\t},\r\n\tremove(k: string) {\r\n\t\ttry { localStorage.removeItem(k); } catch { /* blocked */ }\r\n\t},\r\n\tkeys(prefix: string) {\r\n\t\tconst out: string[] = [];\r\n\t\ttry {\r\n\t\t\tfor (let i = 0; i < localStorage.length; i++) {\r\n\t\t\t\tconst k = localStorage.key(i);\r\n\t\t\t\tif (k?.startsWith(prefix)) out.push(k.slice(prefix.length));\r\n\t\t\t}\r\n\t\t} catch { /* blocked */ }\r\n\t\treturn out;\r\n\t},\r\n};\r\n\r\nexport const isInIframe = () => {\r\n\tif (!isBrowser()) return false;\r\n\ttry {\r\n\t\treturn window.self !== window.top;\r\n\t} catch {\r\n\t\treturn true;\r\n\t}\r\n};\r\n\r\n/**\r\n * Trong iframe khác site (Shopify admin), cookie SameSite=Lax của SDK bị chặn -> mỗi lần F5 là phiên mới.\r\n * Shim document.cookie cho key cw_*: ghi -> localStorage, đọc -> từ localStorage.\r\n */\r\nexport const installCookieShim = () => {\r\n\tif (!isBrowser() || shimInstalled || !isInIframe()) return;\r\n\tconst native = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');\r\n\tif (!native?.get || !native?.set) return;\r\n\tconst nGet = native.get;\r\n\tconst nSet = native.set;\r\n\r\n\tObject.defineProperty(document, 'cookie', {\r\n\t\tconfigurable: true,\r\n\t\tget() {\r\n\t\t\tlet value: string = nGet.call(document);\r\n\t\t\tfor (const name of storage.keys(STORE_PREFIX)) {\r\n\t\t\t\tif (new RegExp(`(^|;\\\\s*)${name}=`).test(value)) continue;\r\n\t\t\t\tconst v = storage.get(STORE_PREFIX + name);\r\n\t\t\t\tif (v) value = value ? `${value}; ${name}=${v}` : `${name}=${v}`;\r\n\t\t\t}\r\n\t\t\treturn value;\r\n\t\t},\r\n\t\tset(raw: string) {\r\n\t\t\tconst m = CW_COOKIE_RE.exec(raw);\r\n\t\t\tif (m) {\r\n\t\t\t\tconst [, name, val] = m;\r\n\t\t\t\tconst expired = !val || /expires=Thu, 01 Jan 1970/i.test(raw) || /max-age=(0|-)/i.test(raw);\r\n\t\t\t\tif (expired) {\r\n\t\t\t\t\tstorage.remove(STORE_PREFIX + name);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst changed = storage.get(STORE_PREFIX + name) !== val;\r\n\t\t\t\t\tstorage.set(STORE_PREFIX + name, val);\r\n\t\t\t\t\tif (changed && name === 'cw_conversation') tokenListener?.(val);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tnSet.call(document, raw);\r\n\t\t},\r\n\t});\r\n\tshimInstalled = true;\r\n};\r\n\r\nconst seedSession = (token: string) => {\r\n\tif (isInIframe()) storage.set(`${STORE_PREFIX}cw_conversation`, token);\r\n\telse document.cookie = `cw_conversation=${token}; path=/; max-age=31536000; SameSite=Lax`;\r\n};\r\n\r\nconst readCurrentSession = (): string | null => {\r\n\tconst stored = storage.get(`${STORE_PREFIX}cw_conversation`);\r\n\tif (stored) return stored;\r\n\treturn /(?:^|;\\s*)cw_conversation=([^;]+)/.exec(document.cookie)?.[1] ?? null;\r\n};\r\n\r\nexport type LoadChatwootOptions = {\r\n\tbaseUrl: string;\r\n\twebsiteToken: string;\r\n\t/** Token phiên đã lưu (init_session) -> mở lại đúng phiên/cuộc chat cũ */\r\n\tsessionToken?: string | null;\r\n\t/** Gọi khi Chatwoot cấp token phiên mới */\r\n\tonSessionToken?: (token: string) => void;\r\n\t/** Nếu set, tự POST { token } tới endpoint này khi có token mới (vd: /api/chatwoot-session) */\r\n\tsessionEndpoint?: string;\r\n\tsettings?: ChatwootSettings;\r\n};\r\n\r\nexport const getChatwoot = (): ChatwootInstance | undefined => (isBrowser() ? window.$chatwoot : undefined);\r\n\r\nexport const loadChatwoot = (opts: LoadChatwootOptions) => {\r\n\tif (!isBrowser() || !opts.baseUrl || !opts.websiteToken) return;\r\n\tif (window.chatwootSDK || document.getElementById('chatwoot-sdk')) return;\r\n\r\n\tconst baseUrl = opts.baseUrl.replace(/\\/$/, '');\r\n\ttokenListener = (token) => {\r\n\t\topts.onSessionToken?.(token);\r\n\t\tif (opts.sessionEndpoint) {\r\n\t\t\tfetch(opts.sessionEndpoint, {\r\n\t\t\t\tmethod: 'POST',\r\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\r\n\t\t\t\tbody: JSON.stringify({ token }),\r\n\t\t\t}).catch(() => {});\r\n\t\t}\r\n\t};\r\n\r\n\tinstallCookieShim(); // phải trước khi inject sdk.js\r\n\tif (opts.sessionToken) {\r\n\t\tseedSession(opts.sessionToken);\r\n\t} else {\r\n\t\t// Server chưa có init_session (shop mới / admin xoá) nhưng browser còn phiên cũ:\r\n\t\t// cookie không đổi nên listener sẽ không tự chạy -> đẩy lên server ngay để lưu lại.\r\n\t\tconst current = readCurrentSession();\r\n\t\tif (current) tokenListener(current);\r\n\t}\r\n\r\n\twindow.chatwootSettings = { position: 'right', type: 'standard', ...opts.settings };\r\n\tconst script = document.createElement('script');\r\n\tscript.id = 'chatwoot-sdk';\r\n\tscript.src = `${baseUrl}/packs/js/sdk.js`;\r\n\tscript.async = true;\r\n\tscript.onload = () => window.chatwootSDK?.run({ websiteToken: opts.websiteToken, baseUrl });\r\n\tdocument.body.appendChild(script);\r\n};\r\n\r\n/** Chạy callback khi widget sẵn sàng. Trả về hàm huỷ đăng ký. */\r\nexport const onChatwootReady = (callback: () => void) => {\r\n\tif (!isBrowser()) return () => {};\r\n\tif (window.$chatwoot?.hasLoaded) {\r\n\t\tcallback();\r\n\t\treturn () => {};\r\n\t}\r\n\twindow.addEventListener('chatwoot:ready', callback);\r\n\treturn () => window.removeEventListener('chatwoot:ready', callback);\r\n};\r\n\r\nexport const identifyChatwoot = (\r\n\tidentifier: string,\r\n\tuser: ChatwootUser,\r\n\tattributes?: Record<string, unknown>\r\n) => {\r\n\tconst cw = getChatwoot();\r\n\tif (!cw || !identifier) return;\r\n\ttry {\r\n\t\tcw.setUser(identifier, user);\r\n\t} catch {\r\n\t\treturn; // SDK ném lỗi khi user không có trường nào hợp lệ\r\n\t}\r\n\tif (attributes && Object.keys(attributes).length) cw.setCustomAttributes(attributes);\r\n};\r\n\r\nexport const openChat = () => getChatwoot()?.toggle('open');\r\nexport const closeChat = () => getChatwoot()?.toggle('close');\r\nexport const toggleChat = () => {\r\n\tconst cw = getChatwoot();\r\n\tif (cw) cw.toggle(cw.isOpen ? 'close' : 'open');\r\n};\r\nexport const setChatVisible = (visible: boolean) => getChatwoot()?.toggleBubbleVisibility(visible ? 'show' : 'hide');\r\nexport const setChatAttributes = (attrs: Record<string, unknown>) => getChatwoot()?.setCustomAttributes(attrs);\r\nexport const resetChatwoot = () => getChatwoot()?.reset();\r\n","import { useEffect, useRef } from 'react';\r\nimport { identifyChatwoot, loadChatwoot, onChatwootReady } from './client';\r\nimport type { ChatwootBootData, ChatwootSettings, ChatwootUser } from './types';\r\n\r\n\r\n\r\nconst hasUserFields = (u?: object) => !!u && Object.values(u).some(Boolean);\r\n\r\nexport type UseChatwootOptions = {\r\n\t/** Từ server: createChatwootServer(...).getBootData(shop) */\r\n\tboot: ChatwootBootData | null | undefined;\r\n\tuser?: Omit<ChatwootUser, 'identifier_hash'>;\r\n\tattributes?: Record<string, unknown>;\r\n\t/** Endpoint lưu init_session. Mặc định /api/chatwoot-session. false = tắt */\r\n\tsessionEndpoint?: string | false;\r\n\tsettings?: ChatwootSettings;\r\n\t/** true = không load widget (vd: support agent) */\r\n\tdisabled?: boolean;\r\n};\r\n\r\nexport function useChatwoot({\r\n\tboot,\r\n\tuser,\r\n\tattributes,\r\n\tsessionEndpoint = '/api/chatwoot-session',\r\n\tsettings,\r\n\tdisabled,\r\n}: UseChatwootOptions) {\r\n\tconst loaded = useRef(false);\r\n\r\n\tuseEffect(() => {\r\n\t\tif (disabled || !boot || loaded.current) return;\r\n\t\tloaded.current = true;\r\n\t\tloadChatwoot({\r\n\t\t\tbaseUrl: boot.baseUrl,\r\n\t\t\twebsiteToken: boot.websiteToken,\r\n\t\t\tsessionToken: boot.initSession,\r\n\t\t\tsessionEndpoint: sessionEndpoint || undefined,\r\n\t\t\tsettings,\r\n\t\t});\r\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\r\n\t}, [disabled, boot?.baseUrl, boot?.websiteToken]);\r\n\r\n\tconst userKey = JSON.stringify(user ?? {});\r\n\tconst attrKey = JSON.stringify(attributes ?? {});\r\n\r\n\tuseEffect(() => {\r\n\t\tif (disabled || !boot?.identifier || !hasUserFields(user)) return;\r\n\t\treturn onChatwootReady(() =>\r\n\t\t\tidentifyChatwoot(boot.identifier, { ...user, identifier_hash: boot.identifierHash || undefined }, attributes)\r\n\t\t);\r\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\r\n\t}, [disabled, boot?.identifier, boot?.identifierHash, userKey, attrKey]);\r\n}\r\n\r\n/** Component tiện dụng: <ChatwootWidget boot={...} user={...} /> */\r\nexport function ChatwootWidget(props: UseChatwootOptions) {\r\n\tuseChatwoot(props);\r\n\treturn null;\r\n}\r\n","// Server-side helpers (gọi trong loader/action). Dùng Web Crypto -> không phụ thuộc node:crypto,\r\n// nên package chỉ cần 1 entry. apiToken/hmacToken chỉ được truyền ở code server (.server.ts).\r\nimport type { ChatwootBootData } from './types';\r\n\r\nexport type ChatwootServerConfig = {\r\n\tbaseUrl: string;\r\n\twebsiteToken: string;\r\n\t/** Access Token của agent dùng cho API (nên tạo agent riêng) */\r\n\tapiToken: string;\r\n\taccountId?: number | string;\r\n\t/** Inbox → Configuration → Identity Validation */\r\n\thmacToken?: string;\r\n\t/** Tên custom attribute lưu phiên. Mặc định: init_session */\r\n\tsessionAttribute?: string;\r\n\ttimeoutMs?: number;\r\n\t/** Cache init_session trong RAM theo identifier. Mặc định 10 phút. 0 = tắt */\r\n\tcacheTtlMs?: number;\r\n\tlogger?: Pick<Console, 'error'>;\r\n};\r\n\r\ntype CwContact = {\r\n\tid: number;\r\n\tidentifier: string | null;\r\n\tcustom_attributes?: Record<string, unknown>;\r\n\tcontact_inboxes?: { source_id: string }[];\r\n};\r\n\r\ntype JwtPayload = { source_id?: string; inbox_id?: number; exp?: number };\r\n\r\nexport type SaveResult = {\r\n\tok: boolean;\r\n\treason: 'saved' | 'unchanged' | 'invalid_token' | 'contact_not_found' | 'token_not_owned' | 'update_failed';\r\n};\r\n\r\nconst decodeJwtPart = (part: string): Record<string, unknown> | null => {\r\n\ttry {\r\n\t\tconst b64 = part.replace(/-/g, '+').replace(/_/g, '/');\r\n\t\tconst json = decodeURIComponent(\r\n\t\t\tatob(b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '='))\r\n\t\t\t\t.split('')\r\n\t\t\t\t.map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))\r\n\t\t\t\t.join('')\r\n\t\t);\r\n\t\tconst obj = JSON.parse(json);\r\n\t\treturn obj && typeof obj === 'object' ? obj : null;\r\n\t} catch {\r\n\t\treturn null;\r\n\t}\r\n};\r\n\r\n// Chỉ đọc (không verify chữ ký): kiểm header là JWT thật (có alg) rồi mới tin payload.\r\nconst decodeJwt = (token: string): JwtPayload | null => {\r\n\tconst [h, p] = token.split('.');\r\n\tif (!h || !p || typeof decodeJwtPart(h)?.alg !== 'string') return null;\r\n\treturn decodeJwtPart(p) as JwtPayload | null;\r\n};\r\n\r\nconst hmacSha256Hex = async (key: string, message: string) => {\r\n\tconst enc = new TextEncoder();\r\n\tconst k = await globalThis.crypto.subtle.importKey('raw', enc.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);\r\n\tconst sig = await globalThis.crypto.subtle.sign('HMAC', k, enc.encode(message));\r\n\treturn Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, '0')).join('');\r\n};\r\n\r\nconst NULL_TTL_MS = 60_000;\r\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\r\n\r\nexport function createChatwootServer(config: ChatwootServerConfig) {\r\n\tconst base = config.baseUrl.replace(/\\/$/, '');\r\n\tconst account = config.accountId ?? 1;\r\n\tconst attr = config.sessionAttribute ?? 'init_session';\r\n\tconst timeout = config.timeoutMs ?? 5000;\r\n\tconst log = config.logger ?? console;\r\n\tconst ttl = config.cacheTtlMs ?? 10 * 60_000;\r\n\tconst cache = new Map<string, { token: string | null; until: number }>();\r\n\tconst cacheSet = (id: string, token: string | null) => {\r\n\t\tif (!ttl) return;\r\n\t\tif (cache.size > 5000) cache.clear(); // chặn phình RAM\r\n\t\t// \"chưa có phiên\" chỉ cache ngắn để shop vừa chat xong sớm nhận được token\r\n\t\tcache.set(id, { token, until: Date.now() + (token ? ttl : Math.min(ttl, NULL_TTL_MS)) });\r\n\t};\r\n\r\n\tconst api = async <T>(path: string, init: RequestInit = {}): Promise<T | null> => {\r\n\t\ttry {\r\n\t\t\tconst res = await fetch(`${base}/api/v1/accounts/${account}${path}`, {\r\n\t\t\t\t...init,\r\n\t\t\t\theaders: { 'Content-Type': 'application/json', api_access_token: config.apiToken, ...(init.headers ?? {}) },\r\n\t\t\t\tsignal: AbortSignal.timeout(timeout),\r\n\t\t\t});\r\n\t\t\tif (!res.ok) {\r\n\t\t\t\tlog.error(`[chatwoot] ${init.method ?? 'GET'} ${path} -> ${res.status}`);\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t\treturn (await res.json()) as T;\r\n\t\t} catch (e) {\r\n\t\t\tlog.error(`[chatwoot] ${path}: ${(e as Error).message}`);\r\n\t\t\treturn null;\r\n\t\t}\r\n\t};\r\n\r\n\tconst identifierHash = async (identifier: string) =>\r\n\t\tconfig.hmacToken && identifier ? hmacSha256Hex(config.hmacToken, identifier) : '';\r\n\r\n\tconst isUsableToken = (token: string) => {\r\n\t\tconst p = decodeJwt(token);\r\n\t\treturn !!p?.source_id && (!p.exp || p.exp * 1000 > Date.now() + 86_400_000);\r\n\t};\r\n\r\n\tconst findContact = async (identifier: string) => {\r\n\t\tconst data = await api<{ payload: CwContact[] }>(`/contacts/search?q=${encodeURIComponent(identifier)}`);\r\n\t\treturn data?.payload?.find((c) => c.identifier === identifier) ?? null;\r\n\t};\r\n\r\n\tconst getInitSession = async (identifier: string) => {\r\n\t\tconst hit = cache.get(identifier);\r\n\t\tif (hit && hit.until > Date.now() && (!hit.token || isUsableToken(hit.token))) return hit.token;\r\n\r\n\t\tconst raw = (await findContact(identifier))?.custom_attributes?.[attr];\r\n\t\tconst token = typeof raw === 'string' && isUsableToken(raw) ? raw : null;\r\n\t\tcacheSet(identifier, token);\r\n\t\treturn token;\r\n\t};\r\n\r\n\tconst saveInitSession = async (identifier: string, token: string): Promise<SaveResult> => {\r\n\t\tconst payload = decodeJwt(token);\r\n\t\tif (!payload?.source_id) return { ok: false, reason: 'invalid_token' };\r\n\r\n\t\tlet contact: CwContact | null = null;\r\n\t\tfor (let i = 0; i < 6 && !contact; i++) {\r\n\t\t\tif (i) await sleep(1000);\r\n\t\t\tcontact = await findContact(identifier);\r\n\t\t}\r\n\t\tif (!contact) return { ok: false, reason: 'contact_not_found' };\r\n\r\n\t\t// contact_inboxes có thể chưa kịp cập nhật source_id của token mới -> thử lại vài lần\r\n\t\tlet owned = false;\r\n\t\tfor (let i = 0; i < 3 && !owned; i++) {\r\n\t\t\tif (i) await sleep(1500);\r\n\t\t\tconst full = await api<{ payload: CwContact }>(`/contacts/${contact.id}`);\r\n\t\t\tconst sources = full?.payload?.contact_inboxes?.map((ci) => ci.source_id);\r\n\t\t\towned = !sources || sources.includes(payload.source_id);\r\n\t\t}\r\n\t\tif (!owned) return { ok: false, reason: 'token_not_owned' };\r\n\t\tif (contact.custom_attributes?.[attr] === token) {\r\n\t\t\tcacheSet(identifier, token);\r\n\t\t\treturn { ok: true, reason: 'unchanged' };\r\n\t\t}\r\n\r\n\t\tconst updated = await api(`/contacts/${contact.id}`, {\r\n\t\t\tmethod: 'PUT',\r\n\t\t\tbody: JSON.stringify({ custom_attributes: { ...(contact.custom_attributes ?? {}), [attr]: token } }),\r\n\t\t});\r\n\t\tif (updated) cacheSet(identifier, token);\r\n\t\treturn updated ? { ok: true, reason: 'saved' } : { ok: false, reason: 'update_failed' };\r\n\t};\r\n\r\n\t/** Gọi trong loader, trả về cho client */\r\n\tconst getBootData = async (identifier: string): Promise<ChatwootBootData> => ({\r\n\t\tbaseUrl: base,\r\n\t\twebsiteToken: config.websiteToken,\r\n\t\tidentifier,\r\n\t\tidentifierHash: await identifierHash(identifier),\r\n\t\tinitSession: await getInitSession(identifier),\r\n\t});\r\n\r\n\t/** Dùng trong route action: return chatwoot.handleSessionRequest(request, session.shop) */\r\n\tconst handleSessionRequest = async (request: Request, identifier: string) => {\r\n\t\tconst body = (await request.json().catch(() => ({}))) as { token?: unknown };\r\n\t\tif (typeof body.token !== 'string' || body.token.split('.').length !== 3) {\r\n\t\t\treturn Response.json({ ok: false, reason: 'invalid_token' }, { status: 400 });\r\n\t\t}\r\n\t\tconst result = await saveInitSession(identifier, body.token);\r\n\t\treturn Response.json(result, { status: result.ok ? 200 : 422 });\r\n\t};\r\n\r\n\tconst clearCache = (identifier?: string) => (identifier ? cache.delete(identifier) : cache.clear());\r\n\r\n\treturn { identifierHash, findContact, clearCache, getInitSession, saveInitSession, getBootData, handleSessionRequest };\r\n}\r\n\r\nexport type ChatwootServer = ReturnType<typeof createChatwootServer>;\r\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fstacksoftware/chatwoot-embed",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Chatwoot widget for Shopify embedded apps: persistent session in iframe, HMAC identity, 1 conversation per shop",
5
5
  "license": "MIT",
6
6
  "type": "module",