@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 +38 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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)
|
|
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
|
-
|
|
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
|
|
231
|
+
var decodeJwtPart = (part) => {
|
|
217
232
|
try {
|
|
218
|
-
const b64 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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" };
|
package/dist/index.cjs.map
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
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
|
|
191
|
+
var decodeJwtPart = (part) => {
|
|
177
192
|
try {
|
|
178
|
-
const b64 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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":";AAIA,IAAM,eAAe;AACrB,IAAM,eAAe;AAErB,IAAI,gBAAgB;AACpB,IAAI;AAEJ,IAAM,YAAY,MAAM,OAAO,WAAW,eAAe,OAAO,aAAa;AAE7E,IAAM,UAAU;AAAA,EACf,IAAI,GAAW;AACd,QAAI;AAAE,aAAO,aAAa,QAAQ,CAAC;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAM;AAAA,EAC9D;AAAA,EACA,IAAI,GAAW,GAAW;AACzB,QAAI;AAAE,mBAAa,QAAQ,GAAG,CAAC;AAAA,IAAG,QAAQ;AAAA,IAAgB;AAAA,EAC3D;AAAA,EACA,OAAO,GAAW;AACjB,QAAI;AAAE,mBAAa,WAAW,CAAC;AAAA,IAAG,QAAQ;AAAA,IAAgB;AAAA,EAC3D;AAAA,EACA,KAAK,QAAgB;AACpB,UAAM,MAAgB,CAAC;AACvB,QAAI;AACH,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC7C,cAAM,IAAI,aAAa,IAAI,CAAC;AAC5B,YAAI,GAAG,WAAW,MAAM,EAAG,KAAI,KAAK,EAAE,MAAM,OAAO,MAAM,CAAC;AAAA,MAC3D;AAAA,IACD,QAAQ;AAAA,IAAgB;AACxB,WAAO;AAAA,EACR;AACD;AAEO,IAAM,aAAa,MAAM;AAC/B,MAAI,CAAC,UAAU,EAAG,QAAO;AACzB,MAAI;AACH,WAAO,OAAO,SAAS,OAAO;AAAA,EAC/B,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAMO,IAAM,oBAAoB,MAAM;AACtC,MAAI,CAAC,UAAU,KAAK,iBAAiB,CAAC,WAAW,EAAG;AACpD,QAAM,SAAS,OAAO,yBAAyB,SAAS,WAAW,QAAQ;AAC3E,MAAI,CAAC,QAAQ,OAAO,CAAC,QAAQ,IAAK;AAClC,QAAM,OAAO,OAAO;AACpB,QAAM,OAAO,OAAO;AAEpB,SAAO,eAAe,UAAU,UAAU;AAAA,IACzC,cAAc;AAAA,IACd,MAAM;AACL,UAAI,QAAgB,KAAK,KAAK,QAAQ;AACtC,iBAAW,QAAQ,QAAQ,KAAK,YAAY,GAAG;AAC9C,YAAI,IAAI,OAAO,YAAY,IAAI,GAAG,EAAE,KAAK,KAAK,EAAG;AACjD,cAAM,IAAI,QAAQ,IAAI,eAAe,IAAI;AACzC,YAAI,EAAG,SAAQ,QAAQ,GAAG,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC;AAAA,MAC/D;AACA,aAAO;AAAA,IACR;AAAA,IACA,IAAI,KAAa;AAChB,YAAM,IAAI,aAAa,KAAK,GAAG;AAC/B,UAAI,GAAG;AACN,cAAM,CAAC,EAAE,MAAM,GAAG,IAAI;AACtB,cAAM,UAAU,CAAC,OAAO,4BAA4B,KAAK,GAAG,KAAK,iBAAiB,KAAK,GAAG;AAC1F,YAAI,SAAS;AACZ,kBAAQ,OAAO,eAAe,IAAI;AAAA,QACnC,OAAO;AACN,gBAAM,UAAU,QAAQ,IAAI,eAAe,IAAI,MAAM;AACrD,kBAAQ,IAAI,eAAe,MAAM,GAAG;AACpC,cAAI,WAAW,SAAS,kBAAmB,iBAAgB,GAAG;AAAA,QAC/D;AAAA,MACD;AACA,WAAK,KAAK,UAAU,GAAG;AAAA,IACxB;AAAA,EACD,CAAC;AACD,kBAAgB;AACjB;AAEA,IAAM,cAAc,CAAC,UAAkB;AACtC,MAAI,WAAW,EAAG,SAAQ,IAAI,GAAG,YAAY,mBAAmB,KAAK;AAAA,MAChE,UAAS,SAAS,mBAAmB,KAAK;AAChD;AAEA,IAAM,qBAAqB,MAAqB;AAC/C,QAAM,SAAS,QAAQ,IAAI,GAAG,YAAY,iBAAiB;AAC3D,MAAI,OAAQ,QAAO;AACnB,SAAO,oCAAoC,KAAK,SAAS,MAAM,IAAI,CAAC,KAAK;AAC1E;AAcO,IAAM,cAAc,MAAqC,UAAU,IAAI,OAAO,YAAY;AAE1F,IAAM,eAAe,CAAC,SAA8B;AAC1D,MAAI,CAAC,UAAU,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,aAAc;AACzD,MAAI,OAAO,eAAe,SAAS,eAAe,cAAc,EAAG;AAEnE,QAAM,UAAU,KAAK,QAAQ,QAAQ,OAAO,EAAE;AAC9C,kBAAgB,CAAC,UAAU;AAC1B,SAAK,iBAAiB,KAAK;AAC3B,QAAI,KAAK,iBAAiB;AACzB,YAAM,KAAK,iBAAiB;AAAA,QAC3B,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;AAAA,MAC/B,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAClB;AAAA,EACD;AAEA,oBAAkB;AAClB,MAAI,KAAK,cAAc;AACtB,gBAAY,KAAK,YAAY;AAAA,EAC9B,OAAO;AAGN,UAAM,UAAU,mBAAmB;AACnC,QAAI,QAAS,eAAc,OAAO;AAAA,EACnC;AAEA,SAAO,mBAAmB,EAAE,UAAU,SAAS,MAAM,YAAY,GAAG,KAAK,SAAS;AAClF,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,KAAK;AACZ,SAAO,MAAM,GAAG,OAAO;AACvB,SAAO,QAAQ;AACf,SAAO,SAAS,MAAM,OAAO,aAAa,IAAI,EAAE,cAAc,KAAK,cAAc,QAAQ,CAAC;AAC1F,WAAS,KAAK,YAAY,MAAM;AACjC;AAGO,IAAM,kBAAkB,CAAC,aAAyB;AACxD,MAAI,CAAC,UAAU,EAAG,QAAO,MAAM;AAAA,EAAC;AAChC,MAAI,OAAO,WAAW,WAAW;AAChC,aAAS;AACT,WAAO,MAAM;AAAA,IAAC;AAAA,EACf;AACA,SAAO,iBAAiB,kBAAkB,QAAQ;AAClD,SAAO,MAAM,OAAO,oBAAoB,kBAAkB,QAAQ;AACnE;AAEO,IAAM,mBAAmB,CAC/B,YACA,MACA,eACI;AACJ,QAAM,KAAK,YAAY;AACvB,MAAI,CAAC,MAAM,CAAC,WAAY;AACxB,MAAI;AACH,OAAG,QAAQ,YAAY,IAAI;AAAA,EAC5B,QAAQ;AACP;AAAA,EACD;AACA,MAAI,cAAc,OAAO,KAAK,UAAU,EAAE,OAAQ,IAAG,oBAAoB,UAAU;AACpF;AAEO,IAAM,WAAW,MAAM,YAAY,GAAG,OAAO,MAAM;AACnD,IAAM,YAAY,MAAM,YAAY,GAAG,OAAO,OAAO;AACrD,IAAM,aAAa,MAAM;AAC/B,QAAM,KAAK,YAAY;AACvB,MAAI,GAAI,IAAG,OAAO,GAAG,SAAS,UAAU,MAAM;AAC/C;AACO,IAAM,iBAAiB,CAAC,YAAqB,YAAY,GAAG,uBAAuB,UAAU,SAAS,MAAM;AAC5G,IAAM,oBAAoB,CAAC,UAAmC,YAAY,GAAG,oBAAoB,KAAK;AACtG,IAAM,gBAAgB,MAAM,YAAY,GAAG,MAAM;;;AClLxD,SAAS,WAAW,cAAc;AAMlC,IAAM,gBAAgB,CAAC,MAAe,CAAC,CAAC,KAAK,OAAO,OAAO,CAAC,EAAE,KAAK,OAAO;AAcnE,SAAS,YAAY;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB;AAAA,EACA;AACD,GAAuB;AACtB,QAAM,SAAS,OAAO,KAAK;AAE3B,YAAU,MAAM;AACf,QAAI,YAAY,CAAC,QAAQ,OAAO,QAAS;AACzC,WAAO,UAAU;AACjB,iBAAa;AAAA,MACZ,SAAS,KAAK;AAAA,MACd,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK;AAAA,MACnB,iBAAiB,mBAAmB;AAAA,MACpC;AAAA,IACD,CAAC;AAAA,EAEF,GAAG,CAAC,UAAU,MAAM,SAAS,MAAM,YAAY,CAAC;AAEhD,QAAM,UAAU,KAAK,UAAU,QAAQ,CAAC,CAAC;AACzC,QAAM,UAAU,KAAK,UAAU,cAAc,CAAC,CAAC;AAE/C,YAAU,MAAM;AACf,QAAI,YAAY,CAAC,MAAM,cAAc,CAAC,cAAc,IAAI,EAAG;AAC3D,WAAO;AAAA,MAAgB,MACtB,iBAAiB,KAAK,YAAY,EAAE,GAAG,MAAM,iBAAiB,KAAK,kBAAkB,OAAU,GAAG,UAAU;AAAA,IAC7G;AAAA,EAED,GAAG,CAAC,UAAU,MAAM,YAAY,MAAM,gBAAgB,SAAS,OAAO,CAAC;AACxE;AAGO,SAAS,eAAe,OAA2B;AACzD,cAAY,KAAK;AACjB,SAAO;AACR;;;ACzBA,IAAM,gBAAgB,CAAC,SAAiD;AACvE,MAAI;AACH,UAAM,MAAM,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACrD,UAAM,OAAO;AAAA,MACZ,KAAK,IAAI,OAAO,IAAI,UAAW,IAAK,IAAI,SAAS,KAAM,GAAI,GAAG,CAAC,EAC7D,MAAM,EAAE,EACR,IAAI,CAAC,MAAM,MAAM,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC9D,KAAK,EAAE;AAAA,IACV;AACA,UAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,WAAO,OAAO,OAAO,QAAQ,WAAW,MAAM;AAAA,EAC/C,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAGA,IAAM,YAAY,CAAC,UAAqC;AACvD,QAAM,CAAC,GAAG,CAAC,IAAI,MAAM,MAAM,GAAG;AAC9B,MAAI,CAAC,KAAK,CAAC,KAAK,OAAO,cAAc,CAAC,GAAG,QAAQ,SAAU,QAAO;AAClE,SAAO,cAAc,CAAC;AACvB;AAEA,IAAM,gBAAgB,OAAO,KAAa,YAAoB;AAC7D,QAAM,MAAM,IAAI,YAAY;AAC5B,QAAM,IAAI,MAAM,WAAW,OAAO,OAAO,UAAU,OAAO,IAAI,OAAO,GAAG,GAAG,EAAE,MAAM,QAAQ,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;AAC7H,QAAM,MAAM,MAAM,WAAW,OAAO,OAAO,KAAK,QAAQ,GAAG,IAAI,OAAO,OAAO,CAAC;AAC9E,SAAO,MAAM,KAAK,IAAI,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACvF;AAEA,IAAM,cAAc;AACpB,IAAM,QAAQ,CAAC,OAAe,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE3D,SAAS,qBAAqB,QAA8B;AAClE,QAAM,OAAO,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAC7C,QAAM,UAAU,OAAO,aAAa;AACpC,QAAM,OAAO,OAAO,oBAAoB;AACxC,QAAM,UAAU,OAAO,aAAa;AACpC,QAAM,MAAM,OAAO,UAAU;AAC7B,QAAM,MAAM,OAAO,cAAc,KAAK;AACtC,QAAM,QAAQ,oBAAI,IAAqD;AACvE,QAAM,WAAW,CAAC,IAAY,UAAyB;AACtD,QAAI,CAAC,IAAK;AACV,QAAI,MAAM,OAAO,IAAM,OAAM,MAAM;AAEnC,UAAM,IAAI,IAAI,EAAE,OAAO,OAAO,KAAK,IAAI,KAAK,QAAQ,MAAM,KAAK,IAAI,KAAK,WAAW,GAAG,CAAC;AAAA,EACxF;AAEA,QAAM,MAAM,OAAU,MAAc,OAAoB,CAAC,MAAyB;AACjF,QAAI;AACH,YAAM,MAAM,MAAM,MAAM,GAAG,IAAI,oBAAoB,OAAO,GAAG,IAAI,IAAI;AAAA,QACpE,GAAG;AAAA,QACH,SAAS,EAAE,gBAAgB,oBAAoB,kBAAkB,OAAO,UAAU,GAAI,KAAK,WAAW,CAAC,EAAG;AAAA,QAC1G,QAAQ,YAAY,QAAQ,OAAO;AAAA,MACpC,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACZ,YAAI,MAAM,cAAc,KAAK,UAAU,KAAK,IAAI,IAAI,OAAO,IAAI,MAAM,EAAE;AACvE,eAAO;AAAA,MACR;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACxB,SAAS,GAAG;AACX,UAAI,MAAM,cAAc,IAAI,KAAM,EAAY,OAAO,EAAE;AACvD,aAAO;AAAA,IACR;AAAA,EACD;AAEA,QAAM,iBAAiB,OAAO,eAC7B,OAAO,aAAa,aAAa,cAAc,OAAO,WAAW,UAAU,IAAI;AAEhF,QAAM,gBAAgB,CAAC,UAAkB;AACxC,UAAM,IAAI,UAAU,KAAK;AACzB,WAAO,CAAC,CAAC,GAAG,cAAc,CAAC,EAAE,OAAO,EAAE,MAAM,MAAO,KAAK,IAAI,IAAI;AAAA,EACjE;AAEA,QAAM,cAAc,OAAO,eAAuB;AACjD,UAAM,OAAO,MAAM,IAA8B,sBAAsB,mBAAmB,UAAU,CAAC,EAAE;AACvG,WAAO,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,eAAe,UAAU,KAAK;AAAA,EACnE;AAEA,QAAM,iBAAiB,OAAO,eAAuB;AACpD,UAAM,MAAM,MAAM,IAAI,UAAU;AAChC,QAAI,OAAO,IAAI,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI,SAAS,cAAc,IAAI,KAAK,GAAI,QAAO,IAAI;AAE1F,UAAM,OAAO,MAAM,YAAY,UAAU,IAAI,oBAAoB,IAAI;AACrE,UAAM,QAAQ,OAAO,QAAQ,YAAY,cAAc,GAAG,IAAI,MAAM;AACpE,aAAS,YAAY,KAAK;AAC1B,WAAO;AAAA,EACR;AAEA,QAAM,kBAAkB,OAAO,YAAoB,UAAuC;AACzF,UAAM,UAAU,UAAU,KAAK;AAC/B,QAAI,CAAC,SAAS,UAAW,QAAO,EAAE,IAAI,OAAO,QAAQ,gBAAgB;AAErE,QAAI,UAA4B;AAChC,aAAS,IAAI,GAAG,IAAI,KAAK,CAAC,SAAS,KAAK;AACvC,UAAI,EAAG,OAAM,MAAM,GAAI;AACvB,gBAAU,MAAM,YAAY,UAAU;AAAA,IACvC;AACA,QAAI,CAAC,QAAS,QAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AAG9D,QAAI,QAAQ;AACZ,aAAS,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO,KAAK;AACrC,UAAI,EAAG,OAAM,MAAM,IAAI;AACvB,YAAM,OAAO,MAAM,IAA4B,aAAa,QAAQ,EAAE,EAAE;AACxE,YAAM,UAAU,MAAM,SAAS,iBAAiB,IAAI,CAAC,OAAO,GAAG,SAAS;AACxE,cAAQ,CAAC,WAAW,QAAQ,SAAS,QAAQ,SAAS;AAAA,IACvD;AACA,QAAI,CAAC,MAAO,QAAO,EAAE,IAAI,OAAO,QAAQ,kBAAkB;AAC1D,QAAI,QAAQ,oBAAoB,IAAI,MAAM,OAAO;AAChD,eAAS,YAAY,KAAK;AAC1B,aAAO,EAAE,IAAI,MAAM,QAAQ,YAAY;AAAA,IACxC;AAEA,UAAM,UAAU,MAAM,IAAI,aAAa,QAAQ,EAAE,IAAI;AAAA,MACpD,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,mBAAmB,EAAE,GAAI,QAAQ,qBAAqB,CAAC,GAAI,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC;AAAA,IACpG,CAAC;AACD,QAAI,QAAS,UAAS,YAAY,KAAK;AACvC,WAAO,UAAU,EAAE,IAAI,MAAM,QAAQ,QAAQ,IAAI,EAAE,IAAI,OAAO,QAAQ,gBAAgB;AAAA,EACvF;AAGA,QAAM,cAAc,OAAO,gBAAmD;AAAA,IAC7E,SAAS;AAAA,IACT,cAAc,OAAO;AAAA,IACrB;AAAA,IACA,gBAAgB,MAAM,eAAe,UAAU;AAAA,IAC/C,aAAa,MAAM,eAAe,UAAU;AAAA,EAC7C;AAGA,QAAM,uBAAuB,OAAO,SAAkB,eAAuB;AAC5E,UAAM,OAAQ,MAAM,QAAQ,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACnD,QAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,GAAG,EAAE,WAAW,GAAG;AACzE,aAAO,SAAS,KAAK,EAAE,IAAI,OAAO,QAAQ,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7E;AACA,UAAM,SAAS,MAAM,gBAAgB,YAAY,KAAK,KAAK;AAC3D,WAAO,SAAS,KAAK,QAAQ,EAAE,QAAQ,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,EAC/D;AAEA,QAAM,aAAa,CAAC,eAAyB,aAAa,MAAM,OAAO,UAAU,IAAI,MAAM,MAAM;AAEjG,SAAO,EAAE,gBAAgB,aAAa,YAAY,gBAAgB,iBAAiB,aAAa,qBAAqB;AACtH;","names":[]}
|
package/package.json
CHANGED