@coxwave/tap-sdk 0.0.11 → 0.1.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 CHANGED
@@ -1,9 +1,35 @@
1
1
  # @coxwave/tap-sdk
2
2
 
3
- TapSDK is a lightweight JavaScript SDK for embedding the EduTap AI tutor inside any web property. It renders the toggle button, manages the iframe chat surface, and provides a structured messaging layer for bi-directional communication with the host page.
3
+ TapSDK is a lightweight JavaScript loader (~2-3KB) that dynamically loads the EduTap AI tutor from CDN. It provides the same familiar API for embedding interactive chat into any web property, but with the flexibility of instant updates without requiring npm package updates.
4
+
5
+ ## Architecture
6
+
7
+ This package is a **bootloader** that follows the ChannelTalk pattern:
8
+
9
+ 1. **Loader Package** (`@coxwave/tap-sdk`) - Published to npm (~2.3KB)
10
+ - Provides the familiar `TapSDK` API
11
+ - Dynamically loads the actual SDK from CloudFront CDN
12
+ - Handles version management and integrity checks
13
+ - Only needs updates when API surface changes
14
+
15
+ 2. **Core SDK** (`tap-kit-core`) - Deployed to S3/CloudFront
16
+ - Contains the actual implementation (UI, iframe management, etc.)
17
+ - Can be updated instantly without npm publishing
18
+ - Version controlled with timestamps (YYYYMMDDHHMMSS format)
19
+ - URL: `https://files.edutap.ai/tap-sdk/core-{VERSION}.js`
20
+
21
+ ### Why This Architecture?
22
+
23
+ - **Instant Updates**: Deploy bug fixes and features to all users immediately
24
+ - **Smaller npm Package**: ~2-3KB loader vs ~20KB+ full SDK
25
+ - **Version Control**: Centralized version management via CDN
26
+ - **Integrity Checks**: Subresource Integrity (SRI) ensures security
27
+ - **Backward Compatibility**: Same API surface, seamless for existing users
4
28
 
5
29
  ## Installation
6
30
 
31
+ ### via npm
32
+
7
33
  ```bash
8
34
  npm install @coxwave/tap-sdk
9
35
  # or
@@ -12,8 +38,21 @@ pnpm add @coxwave/tap-sdk
12
38
  yarn add @coxwave/tap-sdk
13
39
  ```
14
40
 
41
+ ### Understanding the Loader
42
+
43
+ When you install `@coxwave/tap-sdk` from npm, you get a small loader that:
44
+
45
+ 1. Imports the package normally: `import TapSDK from '@coxwave/tap-sdk'`
46
+ 2. On instantiation, loads the core SDK from `https://files.edutap.ai/tap-sdk/core-{VERSION}.js`
47
+ 3. Proxies all API calls to the loaded core SDK
48
+ 4. Verifies integrity using SHA-384 hashes
49
+
50
+ **No code changes required** - the API is identical to the previous monolithic SDK.
51
+
15
52
  ## Quick Start
16
53
 
54
+ ### ES Modules (npm)
55
+
17
56
  ```ts
18
57
  import TapSDK from "@coxwave/tap-sdk";
19
58
 
@@ -28,17 +67,34 @@ await sdk.init({
28
67
  courseId: "course-456",
29
68
  clipId: null,
30
69
  },
31
- customStyles: {
32
- chatBody: {
33
- position: { top: "64px", right: "32px" },
34
- width: "360px",
35
- height: "calc(100% - 128px)",
36
- borderRadius: "18px",
37
- },
70
+ container: {
71
+ position: { top: "64px", right: "32px" },
72
+ width: "360px",
73
+ height: "calc(100% - 128px)",
74
+ borderRadius: "18px",
38
75
  },
39
76
  });
40
77
  ```
41
78
 
