@paramms/chat-widget 1.0.30 → 1.0.31

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.
@@ -0,0 +1,176 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Relay Chat Widget</title>
7
+ <style>
8
+ * { box-sizing: border-box; margin: 0; padding: 0; }
9
+ html, body { height: 100%; }
10
+ body {
11
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
12
+ min-height: 100vh;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ padding: 24px;
17
+ }
18
+
19
+ /* Marketing mode (no profileId in URL): purple gradient + info column */
20
+ body.marketing {
21
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
22
+ }
23
+ .container {
24
+ max-width: 900px;
25
+ width: 100%;
26
+ display: flex;
27
+ gap: 48px;
28
+ align-items: flex-start;
29
+ }
30
+ .info { flex: 1; color: #fff; }
31
+ .info h1 { font-size: 36px; font-weight: 800; margin-bottom: 12px; }
32
+ .info p { font-size: 16px; opacity: .85; line-height: 1.6; margin-bottom: 24px; }
33
+ .snippet {
34
+ background: rgba(0,0,0,.3);
35
+ border-radius: 12px;
36
+ padding: 20px;
37
+ font-family: 'SF Mono', 'Fira Code', monospace;
38
+ font-size: 13px;
39
+ color: #e2e8f0;
40
+ line-height: 1.6;
41
+ white-space: pre;
42
+ }
43
+
44
+ /* Widget-only mode (?profileId=... present): bare contained card on a
45
+ neutral backdrop — what the widget actually looks like embedded on a
46
+ real page, just centered for easy viewing/sharing as a test link. */
47
+ body.widget-only { background: #e9eaee; }
48
+
49
+ /* Both modes use the same card shape for the widget itself */
50
+ .preview {
51
+ width: 390px;
52
+ height: 680px;
53
+ max-width: calc(100vw - 48px);
54
+ max-height: calc(100vh - 48px);
55
+ border-radius: 22px;
56
+ overflow: hidden;
57
+ box-shadow: 0 24px 60px rgba(0,0,0,.25);
58
+ background: #fff;
59
+ flex-shrink: 0;
60
+ }
61
+
62
+ #app { width: 100%; height: 100%; }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <div id="marketing-info" class="info" style="display:none">
67
+ <h1>Relay Chat Widget</h1>
68
+ <p>Add a real-time chat widget to any website in two lines of code.</p>
69
+ <div class="snippet">&lt;div id="chat"&gt;&lt;/div&gt;
70
+ &lt;script type="module"&gt;
71
+ import { mount } from 'https://relay.paramms.com/index.js'
72
+ mount({
73
+ el: document.getElementById('chat'),
74
+ url: 'wss://api.paramms.com/ws',
75
+ profileId: 'YOUR_PROFILE_ID',
76
+ })
77
+ &lt;/script&gt;</div>
78
+ </div>
79
+ <div id="layout"></div>
80
+ <script type="module">
81
+ import { mount } from './index.js'
82
+
83
+ const q = new URLSearchParams(location.search)
84
+ const profileId = q.get('profileId')
85
+ const isWidgetOnly = !!profileId
86
+
87
+ document.body.className = isWidgetOnly ? 'widget-only' : 'marketing'
88
+
89
+ const layout = document.getElementById('layout')
90
+ const preview = document.createElement('div')
91
+ preview.className = 'preview'
92
+ const app = document.createElement('div')
93
+ app.id = 'app'
94
+ preview.append(app)
95
+
96
+ if (isWidgetOnly) {
97
+ // Just the contained card, centered on a neutral backdrop.
98
+ layout.append(preview)
99
+ } else {
100
+ // Marketing landing page: info column + card preview, side by side.
101
+ const container = document.createElement('div')
102
+ container.className = 'container'
103
+ const info = document.getElementById('marketing-info')
104
+ info.style.display = ''
105
+ container.append(info, preview)
106
+ layout.append(container)
107
+ }
108
+
109
+ const wsUrl = q.get('url') || 'wss://api.paramms.com/ws'
110
+ const subjectId = q.get('subjectId') || undefined
111
+
112
+ // ── Persistent guest identity ────────────────────────────────────────────
113
+ // Resolved here and persisted via BOTH a first-party cookie and localStorage.
114
+ // On this top-level page the cookie survives refreshes even when localStorage
115
+ // is cleared/blocked, so the guest keeps the same id across reloads and the
116
+ // server resumes their existing conversation instead of starting a new chat.
117
+ function readCookie(name) {
118
+ const m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'))
119
+ return m ? decodeURIComponent(m[1]) : null
120
+ }
121
+ function persistentGuestId() {
122
+ let id = q.get('uid') // explicit pin wins
123
+ if (!id) { try { id = localStorage.getItem('oc_uid') } catch (e) {} }
124
+ if (!id) id = readCookie('oc_uid')
125
+ if (!id) id = 'g_' + Math.random().toString(36).slice(2) + Date.now().toString(36)
126
+ try { localStorage.setItem('oc_uid', id) } catch (e) {}
127
+ const secure = location.protocol === 'https:' ? '; Secure' : ''
128
+ document.cookie = 'oc_uid=' + encodeURIComponent(id) +
129
+ '; Max-Age=' + (60 * 60 * 24 * 365) + '; Path=/; SameSite=Lax' + secure
130
+ return id
131
+ }
132
+
133
+ // Prefer SERVER-OWNED identity: the relay mints a signed guest token and
134
+ // sets an httpOnly cookie scoped to the parent domain, so the same guest is
135
+ // recognised across refreshes regardless of client storage. Falls back to
136
+ // the client-persisted id if the relay can't be reached — and NEVER hangs:
137
+ // fetch() has no built-in timeout, so without this a slow/unreachable relay
138
+ // would leave the promise pending forever and the widget would never mount
139
+ // (a blank page). We race it against a short timeout.
140
+ let apiBase = wsUrl.replace('wss://', 'https://').replace('ws://', 'http://')
141
+ if (apiBase.endsWith('/ws')) apiBase = apiBase.slice(0, -3)
142
+ async function resolveToken() {
143
+ const pinned = q.get('token') || q.get('uid') // host-supplied identity wins
144
+ if (pinned) return pinned
145
+ try {
146
+ const ctrl = new AbortController()
147
+ const timer = setTimeout(() => ctrl.abort(), 3000)
148
+ const r = await fetch(apiBase + '/auth/guest', { method: 'POST', credentials: 'include', signal: ctrl.signal })
149
+ clearTimeout(timer)
150
+ if (r.ok) { const d = await r.json(); if (d && d.token) return d.token }
151
+ } catch (e) { /* unreachable/slow/aborted — fall back below */ }
152
+ return persistentGuestId()
153
+ }
154
+
155
+ // Mount only ever depends on a resolved token, and any failure surfaces as a
156
+ // visible message rather than a blank page.
157
+ resolveToken()
158
+ .catch(() => persistentGuestId())
159
+ .then((token) => {
160
+ try {
161
+ mount({
162
+ el: app,
163
+ token,
164
+ url: wsUrl,
165
+ profileId: profileId || 'p_hotel',
166
+ ...(subjectId ? { subjectId } : {}),
167
+ accent: q.get('accent') || '#4F63F5',
168
+ })
169
+ } catch (err) {
170
+ app.innerHTML = '<div style="padding:24px;font:14px system-ui;color:#b91c1c">' +
171
+ 'Chat failed to load. ' + (err && err.message ? String(err.message) : '') + '</div>'
172
+ }
173
+ })
174
+ </script>
175
+ </body>
176
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paramms/chat-widget",
3
- "version": "1.0.30",
3
+ "version": "1.0.31",
4
4
  "description": "Embeddable real-time chat widget for the Relay platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,29 +15,7 @@
15
15
  "live-chat"
16
16
  ],
17
17
  "files": [
18
- "dist/index.js",
19
- "dist/index.js.map",
20
- "dist/index.d.ts",
21
- "dist/react.js",
22
- "dist/react.js.map",
23
- "dist/react.d.ts",
24
- "dist/chatlist.js",
25
- "dist/chatlist.js.map",
26
- "dist/chatlist.d.ts",
27
- "dist/uid.js",
28
- "dist/uid.js.map",
29
- "dist/uid.d.ts",
30
- "dist/embed.js",
31
- "dist/embed.js.map",
32
- "dist/embed.d.ts",
33
- "dist/store.d.ts",
34
- "dist/connection.d.ts",
35
- "dist/history.d.ts",
36
- "dist/outbox.d.ts",
37
- "dist/renderer.d.ts",
38
- "dist/crypto.d.ts",
39
- "dist/e2e.d.ts",
40
- "dist/protocol"
18
+ "dist"
41
19
  ],
42
20
  "type": "module",
43
21
  "scripts": {
@@ -81,6 +59,10 @@
81
59
  "./hooks": {
82
60
  "types": "./dist/hooks.d.ts",
83
61
  "default": "./dist/hooks.js"
62
+ },
63
+ "./e2e": {
64
+ "types": "./dist/e2e.d.ts",
65
+ "default": "./dist/e2e.js"
84
66
  }
85
67
  },
86
68
  "peerDependencies": {