@skipphq/desk-web 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Skipp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS IN CONTRACT OFFICERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # @skipphq/desk-web
2
+
3
+ Official JavaScript / TypeScript Web SDK for the **Skipp Desk** customer support chat widget.
4
+
5
+ Easily integrate live chat, support tickets, and identity verification into React, Vue, Next.js, or plain HTML websites.
6
+
7
+ ---
8
+
9
+ ## 📦 Installation
10
+
11
+ ```sh
12
+ npm install @skipphq/desk-web
13
+ # or
14
+ yarn add @skipphq/desk-web
15
+ # or
16
+ pnpm add @skipphq/desk-web
17
+ ```
18
+
19
+ ---
20
+
21
+ ## 🚀 Quick Start
22
+
23
+ ### 1. React / Next.js / Single Page Apps
24
+
25
+ ```ts
26
+ import { SkippDesk } from '@skipphq/desk-web';
27
+ import { useEffect } from 'react';
28
+
29
+ export function SupportWidget() {
30
+ useEffect(() => {
31
+ // Initialize Skipp Desk
32
+ SkippDesk.init({
33
+ workspaceId: 'YOUR_WORKSPACE_ID',
34
+ // Optional pre-authenticated backend JWT identity token
35
+ identityToken: userJwtToken,
36
+ user: {
37
+ name: 'Timi Adesina',
38
+ email: 'adesinatim@gmail.com',
39
+ },
40
+ });
41
+
42
+ // Listen to events
43
+ const unsub = SkippDesk.on('unread-count-change', (count) => {
44
+ console.log('Unread messages:', count);
45
+ });
46
+
47
+ return () => unsub();
48
+ }, []);
49
+
50
+ return (
51
+ <button onClick={() => SkippDesk.open()}>
52
+ Need help? Chat with us
53
+ </button>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ---
59
+
60
+ ### 2. HTML Module Import
61
+
62
+ ```html
63
+ <script type="module">
64
+ import { SkippDesk } from 'https://cdn.jsdelivr.net/npm/@skipphq/desk-web/+esm';
65
+
66
+ SkippDesk.init({
67
+ workspaceId: 'YOUR_WORKSPACE_ID',
68
+ user: { name: 'Alex' },
69
+ });
70
+ </script>
71
+ ```
72
+
73
+ ---
74
+
75
+ ### 3. Standalone Script Tag (Alternative Embed)
76
+
77
+ If you prefer not to use an NPM package or ESM import, you can embed the widget directly using a single `<script>` tag:
78
+
79
+ ```html
80
+ <!-- Embedded Widget Script -->
81
+ <script
82
+ src="https://cdn.skipp.cx/widget.js"
83
+ data-workspace-id="YOUR_WORKSPACE_ID"
84
+ data-position="right"
85
+ data-brand-color="#25282d">
86
+ </script>
87
+
88
+ <!-- Optional: Control widget via global window.SkippDesk -->
89
+ <script>
90
+ window.addEventListener('DOMContentLoaded', () => {
91
+ // Open widget
92
+ window.SkippDesk.open();
93
+
94
+ // Set logged-in user JWT identity token
95
+ window.SkippDesk.setIdentityToken('USER_JWT_TOKEN');
96
+ });
97
+ </script>
98
+ ```
99
+
100
+ Both methods support identical runtime controls (`open`, `close`, `toggle`, `setIdentityToken`, `logout`).
101
+
102
+ ---
103
+
104
+ ## 📖 API Reference
105
+
106
+ ### `SkippDesk.init(config)`
107
+ Initialize the widget on the page.
108
+
109
+ ```ts
110
+ SkippDesk.init({
111
+ workspaceId: string; // Required Workspace ID
112
+ identityToken?: string; // Optional backend-signed JWT token
113
+ apiBaseUrl?: string; // Custom API endpoint override
114
+ position?: 'left' | 'right'; // Position of launcher button
115
+ brandColor?: string; // Brand color override (#176b57)
116
+ welcomeMessage?: string; // Custom greeting message
117
+ user?: {
118
+ name?: string;
119
+ email?: string;
120
+ phone?: string;
121
+ };
122
+ });
123
+ ```
124
+
125
+ ### Methods
126
+
127
+ | Method | Description |
128
+ | :--- | :--- |
129
+ | `SkippDesk.open()` | Open the chat widget |
130
+ | `SkippDesk.close()` | Minimize the chat widget |
131
+ | `SkippDesk.toggle()` | Toggle open/closed state |
132
+ | `SkippDesk.show()` | Show launcher button if hidden |
133
+ | `SkippDesk.hide()` | Hide launcher button |
134
+ | `SkippDesk.setUser(user)` | Identify or update logged-in user details |
135
+ | `SkippDesk.setIdentityToken(token)` | Set/update JWT pre-auth token dynamically |
136
+ | `SkippDesk.logout()` | Clear session and log out customer |
137
+ | `SkippDesk.on(event, callback)` | Subscribe to events (`'ready'`, `'open'`, `'close'`, `'unread-count-change'`) |
138
+
139
+ ---
140
+
141
+ ## 🛡️ Pre-Authenticated Customer Identity
142
+
143
+ For seamless cross-device continuity without requiring users to fill out contact forms:
144
+
145
+ 1. Generate a JWT identity token on your backend signed with your Workspace identity secret.
146
+ 2. Pass `identityToken` during `init()` or call `SkippDesk.setIdentityToken(jwtToken)` upon user login.
147
+
148
+ ---
149
+
150
+ ## 📄 License
151
+
152
+ MIT © [Skipp](https://skipp.cx)
@@ -0,0 +1,66 @@
1
+ import type { SkippDeskConfig, SkippDeskEvent, SkippDeskEventListener, SkippDeskPosition, SkippDeskUser } from "./types.js";
2
+ export type { SkippDeskConfig, SkippDeskEvent, SkippDeskEventListener, SkippDeskPosition, SkippDeskUser, };
3
+ declare class SkippDeskSDK {
4
+ private config;
5
+ private listeners;
6
+ private ready;
7
+ private messageHandlerAttached;
8
+ /**
9
+ * Initialize Skipp Desk on the page with workspace and identity settings.
10
+ */
11
+ init(config: SkippDeskConfig): Promise<void>;
12
+ /**
13
+ * Open the chat widget.
14
+ */
15
+ open(): void;
16
+ /**
17
+ * Close/minimize the chat widget.
18
+ */
19
+ close(): void;
20
+ /**
21
+ * Toggle open/close state of the chat widget.
22
+ */
23
+ toggle(): void;
24
+ /**
25
+ * Show the launcher button if hidden.
26
+ */
27
+ show(): void;
28
+ /**
29
+ * Hide the launcher button.
30
+ */
31
+ hide(): void;
32
+ /**
33
+ * Set or update backend-signed JWT identity token dynamically.
34
+ */
35
+ setIdentityToken(token: string): void;
36
+ /**
37
+ * Identify or update logged-in customer info dynamically.
38
+ */
39
+ setUser(user: SkippDeskUser): void;
40
+ /**
41
+ * Log out the current user and clear visitor session state.
42
+ */
43
+ logout(): Promise<void>;
44
+ /**
45
+ * Reset visitor session and clear local widget state.
46
+ */
47
+ reset(): Promise<void>;
48
+ /**
49
+ * Subscribe to widget lifecycle events.
50
+ */
51
+ on(event: SkippDeskEvent, callback: SkippDeskEventListener): () => void;
52
+ /**
53
+ * Unsubscribe from widget lifecycle events.
54
+ */
55
+ off(event: SkippDeskEvent, callback: SkippDeskEventListener): void;
56
+ /**
57
+ * Check whether Skipp Desk is initialized and ready.
58
+ */
59
+ isReady(): boolean;
60
+ private emit;
61
+ private dispatchCommand;
62
+ private ensureMessageHandler;
63
+ private injectScript;
64
+ }
65
+ export declare const SkippDesk: SkippDeskSDK;
66
+ export default SkippDesk;
package/dist/index.js ADDED
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SkippDesk = void 0;
4
+ const DEFAULT_WIDGET_URL = "https://cdn.skipp.cx/widget.js";
5
+ const DEFAULT_API_BASE_URL = "https://api.zirrodesk.com";
6
+ const WIDGET_SCRIPT_ID = "skipp-desk-widget-script";
7
+ class SkippDeskSDK {
8
+ config = null;
9
+ listeners = new Map();
10
+ ready = false;
11
+ messageHandlerAttached = false;
12
+ /**
13
+ * Initialize Skipp Desk on the page with workspace and identity settings.
14
+ */
15
+ async init(config) {
16
+ if (!config.workspaceId) {
17
+ throw new Error("SkippDesk.init requires a valid workspaceId.");
18
+ }
19
+ this.config = {
20
+ ...config,
21
+ apiBaseUrl: (config.apiBaseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, ""),
22
+ widgetUrl: config.widgetUrl || DEFAULT_WIDGET_URL,
23
+ };
24
+ // Store pre-authenticated identity token if provided
25
+ if (this.config.identityToken && typeof window !== "undefined") {
26
+ try {
27
+ window.localStorage.setItem(`skipp-widget-identity-token:${this.config.workspaceId}`, this.config.identityToken);
28
+ window.localStorage.setItem(`zirro-desk-widget-identity-token:${this.config.workspaceId}`, this.config.identityToken);
29
+ }
30
+ catch {
31
+ // Ignore localStorage restrictions
32
+ }
33
+ }
34
+ // Attach window message listener for widget events
35
+ this.ensureMessageHandler();
36
+ // Inject widget script if not present
37
+ await this.injectScript();
38
+ // Notify listeners if widget is ready
39
+ if (this.ready) {
40
+ this.emit("ready", { workspaceId: this.config.workspaceId });
41
+ }
42
+ if (config.user) {
43
+ this.setUser(config.user);
44
+ }
45
+ if (config.autoOpen) {
46
+ this.open();
47
+ }
48
+ }
49
+ /**
50
+ * Open the chat widget.
51
+ */
52
+ open() {
53
+ this.dispatchCommand("open");
54
+ }
55
+ /**
56
+ * Close/minimize the chat widget.
57
+ */
58
+ close() {
59
+ this.dispatchCommand("close");
60
+ }
61
+ /**
62
+ * Toggle open/close state of the chat widget.
63
+ */
64
+ toggle() {
65
+ this.dispatchCommand("toggle");
66
+ }
67
+ /**
68
+ * Show the launcher button if hidden.
69
+ */
70
+ show() {
71
+ this.dispatchCommand("show");
72
+ }
73
+ /**
74
+ * Hide the launcher button.
75
+ */
76
+ hide() {
77
+ this.dispatchCommand("hide");
78
+ }
79
+ /**
80
+ * Set or update backend-signed JWT identity token dynamically.
81
+ */
82
+ setIdentityToken(token) {
83
+ if (!this.config)
84
+ return;
85
+ try {
86
+ window.localStorage.setItem(`skipp-widget-identity-token:${this.config.workspaceId}`, token);
87
+ window.localStorage.setItem(`zirro-desk-widget-identity-token:${this.config.workspaceId}`, token);
88
+ }
89
+ catch {
90
+ // Ignore
91
+ }
92
+ this.dispatchCommand("setIdentityToken", { token });
93
+ }
94
+ /**
95
+ * Identify or update logged-in customer info dynamically.
96
+ */
97
+ setUser(user) {
98
+ if (!this.config)
99
+ return;
100
+ this.dispatchCommand("setUser", user);
101
+ }
102
+ /**
103
+ * Log out the current user and clear visitor session state.
104
+ */
105
+ async logout() {
106
+ await this.reset();
107
+ }
108
+ /**
109
+ * Reset visitor session and clear local widget state.
110
+ */
111
+ async reset() {
112
+ if (this.config && typeof window !== "undefined") {
113
+ try {
114
+ window.localStorage.removeItem(`skipp-widget-identity-token:${this.config.workspaceId}`);
115
+ window.localStorage.removeItem(`zirro-desk-widget-identity-token:${this.config.workspaceId}`);
116
+ window.localStorage.removeItem(`zirro-desk-widget-visitor-token:${this.config.workspaceId}`);
117
+ }
118
+ catch {
119
+ // Ignore
120
+ }
121
+ }
122
+ this.dispatchCommand("logout");
123
+ }
124
+ /**
125
+ * Subscribe to widget lifecycle events.
126
+ */
127
+ on(event, callback) {
128
+ if (!this.listeners.has(event)) {
129
+ this.listeners.set(event, new Set());
130
+ }
131
+ this.listeners.get(event).add(callback);
132
+ return () => this.off(event, callback);
133
+ }
134
+ /**
135
+ * Unsubscribe from widget lifecycle events.
136
+ */
137
+ off(event, callback) {
138
+ const callbacks = this.listeners.get(event);
139
+ if (callbacks) {
140
+ callbacks.delete(callback);
141
+ }
142
+ }
143
+ /**
144
+ * Check whether Skipp Desk is initialized and ready.
145
+ */
146
+ isReady() {
147
+ return this.ready;
148
+ }
149
+ // Private helpers
150
+ emit(event, data) {
151
+ const callbacks = this.listeners.get(event);
152
+ if (callbacks) {
153
+ callbacks.forEach((cb) => {
154
+ try {
155
+ cb(data);
156
+ }
157
+ catch (err) {
158
+ console.error(`[SkippDesk] Error in '${event}' event listener:`, err);
159
+ }
160
+ });
161
+ }
162
+ }
163
+ dispatchCommand(action, data = {}) {
164
+ if (typeof window === "undefined")
165
+ return;
166
+ // Call window.SkippDesk if exposed by widget.js
167
+ const globalSkipp = window.SkippDesk;
168
+ if (globalSkipp && typeof globalSkipp[action] === "function") {
169
+ globalSkipp[action](data);
170
+ return;
171
+ }
172
+ // Fallback: postMessage to iframe or window
173
+ const messagePayload = {
174
+ source: "skipp-desk-sdk",
175
+ action,
176
+ workspaceId: this.config?.workspaceId,
177
+ ...data,
178
+ };
179
+ window.postMessage(messagePayload, "*");
180
+ }
181
+ ensureMessageHandler() {
182
+ if (this.messageHandlerAttached || typeof window === "undefined")
183
+ return;
184
+ window.addEventListener("message", (event) => {
185
+ if (!event.data)
186
+ return;
187
+ let payload = event.data;
188
+ if (typeof payload === "string") {
189
+ try {
190
+ payload = JSON.parse(payload);
191
+ }
192
+ catch {
193
+ return;
194
+ }
195
+ }
196
+ if (payload.source !== "skipp-desk" && payload.source !== "zirro-desk")
197
+ return;
198
+ switch (payload.type) {
199
+ case "ready":
200
+ this.ready = true;
201
+ this.emit("ready", payload);
202
+ break;
203
+ case "open":
204
+ this.emit("open");
205
+ break;
206
+ case "close":
207
+ this.emit("close");
208
+ break;
209
+ case "unread_count_change":
210
+ case "unread-count-change":
211
+ this.emit("unread-count-change", payload.unreadCount ?? payload.count ?? 0);
212
+ break;
213
+ case "ticket_created":
214
+ case "ticket-created":
215
+ this.emit("ticket-created", payload);
216
+ break;
217
+ case "error":
218
+ this.emit("error", new Error(payload.message || "Skipp Desk Widget Error"));
219
+ break;
220
+ }
221
+ });
222
+ this.messageHandlerAttached = true;
223
+ }
224
+ injectScript() {
225
+ if (typeof document === "undefined" || !this.config)
226
+ return Promise.resolve();
227
+ if (document.getElementById(WIDGET_SCRIPT_ID)) {
228
+ return Promise.resolve();
229
+ }
230
+ return new Promise((resolve, reject) => {
231
+ const script = document.createElement("script");
232
+ script.id = WIDGET_SCRIPT_ID;
233
+ script.async = true;
234
+ script.src = this.config.widgetUrl || DEFAULT_WIDGET_URL;
235
+ script.dataset.workspaceId = this.config.workspaceId;
236
+ script.dataset.apiBaseUrl = this.config.apiBaseUrl;
237
+ if (this.config.position) {
238
+ script.dataset.position = this.config.position;
239
+ }
240
+ script.onload = () => {
241
+ this.ready = true;
242
+ resolve();
243
+ };
244
+ script.onerror = (err) => {
245
+ const error = new Error(`Failed to load Skipp Desk widget script from ${script.src}`);
246
+ this.emit("error", error);
247
+ reject(error);
248
+ };
249
+ document.head.appendChild(script);
250
+ });
251
+ }
252
+ }
253
+ exports.SkippDesk = new SkippDeskSDK();
254
+ exports.default = exports.SkippDesk;
@@ -0,0 +1,52 @@
1
+ export type SkippDeskPosition = "left" | "right";
2
+ export type SkippDeskUser = {
3
+ id?: string;
4
+ name?: string;
5
+ email?: string;
6
+ phone?: string;
7
+ };
8
+ export type SkippDeskConfig = {
9
+ /**
10
+ * Your Skipp Desk Workspace ID (Required)
11
+ */
12
+ workspaceId: string;
13
+ /**
14
+ * Base URL for the Skipp Desk API (Defaults to production API if omitted)
15
+ */
16
+ apiBaseUrl?: string;
17
+ /**
18
+ * Hosted URL of the widget script (Defaults to https://cdn.skipp.cx/widget.js if omitted)
19
+ */
20
+ widgetUrl?: string;
21
+ /**
22
+ * Pre-authenticated JWT identity token issued by your backend
23
+ */
24
+ identityToken?: string;
25
+ /**
26
+ * User identity information
27
+ */
28
+ user?: SkippDeskUser;
29
+ /**
30
+ * Position of the chat widget button
31
+ */
32
+ position?: SkippDeskPosition;
33
+ /**
34
+ * Primary brand color override (hex format)
35
+ */
36
+ brandColor?: string;
37
+ /**
38
+ * Custom welcome message override
39
+ */
40
+ welcomeMessage?: string;
41
+ /**
42
+ * Auto-open widget after initialization
43
+ */
44
+ autoOpen?: boolean;
45
+ };
46
+ export type SkippDeskEvent = "ready" | "open" | "close" | "unread-count-change" | "ticket-created" | "error";
47
+ export type SkippDeskEventListener = (data?: Record<string, unknown> | number | string | Error) => void;
48
+ export type SkippDeskCommand = ["init", SkippDeskConfig] | ["open"] | ["close"] | ["toggle"] | ["show"] | ["hide"] | ["setIdentityToken", string] | ["setUser", SkippDeskUser] | ["logout"] | ["reset"] | ["on", SkippDeskEvent, SkippDeskEventListener] | ["off", SkippDeskEvent, SkippDeskEventListener];
49
+ export interface SkippDeskGlobalQueue {
50
+ q?: SkippDeskCommand[];
51
+ (...args: SkippDeskCommand): void;
52
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@skipphq/desk-web",
3
+ "version": "0.3.0",
4
+ "description": "Web SDK for Skipp Desk support widget",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "module": "dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "test": "npm run build && node --test tests/*.test.cjs",
23
+ "prepack": "npm run build",
24
+ "publish:check": "npm pack --dry-run"
25
+ },
26
+ "homepage": "https://www.npmjs.com/package/@skipphq/desk-web",
27
+ "keywords": [
28
+ "skipp",
29
+ "skipp-desk",
30
+ "customer-support",
31
+ "help-desk",
32
+ "chat-widget",
33
+ "web-sdk",
34
+ "typescript"
35
+ ],
36
+ "sideEffects": false,
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "~5.9.3"
42
+ },
43
+ "license": "MIT"
44
+ }