79
+ ### How It Works
80
+
81
+ ```mermaid
82
+ sequenceDiagram
83
+ participant App as Your App
84
+ participant Loader as tap-sdk (npm)
85
+ participant CDN as CloudFront CDN
86
+ participant Core as Core SDK
87
+
88
+ App->>Loader: import TapSDK
89
+ App->>Loader: new TapSDK(config)
90
+ Loader->>CDN: GET /tap-sdk/core-{VERSION}.js
91
+ CDN-->>Loader: JavaScript Bundle
92
+ Loader->>Core: Initialize with config
93
+ App->>Loader: sdk.init(params)
94
+ Loader->>Core: Forward init call
95
+ Core-->>App: Chat Interface Ready
96
+ ```
97
+
42
98
  On initialization the SDK:
43
99
 
44
100
  - ensures it is running in a browser (guards against SSR)
@@ -52,29 +108,30 @@ On initialization the SDK:
52
108
 
53
109
  The SDK will attach alarm badges and click handlers to the element with the provided `buttonId`.
54
110
 
55
- ### Chat body
111
+ ### Chat container
56
112
 
57
- The chat surface is a fixed container (`#cw-chat-body`). You can override:
113
+ The chat surface is a fixed-position container. Customize via the `container` option:
58
114
 
59
115
  - `position` — partial `{ top/right/bottom/left }` values (strings with units)
60
116
  - `width` / `height` — strings with CSS units
61
- - `borderRadius`
117
+ - `borderRadius` — border radius (e.g., "16px")
62
118
 
63
- The container gracefully hides itself when the chat closes and expands when EduTap opens embedded PDFs.
119
+ The container automatically shows/hides when chat opens/closes and expands when EduTap displays embedded PDFs.
64
120
 
65
121
  ### Alarm notifications & pop-ups
66
122
 
67
- EduTap can send rich alarm notifications and HTML pop-ups over the iframe channel. The SDK fades alarms in/out, handles clicks, and renders pop-ups next to the chat container while inheriting relevant sizing from `chatBody` styles.
123
+ EduTap can send rich alarm notifications and HTML pop-ups over the iframe channel. The SDK automatically fades alarms in/out, handles clicks, and renders pop-ups next to the chat container.
68
124
 
69
125
  ## Runtime API
70
126
 
71
127
  ### `init(options)`
72
128
 
73
- Initializes the iframe, performs the handshake, and wires up UI controls. Required options:
129
+ Initializes the iframe, performs the handshake, and wires up UI controls.
74
130
 
75
- - `buttonId` – string (required) - ID of the HTML element to attach the toggle button to
76
- - `course` – minimal course context (`userId`, `courseId`, `clipId`)
77
- - `customStyles?``{ chatBody? }` - optional UI customization
131
+ **Options:**
132
+ - `buttonId` (required) ID of the HTML element to attach the toggle button to
133
+ - `course` (required) Course context with `userId`, `courseId`, `clipId`
134
+ - `container?` – Optional UI customization with `position`, `width`, `height`, `borderRadius`
78
135
 
79
136
  ### `events.seekTimeline({ clipId, clipPlayHead })`
80
137
 
@@ -116,26 +173,98 @@ sdk.events.onPdfOpen(() => console.log("PDF overlay opened"));
116
173
  sdk.events.onPdfClose(() => console.log("PDF overlay closed"));
