@deway-ai/web-sdk 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -0
- package/dist/loader.d.ts +12 -0
- package/dist/loader.es.js +497 -0
- package/dist/loader.umd.js +1 -0
- package/package.json +110 -0
package/README.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Deway Web SDK
|
|
2
|
+
|
|
3
|
+
[](./documentation/accessibility/compliance.md)
|
|
4
|
+
|
|
5
|
+
A lightweight TypeScript SDK for adding an AI-powered user engagement engine by Deway to your web app.
|
|
6
|
+
|
|
7
|
+
## Quick Install with AI Assistant
|
|
8
|
+
|
|
9
|
+
Copy and paste this prompt to your AI coding assistant (Claude, Copilot, etc.) to get Deway set up with best practices:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Install and integrate the Deway Web SDK (@deway-ai/web-sdk) into my project.
|
|
13
|
+
|
|
14
|
+
PACKAGE INFO:
|
|
15
|
+
- Name: @deway-ai/web-sdk
|
|
16
|
+
- Purpose: Lightweight TypeScript SDK that adds an AI-powered user engagement engine
|
|
17
|
+
- Supports: React, Next.js, Vue, Nuxt, Angular, and vanilla JS
|
|
18
|
+
- Includes full TypeScript definitions
|
|
19
|
+
|
|
20
|
+
TASKS:
|
|
21
|
+
1. Detect my framework and package manager (npm, pnpm, or yarn) from lock files
|
|
22
|
+
2. Install the SDK using the detected package manager
|
|
23
|
+
3. For vanilla JS/HTML projects: Use the CDN script instead:
|
|
24
|
+
<script src="https://unpkg.com/@deway-ai/web-sdk/dist/loader.umd.js"></script>
|
|
25
|
+
4. Initialize early in the app lifecycle (use appropriate lifecycle hook for the framework):
|
|
26
|
+
- Import: import Deway from '@deway-ai/web-sdk'
|
|
27
|
+
- Call Deway.init() with the appKey:
|
|
28
|
+
Deway.init({ appKey: 'your-deway-app-key' })
|
|
29
|
+
5. Identify users after authentication:
|
|
30
|
+
if (user?.id) Deway.identify(user.id);
|
|
31
|
+
|
|
32
|
+
IMPLEMENTATION NOTES:
|
|
33
|
+
- Follow my existing project structure and code style conventions
|
|
34
|
+
- Use appropriate lifecycle hooks: React (useEffect), Vue (onMounted), Angular (ngOnInit)
|
|
35
|
+
|
|
36
|
+
FIRST analyze my project structure and detect the framework and package manager. Then and only then implement the Deway Web SDK integration accordingly.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @deway-ai/web-sdk
|
|
43
|
+
# or
|
|
44
|
+
pnpm add @deway-ai/web-sdk
|
|
45
|
+
# or
|
|
46
|
+
yarn add @deway-ai/web-sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Basic Integration
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import Deway from '@deway-ai/web-sdk';
|
|
55
|
+
|
|
56
|
+
// Initialize the SDK
|
|
57
|
+
Deway.init({
|
|
58
|
+
appKey: 'your-app-key'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Identify a user
|
|
62
|
+
Deway.identify('user-123');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Note:** `Deway.init()` must be called before other methods. User identification via `Deway.identify()` enables activity
|
|
66
|
+
tracking.
|
|
67
|
+
|
|
68
|
+
### HTML/JavaScript Integration
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
|
|
72
|
+
<script src="https://unpkg.com/@deway-ai/web-sdk/dist/loader.umd.js"></script>
|
|
73
|
+
<script>
|
|
74
|
+
Deway.init({
|
|
75
|
+
appKey: 'your-app-key'
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
Deway.identify('user-123');
|
|
79
|
+
</script>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
Deway.init({
|
|
86
|
+
appKey: string, // Required: Your Deway app key
|
|
87
|
+
apiEndpoint: string | undefined, // Optional: Custom API endpoint
|
|
88
|
+
wsEndpoint: string | undefined, // Optional: Custom WebSocket endpoint
|
|
89
|
+
isDevelopment: boolean | undefined, // Optional: Enable dev mode logging (default: false)
|
|
90
|
+
autoShowBubble: boolean | undefined, // Optional: Auto-show bubble (default: true)
|
|
91
|
+
bubbleConfig: BubbleConfig | undefined, // Optional: Bubble appearance customization
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### BubbleConfig
|
|
96
|
+
|
|
97
|
+
Customize the chat bubble appearance:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
interface BubbleConfig {
|
|
101
|
+
position?: BubblePosition; // Position on screen
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
enum BubblePosition {
|
|
105
|
+
BOTTOM_LEFT = "bottom-left",
|
|
106
|
+
BOTTOM_RIGHT = "bottom-right" // default
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Example with Custom Configuration
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
Deway.init({
|
|
114
|
+
appKey: 'your-app-key',
|
|
115
|
+
isDevelopment: true,
|
|
116
|
+
autoShowBubble: true,
|
|
117
|
+
bubbleConfig: {
|
|
118
|
+
position: 'bottom-left'
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### `Deway.init(config)`
|
|
126
|
+
|
|
127
|
+
Initialize the SDK with configuration options. Must be called before using other methods.
|
|
128
|
+
|
|
129
|
+
### `Deway.identify(userId)`
|
|
130
|
+
|
|
131
|
+
Associate a user ID with the current session for personalized experiences.
|
|
132
|
+
|
|
133
|
+
### `Deway.reportEvent(name, params)`
|
|
134
|
+
|
|
135
|
+
Report a custom event with the given name and parameters.
|
|
136
|
+
|
|
137
|
+
**Parameters:**
|
|
138
|
+
|
|
139
|
+
- `name` (string): Event name (will be normalized: trimmed, lowercased, special characters replaced with underscores)
|
|
140
|
+
- `params` (object): Event parameters as a plain object
|
|
141
|
+
|
|
142
|
+
**Example:**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
Deway.reportEvent('Product Purchased', {
|
|
146
|
+
product_id: 'ABC-123',
|
|
147
|
+
quantity: 5,
|
|
148
|
+
price: 99.99
|
|
149
|
+
});
|
|
150
|
+
// Event sent with normalized name: "product_purchased"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `Deway.setUserProfile(userProfile)`
|
|
154
|
+
|
|
155
|
+
Set or update the user profile for the currently identified user.
|
|
156
|
+
|
|
157
|
+
**Parameters:**
|
|
158
|
+
|
|
159
|
+
- `userProfile` (object): User profile data as a plain object. Can contain any JSON-serializable properties.
|
|
160
|
+
|
|
161
|
+
**Example:**
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
Deway.setUserProfile({
|
|
165
|
+
name: 'Jane Doe',
|
|
166
|
+
email: 'jane@example.com',
|
|
167
|
+
age: 28,
|
|
168
|
+
preferences: {
|
|
169
|
+
theme: 'dark',
|
|
170
|
+
notifications: true
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Notes:**
|
|
176
|
+
|
|
177
|
+
- Profile is upserted (created if new, replaced if exists)
|
|
178
|
+
- Profile data completely replaces existing profile on each call
|
|
179
|
+
|
|
180
|
+
### `Deway.showBubble(config?)`
|
|
181
|
+
|
|
182
|
+
Show the AI chat bubble. Optional configuration for appearance customization.
|
|
183
|
+
|
|
184
|
+
### `Deway.hideBubble()`
|
|
185
|
+
|
|
186
|
+
Hide the AI chat bubble.
|
|
187
|
+
|
|
188
|
+
### `Deway.isBubbleVisible()`
|
|
189
|
+
|
|
190
|
+
Check if the bubble is currently visible.
|
|
191
|
+
|
|
192
|
+
### `Deway.destroy()`
|
|
193
|
+
|
|
194
|
+
Clean up SDK resources and stop all tracking.
|
|
195
|
+
|
|
196
|
+
## Framework Examples
|
|
197
|
+
|
|
198
|
+
### React
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import {useEffect} from 'react';
|
|
202
|
+
import Deway from '@deway-ai/web-sdk';
|
|
203
|
+
|
|
204
|
+
function App() {
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
Deway.init({
|
|
207
|
+
appKey: 'your-app-key'
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Identify user when available
|
|
211
|
+
if (user?.id) {
|
|
212
|
+
Deway.identify(user.id);
|
|
213
|
+
}
|
|
214
|
+
}, [user?.id]);
|
|
215
|
+
|
|
216
|
+
return <div>Your App</div>;
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Vue
|
|
221
|
+
|
|
222
|
+
```vue
|
|
223
|
+
|
|
224
|
+
<script setup>
|
|
225
|
+
import {onMounted} from 'vue';
|
|
226
|
+
import Deway from '@deway-ai/web-sdk';
|
|
227
|
+
|
|
228
|
+
onMounted(() => {
|
|
229
|
+
Deway.init({
|
|
230
|
+
appKey: 'your-app-key'
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
</script>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Angular
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import {Component, OnInit} from '@angular/core';
|
|
240
|
+
import Deway from '@deway-ai/web-sdk';
|
|
241
|
+
|
|
242
|
+
@Component({
|
|
243
|
+
selector: 'app-root',
|
|
244
|
+
template: '<div>Your App</div>'
|
|
245
|
+
})
|
|
246
|
+
export class AppComponent implements OnInit {
|
|
247
|
+
ngOnInit() {
|
|
248
|
+
Deway.init({
|
|
249
|
+
appKey: 'your-app-key'
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Features
|
|
256
|
+
|
|
257
|
+
- **AI-powered user engagement and assistance**: Interactive chat interface for user support
|
|
258
|
+
- **Automatic user behavior analysis**: Intelligent tracking and understanding of user interactions
|
|
259
|
+
- **Framework-agnostic integration**: Works with React, Vue, Angular, and vanilla JavaScript
|
|
260
|
+
- **Full TypeScript support**: Complete type definitions included
|
|
261
|
+
- **Lightweight**: Minimal bundle size impact
|
|
262
|
+
|
|
263
|
+
## Edge Cases & Internal Behavior
|
|
264
|
+
|
|
265
|
+
### Error Handling
|
|
266
|
+
|
|
267
|
+
The SDK handles all errors internally. Callers don't need try-catch blocks around SDK methods. Failed operations are
|
|
268
|
+
logged to the console and retried automatically when appropriate.
|
|
269
|
+
|
|
270
|
+
### State Persistence
|
|
271
|
+
|
|
272
|
+
User identification and settings persist across page reloads via localStorage. Once a user is identified, they remain
|
|
273
|
+
identified until `Deway.destroy()` is called or localStorage is cleared.
|
|
274
|
+
|
|
275
|
+
### Multiple init() Calls
|
|
276
|
+
|
|
277
|
+
Calling `Deway.init()` multiple times is safe. Subsequent calls after the first are ignored.
|
|
278
|
+
|
|
279
|
+
### Automatic Retries
|
|
280
|
+
|
|
281
|
+
Failed API calls and uploads are automatically retried with exponential backoff. No manual intervention required.
|
|
282
|
+
|
|
283
|
+
### Call Queue
|
|
284
|
+
|
|
285
|
+
All SDK calls should be called after the SDK is initialized via `Deway.init()`, and the user is identified via
|
|
286
|
+
`Deway.identify()`.
|
|
287
|
+
In case a call is made before `init` or `identify` the SDK queues the call internally and eventually executed once the
|
|
288
|
+
prerequisite calls were made.
|
|
289
|
+
|
|
290
|
+
## Troubleshooting
|
|
291
|
+
|
|
292
|
+
### Bubble doesn't appear
|
|
293
|
+
|
|
294
|
+
- Check `autoShowBubble` is set to `true` OR manually call `Deway.showBubble()`
|
|
295
|
+
- Verify `Deway.init()` was called with valid `appKey`
|
|
296
|
+
- Check browser console for initialization errors
|
|
297
|
+
|
|
298
|
+
### User identification not working
|
|
299
|
+
|
|
300
|
+
- Ensure `Deway.init()` is called before `Deway.identify()`
|
|
301
|
+
- User ID must be non-empty string
|
|
302
|
+
- Check browser console for error logs
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BubbleConfig } from '../deway-bubble/models/bubble-config';
|
|
2
|
+
import { DewayConfig } from '../sdk/types';
|
|
3
|
+
import { IDeway } from '../sdk/types';
|
|
4
|
+
|
|
5
|
+
export { BubbleConfig }
|
|
6
|
+
|
|
7
|
+
declare const Deway: IDeway;
|
|
8
|
+
export default Deway;
|
|
9
|
+
|
|
10
|
+
export { DewayConfig }
|
|
11
|
+
|
|
12
|
+
export { }
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
const w = "deway-sdk-config", p = ["Understanding intent", "Reading web page", "Browsing the docs", "Enriching context", "Validating understanding", "Crafting response"];
|
|
2
|
+
class S {
|
|
3
|
+
saveConfig(e) {
|
|
4
|
+
if (window?.localStorage)
|
|
5
|
+
try {
|
|
6
|
+
const t = JSON.stringify(e);
|
|
7
|
+
window.localStorage.setItem(w, t);
|
|
8
|
+
} catch (t) {
|
|
9
|
+
console.warn("Failed to save SDK config to localStorage:", t);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
loadConfig() {
|
|
13
|
+
if (window?.localStorage)
|
|
14
|
+
try {
|
|
15
|
+
const e = window.localStorage.getItem(w);
|
|
16
|
+
return e ? JSON.parse(e) : null;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return console.warn("Failed to load SDK config from localStorage:", e), null;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
getExcludedVendors() {
|
|
23
|
+
return this.loadConfig()?.excludedVendors ?? [];
|
|
24
|
+
}
|
|
25
|
+
getAssistantName() {
|
|
26
|
+
return this.loadConfig()?.assistantName ?? "Assistant";
|
|
27
|
+
}
|
|
28
|
+
getAiDisclaimer() {
|
|
29
|
+
return this.loadConfig()?.aiDisclaimer;
|
|
30
|
+
}
|
|
31
|
+
getThinkingMessages() {
|
|
32
|
+
return this.loadConfig()?.thinkingMessages ?? p;
|
|
33
|
+
}
|
|
34
|
+
getHumanHandoffUrl() {
|
|
35
|
+
return this.loadConfig()?.humanHandoffUrl;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class s {
|
|
39
|
+
static CACHE_KEY = "deway-sdk-cache";
|
|
40
|
+
static DB_NAME = "DewaySdk";
|
|
41
|
+
static DB_VERSION = 1;
|
|
42
|
+
static STORE_NAME = "sdk";
|
|
43
|
+
async cacheSDK(e, t, o, n) {
|
|
44
|
+
try {
|
|
45
|
+
const c = (await this.openIndexedDB()).transaction([s.STORE_NAME], "readwrite").objectStore(s.STORE_NAME);
|
|
46
|
+
await new Promise((h, y) => {
|
|
47
|
+
const d = c.put({
|
|
48
|
+
id: "latest",
|
|
49
|
+
code: e,
|
|
50
|
+
version: t,
|
|
51
|
+
checksum: o,
|
|
52
|
+
signature: n,
|
|
53
|
+
timestamp: Date.now()
|
|
54
|
+
});
|
|
55
|
+
d.onsuccess = () => h(d.result), d.onerror = () => y(d.error);
|
|
56
|
+
}), console.debug("SDK cached in IndexedDB");
|
|
57
|
+
} catch {
|
|
58
|
+
try {
|
|
59
|
+
const a = JSON.stringify({
|
|
60
|
+
code: e,
|
|
61
|
+
version: t,
|
|
62
|
+
checksum: o,
|
|
63
|
+
signature: n,
|
|
64
|
+
timestamp: Date.now()
|
|
65
|
+
});
|
|
66
|
+
localStorage.setItem(s.CACHE_KEY, a), console.debug("SDK cached in localStorage");
|
|
67
|
+
} catch (a) {
|
|
68
|
+
console.warn("Unable to cache SDK:", a);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async getCachedSDK() {
|
|
73
|
+
try {
|
|
74
|
+
const o = (await this.openIndexedDB()).transaction([s.STORE_NAME], "readonly").objectStore(s.STORE_NAME), n = await new Promise((i, a) => {
|
|
75
|
+
const c = o.get("latest");
|
|
76
|
+
c.onsuccess = () => i(c.result || null), c.onerror = () => a(c.error);
|
|
77
|
+
});
|
|
78
|
+
if (n)
|
|
79
|
+
return console.debug("Found cached SDK in IndexedDB"), n;
|
|
80
|
+
} catch {
|
|
81
|
+
console.debug("IndexedDB unavailable, trying localStorage");
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const e = localStorage.getItem(s.CACHE_KEY);
|
|
85
|
+
if (e)
|
|
86
|
+
return console.debug("Found cached SDK in localStorage"), JSON.parse(e);
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.warn("Failed to read from localStorage:", e);
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
openIndexedDB() {
|
|
93
|
+
return new Promise((e, t) => {
|
|
94
|
+
const o = indexedDB.open(s.DB_NAME, s.DB_VERSION);
|
|
95
|
+
o.onerror = () => t(o.error), o.onsuccess = () => e(o.result), o.onupgradeneeded = (n) => {
|
|
96
|
+
const i = n.target.result;
|
|
97
|
+
i.objectStoreNames.contains(s.STORE_NAME) || i.createObjectStore(s.STORE_NAME, { keyPath: "id" });
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function b(r) {
|
|
103
|
+
const t = new TextEncoder().encode(r), o = await crypto.subtle.digest("SHA-256", t);
|
|
104
|
+
return Array.from(new Uint8Array(o)).map((i) => i.toString(16).padStart(2, "0")).join("");
|
|
105
|
+
}
|
|
106
|
+
function g(r) {
|
|
107
|
+
const e = atob(r), t = new Uint8Array(e.length);
|
|
108
|
+
for (let o = 0; o < e.length; o++)
|
|
109
|
+
t[o] = e.charCodeAt(o);
|
|
110
|
+
return t;
|
|
111
|
+
}
|
|
112
|
+
function D(r) {
|
|
113
|
+
const e = new Uint8Array(r.length / 2);
|
|
114
|
+
for (let t = 0; t < r.length; t += 2)
|
|
115
|
+
e[t / 2] = Number.parseInt(r.substr(t, 2), 16);
|
|
116
|
+
return e;
|
|
117
|
+
}
|
|
118
|
+
async function E(r, e, t) {
|
|
119
|
+
try {
|
|
120
|
+
const o = g(r), n = D(e), i = g(t), a = await crypto.subtle.importKey("raw", o, { name: "Ed25519" }, !1, ["verify"]);
|
|
121
|
+
return await crypto.subtle.verify("Ed25519", a, i, n);
|
|
122
|
+
} catch (o) {
|
|
123
|
+
return console.error("Ed25519 signature verification failed:", o), !1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function C(r, e, t, o) {
|
|
127
|
+
if (!e || !t)
|
|
128
|
+
throw new Error("SDK verification failed: Missing security headers");
|
|
129
|
+
if (await b(r) !== e)
|
|
130
|
+
throw new Error("SDK verification failed: Checksum mismatch - content tampered");
|
|
131
|
+
if (!await E(o, e, t))
|
|
132
|
+
throw new Error("SDK verification failed: Invalid signature - content tampered");
|
|
133
|
+
}
|
|
134
|
+
class K {
|
|
135
|
+
cleanApiEndpoint(e) {
|
|
136
|
+
return e.trim().replace(/\/+$/, "");
|
|
137
|
+
}
|
|
138
|
+
async fetchSDK(e, t, o) {
|
|
139
|
+
try {
|
|
140
|
+
console.info("Fetching Deway SDK from backend...");
|
|
141
|
+
const n = this.cleanApiEndpoint(t), i = await fetch(`${n}/sdk-serve/sdk/v0`, {
|
|
142
|
+
method: "GET",
|
|
143
|
+
headers: {
|
|
144
|
+
Accept: "application/javascript",
|
|
145
|
+
"deway-app-key": e,
|
|
146
|
+
Origin: window?.location?.origin || ""
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return i.ok ? this.handleSuccessfulFetch(i, o) : (console.warn(`Failed to fetch SDK: HTTP ${i.status}: ${i.statusText}`), null);
|
|
150
|
+
} catch (n) {
|
|
151
|
+
return console.warn("Failed to fetch SDK from server:", n), null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async handleSuccessfulFetch(e, t) {
|
|
155
|
+
const o = e.headers.get("x-sdk-checksum"), n = e.headers.get("x-sdk-version"), i = e.headers.get("x-sdk-signature"), a = await e.text();
|
|
156
|
+
if (console.info(`Fetched Deway SDK version ${n}`), !n || !a || !o) {
|
|
157
|
+
const c = Object.fromEntries(e.headers.entries());
|
|
158
|
+
throw console.error(`Failed to get required data from sdk fetch. Headers: ${JSON.stringify(c)}`), new Error("Invalid SDK response: missing version, code, or checksum");
|
|
159
|
+
}
|
|
160
|
+
return await C(a, o, i, t), { code: a, version: n, checksum: o, signature: i || "" };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
class u {
|
|
164
|
+
static MAX_QUEUE_SIZE = 50;
|
|
165
|
+
commandQueue = [];
|
|
166
|
+
queueCommand(e, ...t) {
|
|
167
|
+
this.commandQueue.length >= u.MAX_QUEUE_SIZE && (console.warn(`Command queue full (${u.MAX_QUEUE_SIZE} commands). Discarding oldest command.`), this.commandQueue.shift()), this.commandQueue.push({ method: e, args: t }), console.debug(`Queued command: ${e} (queue size: ${this.commandQueue.length})`);
|
|
168
|
+
}
|
|
169
|
+
replayQueuedCommands() {
|
|
170
|
+
if (this.commandQueue.length === 0)
|
|
171
|
+
return;
|
|
172
|
+
console.info(`Replaying ${this.commandQueue.length} queued commands`);
|
|
173
|
+
const e = [...this.commandQueue];
|
|
174
|
+
if (this.commandQueue = [], !this.isSDKAvailable()) {
|
|
175
|
+
console.warn("Deway SDK not available for command replay");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (const t of e)
|
|
179
|
+
this.replayCommand(t);
|
|
180
|
+
}
|
|
181
|
+
clearQueue() {
|
|
182
|
+
this.commandQueue = [];
|
|
183
|
+
}
|
|
184
|
+
isSDKAvailable() {
|
|
185
|
+
return typeof window < "u" && "Deway" in window && !!window.Deway;
|
|
186
|
+
}
|
|
187
|
+
replayCommand(e) {
|
|
188
|
+
try {
|
|
189
|
+
const t = window.Deway, o = t?.[e.method];
|
|
190
|
+
typeof o == "function" ? o.apply(t, e.args) : console.warn(`Method ${e.method} not found on Deway SDK`);
|
|
191
|
+
} catch (t) {
|
|
192
|
+
console.error(`Failed to replay command ${e.method}:`, t);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
class f {
|
|
197
|
+
static CACHE_KEY = "deway-remote-config-cache";
|
|
198
|
+
/**
|
|
199
|
+
* Caches remote configuration in localStorage
|
|
200
|
+
* TTL only controls fetch frequency, cached config is never deleted
|
|
201
|
+
*
|
|
202
|
+
* @param config - Remote configuration to cache
|
|
203
|
+
* @param ttlSeconds - Time-to-live in seconds from server
|
|
204
|
+
*/
|
|
205
|
+
async cacheRemoteConfig(e, t) {
|
|
206
|
+
try {
|
|
207
|
+
const o = {
|
|
208
|
+
config: e,
|
|
209
|
+
ttl_seconds: t,
|
|
210
|
+
timestamp: Date.now()
|
|
211
|
+
};
|
|
212
|
+
localStorage.setItem(f.CACHE_KEY, JSON.stringify(o)), console.debug("Remote configuration cached in localStorage");
|
|
213
|
+
} catch (o) {
|
|
214
|
+
console.warn("Failed to cache remote config:", o);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Retrieves cached remote configuration from localStorage
|
|
219
|
+
* Returns null if no cache exists
|
|
220
|
+
*
|
|
221
|
+
* @returns Cached remote config or null
|
|
222
|
+
*/
|
|
223
|
+
async getCachedRemoteConfig() {
|
|
224
|
+
try {
|
|
225
|
+
const e = localStorage.getItem(f.CACHE_KEY);
|
|
226
|
+
if (!e)
|
|
227
|
+
return null;
|
|
228
|
+
const t = JSON.parse(e);
|
|
229
|
+
return console.debug("Found cached remote configuration"), t;
|
|
230
|
+
} catch (e) {
|
|
231
|
+
return console.warn("Failed to read cached remote config:", e), null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Checks if the cached config is still valid based on TTL
|
|
236
|
+
* Note: This only affects whether we fetch fresh config, not if we use cached config
|
|
237
|
+
*
|
|
238
|
+
* @param timestamp - Timestamp when config was cached (milliseconds)
|
|
239
|
+
* @param ttlSeconds - TTL in seconds
|
|
240
|
+
* @returns true if cache is still valid
|
|
241
|
+
*/
|
|
242
|
+
isCacheValid(e, t) {
|
|
243
|
+
const o = Date.now(), n = e + t * 1e3;
|
|
244
|
+
return o < n;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
class v {
|
|
248
|
+
cleanApiEndpoint(e) {
|
|
249
|
+
return e.trim().replace(/\/+$/, "");
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Fetches remote configuration from the backend
|
|
253
|
+
* Returns null if the fetch fails for any reason
|
|
254
|
+
*
|
|
255
|
+
* @param appKey - The application key for authentication
|
|
256
|
+
* @param apiEndpoint - The API endpoint URL
|
|
257
|
+
* @returns Remote config response or null on error
|
|
258
|
+
*/
|
|
259
|
+
async fetchRemoteConfig(e, t) {
|
|
260
|
+
try {
|
|
261
|
+
console.info("Fetching remote configuration from backend...");
|
|
262
|
+
const o = this.cleanApiEndpoint(t), n = await fetch(`${o}/sdk-remote-config-serve/`, {
|
|
263
|
+
method: "GET",
|
|
264
|
+
headers: {
|
|
265
|
+
Accept: "application/json",
|
|
266
|
+
"deway-app-key": e,
|
|
267
|
+
Origin: window?.location?.origin || ""
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
if (n.ok) {
|
|
271
|
+
const i = await n.json();
|
|
272
|
+
return console.info("Remote configuration fetched successfully"), i;
|
|
273
|
+
}
|
|
274
|
+
return console.warn(`Failed to fetch remote config: HTTP ${n.status}: ${n.statusText}`), null;
|
|
275
|
+
} catch (o) {
|
|
276
|
+
return console.warn("Failed to fetch remote config from server:", o), null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
class A {
|
|
281
|
+
async executeSDK(e) {
|
|
282
|
+
return new Promise((t) => {
|
|
283
|
+
try {
|
|
284
|
+
if (!this.isDocumentReady()) {
|
|
285
|
+
console.error("Document is not available for script execution"), t(!1);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const o = document.createElement("script");
|
|
289
|
+
o.textContent = e, o.type = "text/javascript";
|
|
290
|
+
let n = !1;
|
|
291
|
+
const i = (a) => {
|
|
292
|
+
n || (n = !0, this.cleanupScript(o), t(a));
|
|
293
|
+
};
|
|
294
|
+
o.onerror = () => {
|
|
295
|
+
console.error("Script execution failed"), i(!1);
|
|
296
|
+
}, o.onload = () => i(!0), document.head.appendChild(o), setTimeout(() => {
|
|
297
|
+
!n && this.verifySDKLoaded() ? i(!0) : n || (console.error("SDK execution timeout - Deway object not found"), i(!1));
|
|
298
|
+
}, 100);
|
|
299
|
+
} catch (o) {
|
|
300
|
+
console.error("Failed to execute SDK script:", o), t(!1);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
isDocumentReady() {
|
|
305
|
+
return typeof document < "u" && document.head != null;
|
|
306
|
+
}
|
|
307
|
+
verifySDKLoaded() {
|
|
308
|
+
return typeof window < "u" && "Deway" in window && !!window.Deway;
|
|
309
|
+
}
|
|
310
|
+
cleanupScript(e) {
|
|
311
|
+
try {
|
|
312
|
+
e.parentNode && e.parentNode.removeChild(e);
|
|
313
|
+
} catch (t) {
|
|
314
|
+
console.debug("Failed to cleanup script element:", t);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
class F {
|
|
319
|
+
validateConfig(e) {
|
|
320
|
+
return e ? !e.appKey || e.appKey.trim().length === 0 ? (console.error("Config.appKey is required and must be a non-empty string"), !1) : e.apiEndpoint !== void 0 && !this.isValidUrl(e.apiEndpoint) ? (console.error("Config.apiEndpoint must be a valid URL"), !1) : e.publicKey !== void 0 && e.publicKey.trim().length === 0 ? (console.error("Config.publicKey must be a non-empty string if provided"), !1) : !0 : (console.error("Config is required"), !1);
|
|
321
|
+
}
|
|
322
|
+
isValidUrl(e) {
|
|
323
|
+
try {
|
|
324
|
+
return new URL(e), !0;
|
|
325
|
+
} catch {
|
|
326
|
+
return !1;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const m = {
|
|
331
|
+
apiEndpoint: "https://service.deway.app",
|
|
332
|
+
publicKey: "9d3dBUvqyUQ7egd5j5uORdHSqZ7VFWOu+ud/SWt9WUY=",
|
|
333
|
+
isDevelopment: !1,
|
|
334
|
+
autoShowBubble: !1
|
|
335
|
+
};
|
|
336
|
+
class x {
|
|
337
|
+
isLoaded = !1;
|
|
338
|
+
isLoading = !1;
|
|
339
|
+
cacheManager;
|
|
340
|
+
scriptExecutor;
|
|
341
|
+
commandQueue;
|
|
342
|
+
sdkFetcher;
|
|
343
|
+
configValidator;
|
|
344
|
+
remoteConfigFetcher;
|
|
345
|
+
remoteConfigCache;
|
|
346
|
+
sdkConfigStore;
|
|
347
|
+
constructor() {
|
|
348
|
+
this.cacheManager = new s(), this.scriptExecutor = new A(), this.commandQueue = new u(), this.sdkFetcher = new K(), this.configValidator = new F(), this.sdkConfigStore = new S(), this.remoteConfigFetcher = new v(), this.remoteConfigCache = new f();
|
|
349
|
+
}
|
|
350
|
+
init(e) {
|
|
351
|
+
this.performInit(e).catch((t) => {
|
|
352
|
+
console.error("Failed to initialize Deway SDK:", t);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
async performInit(e) {
|
|
356
|
+
try {
|
|
357
|
+
if (!this.canInitialize())
|
|
358
|
+
return;
|
|
359
|
+
const t = this.isInitializationPayload(e), o = t ? e.localConfig : e;
|
|
360
|
+
if (!this.configValidator.validateConfig(o)) {
|
|
361
|
+
console.error("Invalid config provided to Deway SDK");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
this.isLoading = !0;
|
|
365
|
+
const n = o.apiEndpoint || m.apiEndpoint, i = o.publicKey || m.publicKey, [a, c] = await Promise.all([this.fetchOrLoadSDK(o.appKey, n, i), this.fetchRemoteConfigWithCache(o, n)]);
|
|
366
|
+
if (!a)
|
|
367
|
+
return;
|
|
368
|
+
const h = t ? e : this.createInitializationPayload(c, o);
|
|
369
|
+
if (!await this.scriptExecutor.executeSDK(a.code)) {
|
|
370
|
+
console.error("SDK execution failed");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (!this.initializeSDK(h))
|
|
374
|
+
return;
|
|
375
|
+
this.commandQueue.replayQueuedCommands(), this.isLoaded = !0;
|
|
376
|
+
} finally {
|
|
377
|
+
this.isLoading = !1;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
isInitializationPayload(e) {
|
|
381
|
+
return "localConfig" in e && "remoteConfig" in e && "defaults" in e;
|
|
382
|
+
}
|
|
383
|
+
canInitialize() {
|
|
384
|
+
return this.isLoaded ? (console.warn("Deway SDK already initialized"), !1) : this.isLoading ? (console.warn("Deway SDK initialization already in progress"), !1) : !0;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Fetches remote config from backend with cache fallback
|
|
388
|
+
* Returns null if both fetch and cache fail
|
|
389
|
+
*/
|
|
390
|
+
async fetchRemoteConfigWithCache(e, t) {
|
|
391
|
+
this.sdkConfigStore.saveConfig(e);
|
|
392
|
+
const o = await this.remoteConfigFetcher.fetchRemoteConfig(e.appKey, t);
|
|
393
|
+
if (o)
|
|
394
|
+
return await this.remoteConfigCache.cacheRemoteConfig(o.config, o.ttl_seconds), o;
|
|
395
|
+
console.info("Using cached remote config as fallback");
|
|
396
|
+
const n = await this.remoteConfigCache.getCachedRemoteConfig();
|
|
397
|
+
return n ? {
|
|
398
|
+
config: n.config,
|
|
399
|
+
ttl_seconds: n.ttl_seconds
|
|
400
|
+
} : (console.warn("No remote config available (fetch failed and no cache)"), null);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Creates initialization payload for backend SDK (NEW LOADER v0.29.0+)
|
|
404
|
+
* Passes raw configuration data to backend for merging
|
|
405
|
+
* This allows backend to own merge logic and handle all current/future config fields
|
|
406
|
+
*/
|
|
407
|
+
createInitializationPayload(e, t) {
|
|
408
|
+
return {
|
|
409
|
+
localConfig: t,
|
|
410
|
+
remoteConfig: e?.config ?? null,
|
|
411
|
+
defaults: m
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
async fetchOrLoadSDK(e, t, o) {
|
|
415
|
+
const n = await this.sdkFetcher.fetchSDK(e, t, o);
|
|
416
|
+
return n ? (await this.cacheManager.cacheSDK(n.code, n.version, n.checksum, n.signature), n) : (console.warn("Failed to fetch SDK from server, attempting cache fallback"), this.loadFromCache());
|
|
417
|
+
}
|
|
418
|
+
async loadFromCache() {
|
|
419
|
+
const e = await this.cacheManager.getCachedSDK();
|
|
420
|
+
return e ? (console.info(`Loading cached Deway SDK version ${e.version}`), { code: e.code, version: e.version }) : (console.error("SDK unavailable: Network error and no cached version found"), null);
|
|
421
|
+
}
|
|
422
|
+
initializeSDK(e) {
|
|
423
|
+
if (!this.isSDKAvailable())
|
|
424
|
+
return console.error("SDK execution failed: Deway object not found after loading"), !1;
|
|
425
|
+
try {
|
|
426
|
+
return window.Deway?.init(e), console.info("Deway SDK initialized successfully with payload"), !0;
|
|
427
|
+
} catch (t) {
|
|
428
|
+
return console.error("Failed to initialize SDK with payload:", t), !1;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
identify(e) {
|
|
432
|
+
try {
|
|
433
|
+
this.isLoaded && this.isSDKAvailable() ? window.Deway?.identify(e) : this.commandQueue.queueCommand("identify", e);
|
|
434
|
+
} catch (t) {
|
|
435
|
+
console.error("Failed to identify user:", t);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
reportEvent(e, t) {
|
|
439
|
+
try {
|
|
440
|
+
this.isLoaded && this.isSDKAvailable() ? window.Deway?.reportEvent(e, t) : this.commandQueue.queueCommand("reportEvent", e, t);
|
|
441
|
+
} catch (o) {
|
|
442
|
+
console.error("Failed to report event:", o);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
setUserProfile(e) {
|
|
446
|
+
try {
|
|
447
|
+
this.isLoaded && this.isSDKAvailable() ? window.Deway?.setUserProfile(e) : this.commandQueue.queueCommand("setUserProfile", e);
|
|
448
|
+
} catch (t) {
|
|
449
|
+
console.error("Failed to set user profile:", t);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
showBubble(e) {
|
|
453
|
+
try {
|
|
454
|
+
this.isLoaded && this.isSDKAvailable() ? window.Deway?.showBubble(e) : this.commandQueue.queueCommand("showBubble", e);
|
|
455
|
+
} catch (t) {
|
|
456
|
+
console.error("Failed to show bubble:", t);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
hideBubble() {
|
|
460
|
+
try {
|
|
461
|
+
this.isLoaded && this.isSDKAvailable() ? window.Deway?.hideBubble() : this.commandQueue.queueCommand("hideBubble");
|
|
462
|
+
} catch (e) {
|
|
463
|
+
console.error("Failed to hide bubble:", e);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
isBubbleVisible() {
|
|
467
|
+
try {
|
|
468
|
+
return this.isLoaded && this.isSDKAvailable() ? window.Deway?.isBubbleVisible() ?? !1 : !1;
|
|
469
|
+
} catch (e) {
|
|
470
|
+
return console.error("Failed to check bubble visibility:", e), !1;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
destroy() {
|
|
474
|
+
try {
|
|
475
|
+
this.isLoaded && this.isSDKAvailable() ? window.Deway?.destroy() : this.commandQueue.queueCommand("destroy"), this.commandQueue.clearQueue();
|
|
476
|
+
} catch (e) {
|
|
477
|
+
console.error("Failed to destroy SDK:", e);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
isSDKAvailable() {
|
|
481
|
+
return typeof window < "u" && "Deway" in window && !!window.Deway;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const l = new x(), k = {
|
|
485
|
+
init: (r) => l.init(r),
|
|
486
|
+
identify: (r) => l.identify(r),
|
|
487
|
+
reportEvent: (r, e) => l.reportEvent(r, e),
|
|
488
|
+
setUserProfile: (r) => l.setUserProfile(r),
|
|
489
|
+
showBubble: (r) => l.showBubble(r),
|
|
490
|
+
hideBubble: () => l.hideBubble(),
|
|
491
|
+
isBubbleVisible: () => l.isBubbleVisible(),
|
|
492
|
+
destroy: () => l.destroy()
|
|
493
|
+
};
|
|
494
|
+
typeof window < "u" && (window.Deway = k);
|
|
495
|
+
export {
|
|
496
|
+
k as default
|
|
497
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(d,u){typeof exports=="object"&&typeof module<"u"?module.exports=u():typeof define=="function"&&define.amd?define(u):(d=typeof globalThis<"u"?globalThis:d||self,d.Deway=u())})(this,(function(){"use strict";const d="deway-sdk-config",u=["Understanding intent","Reading web page","Browsing the docs","Enriching context","Validating understanding","Crafting response"];class b{saveConfig(e){if(window?.localStorage)try{const t=JSON.stringify(e);window.localStorage.setItem(d,t)}catch(t){console.warn("Failed to save SDK config to localStorage:",t)}}loadConfig(){if(window?.localStorage)try{const e=window.localStorage.getItem(d);return e?JSON.parse(e):null}catch(e){return console.warn("Failed to load SDK config from localStorage:",e),null}return null}getExcludedVendors(){return this.loadConfig()?.excludedVendors??[]}getAssistantName(){return this.loadConfig()?.assistantName??"Assistant"}getAiDisclaimer(){return this.loadConfig()?.aiDisclaimer}getThinkingMessages(){return this.loadConfig()?.thinkingMessages??u}getHumanHandoffUrl(){return this.loadConfig()?.humanHandoffUrl}}class s{static CACHE_KEY="deway-sdk-cache";static DB_NAME="DewaySdk";static DB_VERSION=1;static STORE_NAME="sdk";async cacheSDK(e,t,o,n){try{const c=(await this.openIndexedDB()).transaction([s.STORE_NAME],"readwrite").objectStore(s.STORE_NAME);await new Promise((w,S)=>{const f=c.put({id:"latest",code:e,version:t,checksum:o,signature:n,timestamp:Date.now()});f.onsuccess=()=>w(f.result),f.onerror=()=>S(f.error)}),console.debug("SDK cached in IndexedDB")}catch{try{const a=JSON.stringify({code:e,version:t,checksum:o,signature:n,timestamp:Date.now()});localStorage.setItem(s.CACHE_KEY,a),console.debug("SDK cached in localStorage")}catch(a){console.warn("Unable to cache SDK:",a)}}}async getCachedSDK(){try{const o=(await this.openIndexedDB()).transaction([s.STORE_NAME],"readonly").objectStore(s.STORE_NAME),n=await new Promise((i,a)=>{const c=o.get("latest");c.onsuccess=()=>i(c.result||null),c.onerror=()=>a(c.error)});if(n)return console.debug("Found cached SDK in IndexedDB"),n}catch{console.debug("IndexedDB unavailable, trying localStorage")}try{const e=localStorage.getItem(s.CACHE_KEY);if(e)return console.debug("Found cached SDK in localStorage"),JSON.parse(e)}catch(e){console.warn("Failed to read from localStorage:",e)}return null}openIndexedDB(){return new Promise((e,t)=>{const o=indexedDB.open(s.DB_NAME,s.DB_VERSION);o.onerror=()=>t(o.error),o.onsuccess=()=>e(o.result),o.onupgradeneeded=n=>{const i=n.target.result;i.objectStoreNames.contains(s.STORE_NAME)||i.createObjectStore(s.STORE_NAME,{keyPath:"id"})}})}}async function D(r){const t=new TextEncoder().encode(r),o=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(o)).map(i=>i.toString(16).padStart(2,"0")).join("")}function g(r){const e=atob(r),t=new Uint8Array(e.length);for(let o=0;o<e.length;o++)t[o]=e.charCodeAt(o);return t}function E(r){const e=new Uint8Array(r.length/2);for(let t=0;t<r.length;t+=2)e[t/2]=Number.parseInt(r.substr(t,2),16);return e}async function C(r,e,t){try{const o=g(r),n=E(e),i=g(t),a=await crypto.subtle.importKey("raw",o,{name:"Ed25519"},!1,["verify"]);return await crypto.subtle.verify("Ed25519",a,i,n)}catch(o){return console.error("Ed25519 signature verification failed:",o),!1}}async function K(r,e,t,o){if(!e||!t)throw new Error("SDK verification failed: Missing security headers");if(await D(r)!==e)throw new Error("SDK verification failed: Checksum mismatch - content tampered");if(!await C(o,e,t))throw new Error("SDK verification failed: Invalid signature - content tampered")}class v{cleanApiEndpoint(e){return e.trim().replace(/\/+$/,"")}async fetchSDK(e,t,o){try{console.info("Fetching Deway SDK from backend...");const n=this.cleanApiEndpoint(t),i=await fetch(`${n}/sdk-serve/sdk/v0`,{method:"GET",headers:{Accept:"application/javascript","deway-app-key":e,Origin:window?.location?.origin||""}});return i.ok?this.handleSuccessfulFetch(i,o):(console.warn(`Failed to fetch SDK: HTTP ${i.status}: ${i.statusText}`),null)}catch(n){return console.warn("Failed to fetch SDK from server:",n),null}}async handleSuccessfulFetch(e,t){const o=e.headers.get("x-sdk-checksum"),n=e.headers.get("x-sdk-version"),i=e.headers.get("x-sdk-signature"),a=await e.text();if(console.info(`Fetched Deway SDK version ${n}`),!n||!a||!o){const c=Object.fromEntries(e.headers.entries());throw console.error(`Failed to get required data from sdk fetch. Headers: ${JSON.stringify(c)}`),new Error("Invalid SDK response: missing version, code, or checksum")}return await K(a,o,i,t),{code:a,version:n,checksum:o,signature:i||""}}}class h{static MAX_QUEUE_SIZE=50;commandQueue=[];queueCommand(e,...t){this.commandQueue.length>=h.MAX_QUEUE_SIZE&&(console.warn(`Command queue full (${h.MAX_QUEUE_SIZE} commands). Discarding oldest command.`),this.commandQueue.shift()),this.commandQueue.push({method:e,args:t}),console.debug(`Queued command: ${e} (queue size: ${this.commandQueue.length})`)}replayQueuedCommands(){if(this.commandQueue.length===0)return;console.info(`Replaying ${this.commandQueue.length} queued commands`);const e=[...this.commandQueue];if(this.commandQueue=[],!this.isSDKAvailable()){console.warn("Deway SDK not available for command replay");return}for(const t of e)this.replayCommand(t)}clearQueue(){this.commandQueue=[]}isSDKAvailable(){return typeof window<"u"&&"Deway"in window&&!!window.Deway}replayCommand(e){try{const t=window.Deway,o=t?.[e.method];typeof o=="function"?o.apply(t,e.args):console.warn(`Method ${e.method} not found on Deway SDK`)}catch(t){console.error(`Failed to replay command ${e.method}:`,t)}}}class m{static CACHE_KEY="deway-remote-config-cache";async cacheRemoteConfig(e,t){try{const o={config:e,ttl_seconds:t,timestamp:Date.now()};localStorage.setItem(m.CACHE_KEY,JSON.stringify(o)),console.debug("Remote configuration cached in localStorage")}catch(o){console.warn("Failed to cache remote config:",o)}}async getCachedRemoteConfig(){try{const e=localStorage.getItem(m.CACHE_KEY);if(!e)return null;const t=JSON.parse(e);return console.debug("Found cached remote configuration"),t}catch(e){return console.warn("Failed to read cached remote config:",e),null}}isCacheValid(e,t){const o=Date.now(),n=e+t*1e3;return o<n}}class A{cleanApiEndpoint(e){return e.trim().replace(/\/+$/,"")}async fetchRemoteConfig(e,t){try{console.info("Fetching remote configuration from backend...");const o=this.cleanApiEndpoint(t),n=await fetch(`${o}/sdk-remote-config-serve/`,{method:"GET",headers:{Accept:"application/json","deway-app-key":e,Origin:window?.location?.origin||""}});if(n.ok){const i=await n.json();return console.info("Remote configuration fetched successfully"),i}return console.warn(`Failed to fetch remote config: HTTP ${n.status}: ${n.statusText}`),null}catch(o){return console.warn("Failed to fetch remote config from server:",o),null}}}class F{async executeSDK(e){return new Promise(t=>{try{if(!this.isDocumentReady()){console.error("Document is not available for script execution"),t(!1);return}const o=document.createElement("script");o.textContent=e,o.type="text/javascript";let n=!1;const i=a=>{n||(n=!0,this.cleanupScript(o),t(a))};o.onerror=()=>{console.error("Script execution failed"),i(!1)},o.onload=()=>i(!0),document.head.appendChild(o),setTimeout(()=>{!n&&this.verifySDKLoaded()?i(!0):n||(console.error("SDK execution timeout - Deway object not found"),i(!1))},100)}catch(o){console.error("Failed to execute SDK script:",o),t(!1)}})}isDocumentReady(){return typeof document<"u"&&document.head!=null}verifySDKLoaded(){return typeof window<"u"&&"Deway"in window&&!!window.Deway}cleanupScript(e){try{e.parentNode&&e.parentNode.removeChild(e)}catch(t){console.debug("Failed to cleanup script element:",t)}}}class x{validateConfig(e){return e?!e.appKey||e.appKey.trim().length===0?(console.error("Config.appKey is required and must be a non-empty string"),!1):e.apiEndpoint!==void 0&&!this.isValidUrl(e.apiEndpoint)?(console.error("Config.apiEndpoint must be a valid URL"),!1):e.publicKey!==void 0&&e.publicKey.trim().length===0?(console.error("Config.publicKey must be a non-empty string if provided"),!1):!0:(console.error("Config is required"),!1)}isValidUrl(e){try{return new URL(e),!0}catch{return!1}}}const y={apiEndpoint:"https://service.deway.app",publicKey:"9d3dBUvqyUQ7egd5j5uORdHSqZ7VFWOu+ud/SWt9WUY=",isDevelopment:!1,autoShowBubble:!1};class k{isLoaded=!1;isLoading=!1;cacheManager;scriptExecutor;commandQueue;sdkFetcher;configValidator;remoteConfigFetcher;remoteConfigCache;sdkConfigStore;constructor(){this.cacheManager=new s,this.scriptExecutor=new F,this.commandQueue=new h,this.sdkFetcher=new v,this.configValidator=new x,this.sdkConfigStore=new b,this.remoteConfigFetcher=new A,this.remoteConfigCache=new m}init(e){this.performInit(e).catch(t=>{console.error("Failed to initialize Deway SDK:",t)})}async performInit(e){try{if(!this.canInitialize())return;const t=this.isInitializationPayload(e),o=t?e.localConfig:e;if(!this.configValidator.validateConfig(o)){console.error("Invalid config provided to Deway SDK");return}this.isLoading=!0;const n=o.apiEndpoint||y.apiEndpoint,i=o.publicKey||y.publicKey,[a,c]=await Promise.all([this.fetchOrLoadSDK(o.appKey,n,i),this.fetchRemoteConfigWithCache(o,n)]);if(!a)return;const w=t?e:this.createInitializationPayload(c,o);if(!await this.scriptExecutor.executeSDK(a.code)){console.error("SDK execution failed");return}if(!this.initializeSDK(w))return;this.commandQueue.replayQueuedCommands(),this.isLoaded=!0}finally{this.isLoading=!1}}isInitializationPayload(e){return"localConfig"in e&&"remoteConfig"in e&&"defaults"in e}canInitialize(){return this.isLoaded?(console.warn("Deway SDK already initialized"),!1):this.isLoading?(console.warn("Deway SDK initialization already in progress"),!1):!0}async fetchRemoteConfigWithCache(e,t){this.sdkConfigStore.saveConfig(e);const o=await this.remoteConfigFetcher.fetchRemoteConfig(e.appKey,t);if(o)return await this.remoteConfigCache.cacheRemoteConfig(o.config,o.ttl_seconds),o;console.info("Using cached remote config as fallback");const n=await this.remoteConfigCache.getCachedRemoteConfig();return n?{config:n.config,ttl_seconds:n.ttl_seconds}:(console.warn("No remote config available (fetch failed and no cache)"),null)}createInitializationPayload(e,t){return{localConfig:t,remoteConfig:e?.config??null,defaults:y}}async fetchOrLoadSDK(e,t,o){const n=await this.sdkFetcher.fetchSDK(e,t,o);return n?(await this.cacheManager.cacheSDK(n.code,n.version,n.checksum,n.signature),n):(console.warn("Failed to fetch SDK from server, attempting cache fallback"),this.loadFromCache())}async loadFromCache(){const e=await this.cacheManager.getCachedSDK();return e?(console.info(`Loading cached Deway SDK version ${e.version}`),{code:e.code,version:e.version}):(console.error("SDK unavailable: Network error and no cached version found"),null)}initializeSDK(e){if(!this.isSDKAvailable())return console.error("SDK execution failed: Deway object not found after loading"),!1;try{return window.Deway?.init(e),console.info("Deway SDK initialized successfully with payload"),!0}catch(t){return console.error("Failed to initialize SDK with payload:",t),!1}}identify(e){try{this.isLoaded&&this.isSDKAvailable()?window.Deway?.identify(e):this.commandQueue.queueCommand("identify",e)}catch(t){console.error("Failed to identify user:",t)}}reportEvent(e,t){try{this.isLoaded&&this.isSDKAvailable()?window.Deway?.reportEvent(e,t):this.commandQueue.queueCommand("reportEvent",e,t)}catch(o){console.error("Failed to report event:",o)}}setUserProfile(e){try{this.isLoaded&&this.isSDKAvailable()?window.Deway?.setUserProfile(e):this.commandQueue.queueCommand("setUserProfile",e)}catch(t){console.error("Failed to set user profile:",t)}}showBubble(e){try{this.isLoaded&&this.isSDKAvailable()?window.Deway?.showBubble(e):this.commandQueue.queueCommand("showBubble",e)}catch(t){console.error("Failed to show bubble:",t)}}hideBubble(){try{this.isLoaded&&this.isSDKAvailable()?window.Deway?.hideBubble():this.commandQueue.queueCommand("hideBubble")}catch(e){console.error("Failed to hide bubble:",e)}}isBubbleVisible(){try{return this.isLoaded&&this.isSDKAvailable()?window.Deway?.isBubbleVisible()??!1:!1}catch(e){return console.error("Failed to check bubble visibility:",e),!1}}destroy(){try{this.isLoaded&&this.isSDKAvailable()?window.Deway?.destroy():this.commandQueue.queueCommand("destroy"),this.commandQueue.clearQueue()}catch(e){console.error("Failed to destroy SDK:",e)}}isSDKAvailable(){return typeof window<"u"&&"Deway"in window&&!!window.Deway}}const l=new k,p={init:r=>l.init(r),identify:r=>l.identify(r),reportEvent:(r,e)=>l.reportEvent(r,e),setUserProfile:r=>l.setUserProfile(r),showBubble:r=>l.showBubble(r),hideBubble:()=>l.hideBubble(),isBubbleVisible:()=>l.isBubbleVisible(),destroy:()=>l.destroy()};return typeof window<"u"&&(window.Deway=p),p}));
|
package/package.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deway-ai/web-sdk",
|
|
3
|
+
"version": "0.36.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Deway's Web SDK",
|
|
6
|
+
"main": "dist/loader.umd.js",
|
|
7
|
+
"module": "dist/loader.es.js",
|
|
8
|
+
"types": "dist/loader.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/loader.d.ts",
|
|
12
|
+
"import": "./dist/loader.es.js",
|
|
13
|
+
"require": "./dist/loader.umd.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/loader.*",
|
|
18
|
+
"dist/backend.*"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build:iframe": "node scripts/build-deway-bubble-iframe-bundle.js && node scripts/build-options-menu-iframe-bundle.js && node scripts/build-page-guide-iframe-bundle.js",
|
|
22
|
+
"build:iframe:dev": "node scripts/build-deway-bubble-iframe-bundle.js --dev && node scripts/build-options-menu-iframe-bundle.js --dev && node scripts/build-page-guide-iframe-bundle.js --dev",
|
|
23
|
+
"build:iframe:prod": "node scripts/build-deway-bubble-iframe-bundle.js && node scripts/build-options-menu-iframe-bundle.js && node scripts/build-page-guide-iframe-bundle.js",
|
|
24
|
+
"build:loader:dev": "vite build --config vite.config.loader.ts --mode development",
|
|
25
|
+
"build:loader:prod": "vite build --config vite.config.loader.ts --mode production",
|
|
26
|
+
"prebuild:backend:dev": "pnpm build:iframe:dev",
|
|
27
|
+
"build:backend:dev": "vite build --config vite.config.backend.ts --mode development",
|
|
28
|
+
"prebuild:backend:prod": "pnpm build:iframe:prod",
|
|
29
|
+
"build:backend:prod": "vite build --config vite.config.backend.ts --mode production",
|
|
30
|
+
"build:dev": "pnpm build:loader:dev && pnpm build:backend:dev",
|
|
31
|
+
"build:prod": "pnpm build:loader:prod && pnpm build:backend:prod",
|
|
32
|
+
"deploy:test": "node scripts/deploy-to-dev-env.js test",
|
|
33
|
+
"deploy:alon": "node scripts/deploy-to-dev-env.js alon",
|
|
34
|
+
"deploy:noy": "node scripts/deploy-to-dev-env.js noy",
|
|
35
|
+
"deploy:demo": "node scripts/deploy-to-dev-env.js demo",
|
|
36
|
+
"preserve:test": "pnpm deploy:test",
|
|
37
|
+
"serve:test": "http-server . -p 2999 -c-1 -o /demo-site/?env=test",
|
|
38
|
+
"preserve:alon": "pnpm deploy:alon",
|
|
39
|
+
"serve:alon": "http-server . -p 2999 -c-1 -o /demo-site/?env=alon",
|
|
40
|
+
"preserve:noy": "pnpm deploy:noy",
|
|
41
|
+
"serve:noy": "http-server . -p 2999 -c-1 -o /demo-site/?env=noy",
|
|
42
|
+
"preserve:demo": "pnpm deploy:demo",
|
|
43
|
+
"serve:demo": "http-server . -p 2999 -c-1 -o /demo-site/?env=demo",
|
|
44
|
+
"preserve:extension": "node ./scripts/version-burning.js && pnpm build:prod",
|
|
45
|
+
"serve:extension": "http-server . -p 2999 -c-1",
|
|
46
|
+
"postserve:extension": "node ./scripts/version-burning.js",
|
|
47
|
+
"preserve:prod": "node ./scripts/version-burning.js && pnpm build:prod",
|
|
48
|
+
"serve:prod": "http-server . -p 2999 -c-1",
|
|
49
|
+
"postserve:prod": "node ./scripts/version-burning.js",
|
|
50
|
+
"lint": "biome check --write .",
|
|
51
|
+
"typecheck:tests": "tsc --noEmit --project tsconfig.tests.json",
|
|
52
|
+
"test:unit": "vitest run --coverage --config vitest.config.ts",
|
|
53
|
+
"check": "pnpm lint && pnpm typecheck:tests && pnpm test:unit",
|
|
54
|
+
"test:e2e:ui": "TEST_ENV=noy playwright test --ui",
|
|
55
|
+
"test:e2e:debug": "TEST_ENV=noy playwright test --debug",
|
|
56
|
+
"test:e2e:headed": "TEST_ENV=noy playwright test --headed",
|
|
57
|
+
"test:e2e:test": "TEST_ENV=test playwright test",
|
|
58
|
+
"test:e2e:alon": "TEST_ENV=alon playwright test",
|
|
59
|
+
"test:e2e:noy": "TEST_ENV=noy playwright test"
|
|
60
|
+
},
|
|
61
|
+
"keywords": [
|
|
62
|
+
"walkthrough",
|
|
63
|
+
"guide",
|
|
64
|
+
"user-guide",
|
|
65
|
+
"product-tour",
|
|
66
|
+
"tour",
|
|
67
|
+
"tutorial",
|
|
68
|
+
"deway",
|
|
69
|
+
"theway",
|
|
70
|
+
"the-way"
|
|
71
|
+
],
|
|
72
|
+
"repository": {
|
|
73
|
+
"type": "git",
|
|
74
|
+
"url": "git+https://github.com/Deway-AI/deway-web-sdk.git"
|
|
75
|
+
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"markdown-it": "^14.1.0",
|
|
78
|
+
"pako": "^2.1.0"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@aws-sdk/client-cloudwatch-logs": "^3.919.0",
|
|
82
|
+
"@aws-sdk/client-s3": "^3.925.0",
|
|
83
|
+
"@axe-core/playwright": "^4.11.0",
|
|
84
|
+
"@biomejs/biome": "^2.2.4",
|
|
85
|
+
"@faker-js/faker": "^10.0.0",
|
|
86
|
+
"@playwright/test": "^1.55.1",
|
|
87
|
+
"@types/jsdom": "^21.1.7",
|
|
88
|
+
"@types/markdown-it": "^14.1.2",
|
|
89
|
+
"@types/node": "^22.18.1",
|
|
90
|
+
"@types/pako": "^2.0.4",
|
|
91
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
92
|
+
"axe-core": "^4.11.0",
|
|
93
|
+
"fishery": "^2.3.1",
|
|
94
|
+
"http-server": "^14.1.1",
|
|
95
|
+
"jsdom": "^26.1.0",
|
|
96
|
+
"ky": "^1.11.0",
|
|
97
|
+
"p-event": "^7.0.0",
|
|
98
|
+
"p-timeout": "^7.0.1",
|
|
99
|
+
"ts-node": "^10.9.2",
|
|
100
|
+
"typescript": "^5.9.2",
|
|
101
|
+
"vite": "7.1.11",
|
|
102
|
+
"vite-plugin-dts": "^4.5.4",
|
|
103
|
+
"vitest": "^3.2.4",
|
|
104
|
+
"zod": "^4.1.12"
|
|
105
|
+
},
|
|
106
|
+
"engines": {
|
|
107
|
+
"node": ">=22.0.0"
|
|
108
|
+
},
|
|
109
|
+
"packageManager": "pnpm@10.19.0"
|
|
110
|
+
}
|