117
174
  ```
118
175
 
119
- ## TypeScript support
176
+ ## TypeScript Support
120
177
 
121
- Type definitions ship with the package. Notable exports:
178
+ Type definitions are included with the package. Key exports:
179
+
180
+ ```ts
181
+ import type {
182
+ TapSDKConfig,
183
+ TapSDKInitParams,
184
+ Course,
185
+ ContainerStyle,
186
+ PositionType,
187
+ SeekTimelineParamsType,
188
+ AlarmMessageInstanceType,
189
+ } from "@coxwave/tap-sdk";
190
+ ```
122
191
 
123
- - `TapSDKConfig`
124
- - `Course`
125
- - `CustomStyles`
126
- - `ChatBodyStyle`
127
- - `SeekTimelineParamsType`
128
- - `AlarmMessageInstanceType`
192
+ All iframe message contracts are sourced from `@coxwave/tap-messages` and validated with valibot schemas.
193
+
194
+ ## Version Management
195
+
196
+ ### How It Works
197
+
198
+ The npm package (`@coxwave/tap-sdk`) simply loads the CDN loader script:
199
+
200
+ ```typescript
201
+ const CDN_LOADER_URL = 'https://files.edutap.ai/tap-sdk/loader.js';
202
+ ```
129
203
 
130
- All message contracts are sourced from `@coxwave/tap-messages` and validated with `valibot` during the iframe handshake.
204
+ The CDN loader (`tap-kit-core/loader.ts`) handles all version management:
205
+ 1. Fetches `versions.json` to get the latest version
206
+ 2. Loads the appropriate `tap-kit-{version}.js` with SRI verification
207
+ 3. Initializes the actual SDK
131
208
 
132
- ## Browser support
209
+ ### Deployment Workflow
210
+
211
+ **For SDK updates (most common):**
212
+ ```bash
213
+ cd packages/tap-kit-core
214
+ pnpm build
215
+ ./scripts/deploy-s3.sh
216
+ ```
217
+
218
+ This deploys to CDN and all users automatically get the update.
219
+
220
+ **For npm package updates (rare):**
221
+ ```bash
222
+ cd packages/tap-sdk
223
+ pnpm build
224
+ pnpm publish:npm
225
+ ```
226
+
227
+ Only needed when the API surface changes (new methods, types, etc).
228
+
229
+ ## Security Considerations
230
+
231
+ ### Content Security Policy (CSP)
232
+
233
+ If your site uses CSP headers, add these directives:
234
+
235
+ ```http
236
+ Content-Security-Policy:
237
+ script-src 'self' https://files.edutap.ai;
238
+ style-src 'self' 'unsafe-inline';
239
+ connect-src 'self' https://your-tap-api-domain.com;
240
+ frame-src 'self' https://your-edutap-domain.com;
241
+ ```
242
+
243
+ **Important:**
244
+ - `script-src` must include `https://files.edutap.ai` for CDN loading
245
+ - `style-src 'unsafe-inline'` is required for injected CSS (Vanilla Extract)
246
+
247
+ ### Subresource Integrity (SRI)
248
+
249
+ The loader verifies CDN files using SHA-384 integrity checks:
250
+
251
+ ```typescript
252
+ // In loader.ts
253
+ script.integrity = CDN_INTEGRITY;
254
+ script.crossOrigin = 'anonymous';
255
+ ```
256
+
257
+ This ensures the loaded SDK hasn't been tampered with, even if CloudFront is compromised.
258
+
259
+ ## Browser Support
133
260
 
134
261
  - Chrome 60+
135
262
  - Firefox 60+
136
263
  - Safari 12+
137
264
  - Edge 79+
138
265
 
266
+ Requires modern browsers with ES2020 support and `postMessage` API.
267
+
139
268
  ## License
140
269
 
141
270
  MIT © 2025 Coxwave
package/dist/index.d.cts CHANGED
@@ -1,49 +1,12 @@
1
- import { ChatInitMessage, ChatOpenMessage, ChatCloseMessage, TimelineSeekMessage, AlarmClickMessage, PopUpCloseMessage, PdfEnlargedMessage, PdfShrinkedMessage, ChatInitiatedMessage, ChatOpenedMessage, ChatClosedMessage, AlarmFadeInMessage, PopUpOpenMessage, PdfOpenMessage, PdfCloseMessage, AlarmMessageInstanceType } from '@coxwave/tap-messages';
1
+ import { AlarmMessageInstanceType } from '@coxwave/tap-messages';
2
+ export { AlarmMessageInstanceType, AlarmType } from '@coxwave/tap-messages';
2
3
 
3
- type Unsubscribe = () => void;
4
- type Message = {
5
- type: string;
6
- [key: string]: any;
7
- };
8
- declare abstract class Messenger<TToTargetMessages extends Message = Message, TFromTargetMessage extends Message = Message> {
9
- protected listeners: Map<TFromTargetMessage["type"], ((event: MessageEvent) => void)[]>;
10
- protected unifiedMessageHandler: ((event: MessageEvent) => void) | null;
11
- protected on<T extends TFromTargetMessage["type"]>(messageType: T, callback: (data: Extract<TFromTargetMessage, {
12
- type: T;
13
- }>) => void): Unsubscribe;
14
- removeListener(messageType: TFromTargetMessage["type"]): void;
15
- removeAllListeners(): void;
16
- postMessage(message: TToTargetMessages): boolean;
17
- protected abstract isValidOrigin(event: MessageEvent): boolean;
18
- protected abstract getMessageTarget(): Window | null;
19
- protected abstract getTargetOrigin(): string;
20
- }
21
-
22
- interface HandshakeOptions {
23
- timeout?: number;
24
- maxRetries?: number;
25
- retryInterval?: number;
26
- }
27
-
28
- interface LogEvent {
29
- action: string;
30
- category: string;
31
- label?: string;
32
- value?: number;
33
- customParams?: Record<string, any>;
34
- }
35
- interface EventMiddleware {
36
- handle(event: LogEvent): void;
37
- }
38
- declare class EventLogger {
39
- private middlewares;
40
- addMiddleware(middleware: EventMiddleware): void;
41
- removeMiddleware(middleware: EventMiddleware): void;
42
- log(event: LogEvent): void;
43
- logChatAction(action: "opened" | "closed" | "initialized", customParams?: Record<string, any>): void;
44
- logButtonClick(buttonType: "toggle" | "alarm", state?: string, customParams?: Record<string, any>): void;
45
- logSDKInitialization(success: boolean, startTime: number, error?: string): void;
46
- }
4
+ /**
5
+ * TapSDK Type Definitions
6
+ *
7
+ * Re-exported types for API compatibility with the loader.
8
+ * These types match the actual SDK implementation in tap-sdk-cdn.
9
+ */
47
10
 
48
11
  type TapSDKConfig = {
49
12
  apiKey: string;
@@ -71,155 +34,86 @@ type PositionType = {
71
34
  right?: string;
72
35
  bottom?: string;
73
36
  };
74
-
75
37
  type SeekTimelineParamsType = {
76
38
  clipId: string;
77
39
  clipPlayHead: number;
78
40
  };
79
41
 
80
- type ToTapMessage = ChatInitMessage | ChatOpenMessage | ChatCloseMessage | TimelineSeekMessage | AlarmClickMessage | PopUpCloseMessage | PdfEnlargedMessage | PdfShrinkedMessage;
81
- type FromTapMessage = ChatInitiatedMessage | ChatOpenedMessage | ChatClosedMessage | AlarmFadeInMessage | PopUpOpenMessage | PdfOpenMessage | PdfCloseMessage | TimelineSeekMessage;
82
- interface ChatInitConfig {
83
- course: Course;
84
- container?: ContainerStyle;
85
- }
86
- declare class TapIframeBridge extends Messenger<ToTapMessage, FromTapMessage> {
87
- #private;
88
- protected hostOrigin: string;
89
- protected apiKey: string;
90
- protected iframe: HTMLIFrameElement | null;
91
- private eventLogger;
92
- constructor({ hostOrigin, apiKey, eventLogger, }: {
93
- hostOrigin: string;
94
- apiKey: string;
95
- eventLogger?: EventLogger;
96
- });
97
- protected isValidOrigin(_event: MessageEvent): boolean;
98
- protected getMessageTarget(): Window | null;
99
- protected getTargetOrigin(): string;
100
- renderIframe(iframeRootElement?: HTMLElement): Promise<HTMLIFrameElement>;
101
- hasIframe(): boolean;
102
- ensureIframe(rootElement?: HTMLElement): Promise<HTMLIFrameElement>;
103
- removeIframe(): void;
104
- postToTap: (message: ToTapMessage) => boolean;
105
- listenToTap: <T extends "timeline:seek" | "chat:opened" | "chat:initiated" | "chat:closed" | "alarm:fadeIn" | "popUp:open" | "pdf:open" | "pdf:close">(messageType: T, callback: (data: Extract<TimelineSeekMessage, {
106
- type: T;
107
- }> | Extract<ChatInitiatedMessage, {
108
- type: T;
109
- }> | Extract<ChatOpenedMessage, {
110
- type: T;
111
- }> | Extract<ChatClosedMessage, {
112
- type: T;
113
- }> | Extract<AlarmFadeInMessage, {
114
- type: T;
115
- }> | Extract<PopUpOpenMessage, {
116
- type: T;
117
- }> | Extract<PdfOpenMessage, {
118
- type: T;
119
- }> | Extract<PdfCloseMessage, {
120
- type: T;
121
- }>) => void) => () => void;
122
- performHandshake(config: ChatInitConfig, options?: HandshakeOptions): Promise<ChatInitiatedMessage>;
123
- static defaultHandshakeOptions: HandshakeOptions;
124
- }
42
+ /**
43
+ * TapSDK - npm package wrapper
44
+ *
45
+ * Simply loads the CDN loader which handles version management and SDK loading.
46
+ * This keeps the npm package minimal and allows instant updates via CDN.
47
+ */
125
48
 
126
- declare class EventManager {
127
- private iframeBridge;
128
- constructor(iframeBridge: TapIframeBridge);
129
- /**
130
- * Updates timeline position in the chat interface
131
- * @param params - Timeline seek parameters
132
- */
133
- seekTimeline({ clipId, clipPlayHead }: SeekTimelineParamsType): void;
134
- onTimelineSeek(handler: (clipPlayHead: number, clipId: string) => void): void;
135
- onChatOpened(handler: () => void): void;
136
- onChatClosed(handler: () => void): void;
137
- onChatInitiated(handler: () => void): void;
138
- onAlarmFadeIn(handler: (messageInfo: AlarmMessageInstanceType) => void): void;
139
- onPopUpOpen(handler: (popUpInfo: PopUpOpenMessage["popUpInfo"]) => void): void;
140
- onPdfOpen(handler: () => void): void;
141
- onPdfClose(handler: () => void): void;
49
+ type TapSDKConstructor = new (config: TapSDKConfig) => TapSDKInstance;
50
+ declare global {
51
+ interface Window {
52
+ TapSDK?: TapSDKConstructor;
53
+ __TAP_SDK_LOADER_LOADED__?: boolean;
54
+ __TAP_SDK_LOADER_LOADING__?: Promise<void>;
55
+ }
56
+ }
57
+ interface TapSDKInstance {
58
+ events: {
59
+ seekTimeline: (params: SeekTimelineParamsType) => void;
60
+ onTimelineSeek: (callback: (clipPlayHead: number, clipId: string) => void) => void;
61
+ onChatInitiated: (handler: () => void) => void;
62
+ onChatOpened: (handler: () => void) => void;
63
+ onChatClosed: (handler: () => void) => void;
64
+ onAlarmFadeIn: (handler: (messageInfo: AlarmMessageInstanceType) => void) => void;
65
+ onPopUpOpen: (handler: (popUpInfo: any) => void) => void;
66
+ onPdfOpen: (handler: () => void) => void;
67
+ onPdfClose: (handler: () => void) => void;
68
+ };
69
+ isOpen: boolean;
70
+ isInitialized: boolean;
71
+ init(params: TapSDKInitParams): Promise<void>;
72
+ destroy(): void;
73
+ initChat(params: TapSDKInitParams): Promise<void>;
74
+ postChatInfo(params: {
75
+ clipId: string;
76
+ clipPlayHead: number;
77
+ }): Promise<void>;
78
+ getTimelineInfo(params: {
79
+ callback: (clipPlayHead: number, clipId: string) => void;
80
+ }): Promise<void>;
142
81
  }
143
-
144
82
  /**
145
- * TapSDK - Interactive chat SDK with toggle button and iframe communication
83
+ * TapSDK Wrapper Class
146
84
  *
147
- * @example
148
- * ```typescript
149
- * const sdk = new TapSDK({ apiKey: 'your-key' });
150
- * await sdk.init({ course: { userId: 'user1', courseId: 'course1' } });
151
- * ```
85
+ * Loads the CDN loader and proxies all calls to the actual SDK
152
86
  */
153
- declare class TapSDK {
154
- private apiKey;
155
- private iframeBridge;
156
- events: EventManager;
157
- private eventLogger;
158
- private container;
159
- private button;
160
- private alarm;
161
- private containerVisible;
162
- private isPdfOpen;
163
- private _isInitialized;
164
- /**
165
- * Creates a new TapSDK instance
166
- * @param config - SDK configuration options
167
- */
168
- constructor({ apiKey }: TapSDKConfig);
87
+ declare class TapSDK implements TapSDKInstance {
88
+ private config;
89
+ private sdkInstance?;
90
+ private loadPromise;
91
+ constructor(config: TapSDKConfig);
92
+ private loadAndInitialize;
93
+ private ensureLoaded;
94
+ get events(): {
95
+ seekTimeline: (params: SeekTimelineParamsType) => void;
96
+ onTimelineSeek: (callback: (clipPlayHead: number, clipId: string) => void) => void;
97
+ onChatInitiated: (handler: () => void) => void;
98
+ onChatOpened: (handler: () => void) => void;
99
+ onChatClosed: (handler: () => void) => void;
100
+ onAlarmFadeIn: (handler: (messageInfo: AlarmMessageInstanceType) => void) => void;
101
+ onPopUpOpen: (handler: (popUpInfo: any) => void) => void;
102
+ onPdfOpen: (handler: () => void) => void;
103
+ onPdfClose: (handler: () => void) => void;
104
+ };
169
105
  get isOpen(): boolean;
170
106
  get isInitialized(): boolean;
171
- /**
172
- * Initializes the TapSDK with course data and optional container styles
173
- * @param params - Initialization parameters
174
- * @param params.course - Course information for chat context
175
- * @param params.container - Optional container styling options
176
- * @throws {Error} When initialization fails
177
- */
178
- init({ buttonId, course, container }: TapSDKInitParams): Promise<void>;
179
- /**
180
- * Destroys the SDK instance and cleans up all resources
181
- */
107
+ init(params: TapSDKInitParams): Promise<void>;
182
108
  destroy(): void;
183
- private setupEventLogger;
184
- private toggleChatOpen;
185
- private validateEnvironment;
186
- private setupContainer;
187
- private updateContainerStyles;
188
- private setupButton;
189
- private setupEventListeners;
190
- /**
191
- * Update container styles dynamically
192
- */
193
- updateStyles(container: ContainerStyle): void;
194
- /**
195
- * POC: Dynamically set container size
196
- */
197
- setContainerSize(width: string, height: string): void;
198
- /**
199
- * POC: Animate container resize
200
- */
201
- animateContainerResize(width: string, height: string, duration?: number): void;
202
- /**
203
- * POC: Set responsive mode
204
- */
205
- setResponsiveMode(mode: "compact" | "normal" | "expanded"): void;
206
- /**
207
- * @deprecated Use `init` method instead. Will be removed in v1.0.0
208
- */
209
109
  initChat(params: TapSDKInitParams): Promise<void>;
210
- /**
211
- * @deprecated Use `events.seekTimeline` method instead. Will be removed in v1.0.0
212
- */
213
- postChatInfo({ clipId, clipPlayHead, }: {
110
+ postChatInfo(params: {
214
111
  clipId: string;
215
112
  clipPlayHead: number;
216
- }): void;
217
- /**
218
- * @deprecated Use `events.onTimelineSeek` method instead. Will be removed in v1.0.0
219
- */
220
- getTimelineInfo({ callback, }: {
113
+ }): Promise<void>;
114
+ getTimelineInfo(params: {
221
115
  callback: (clipPlayHead: number, clipId: string) => void;
222
- }): void;
116
+ }): Promise<void>;
223
117
  }
224
118
 
225
- export { TapSDK as default };
119
+ export { type ContainerStyle, type Course, type PositionType, type SeekTimelineParamsType, TapSDK, type TapSDKConfig, type TapSDKConstructor, type TapSDKInitParams, type TapSDKInstance, TapSDK as default };