@teacharium/embed-sdk 0.1.0 → 0.2.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,6 +1,6 @@
1
1
  # Teacharium Embed SDK
2
2
 
3
- A lightweight JavaScript SDK for embedding Teacharium lessons into any website using secure iframe-based embedding.
3
+ A lightweight JavaScript SDK for embedding Teacharium lessons into any website using secure iframe-based embedding with signed JWT tokens.
4
4
 
5
5
  ## Installation
6
6
 
@@ -16,54 +16,69 @@ Or use directly via CDN:
16
16
 
17
17
  ## Quick Start
18
18
 
19
- ### 1. Obtain a JWT Token
19
+ ### 1. Publish a Lesson Version
20
20
 
21
- First, generate a signed token from your backend using the Teacharium API:
21
+ In the Teacharium editor, publish a version of your lesson:
22
+
23
+ 1. Open your lesson in the editor
24
+ 2. Go to the **Lesson Delivery** tab
25
+ 3. Click **Publish New Version**
26
+ 4. Expand the published version and click **Embed** to get the Lesson ID and Version ID
27
+
28
+ ### 2. Generate a JWT Token (Server-Side)
29
+
30
+ Generate a signed token from your backend using the Teacharium API. The token must include both a `lessonId` and a `versionId` to embed a specific published version:
22
31
 
23
32
  ```javascript
24
- const response = await fetch('https://www.teacharium.io/api/public/sign-token', {
25
- method: 'POST',
26
- headers: {
27
- 'Authorization': `Bearer ${publicKey}.${secretKey}`,
28
- 'Content-Type': 'application/json'
29
- },
30
- body: JSON.stringify({
31
- lessonId: 'your-lesson-id',
32
- userAttributes: {
33
- userId: 'user_12345',
34
- organizationRole: 'student'
33
+ const response = await fetch(
34
+ "https://www.teacharium.io/api/public/sign-token",
35
+ {
36
+ method: "POST",
37
+ headers: {
38
+ Authorization: `Bearer ${publicKey}.${secretKey}`,
39
+ "Content-Type": "application/json",
35
40
  },
36
- timeout: 3600
37
- })
38
- });
41
+ body: JSON.stringify({
42
+ lessonId: "your-lesson-id",
43
+ versionId: "your-published-version-id",
44
+ learnerId: "learner_anonymous_id",
45
+ userAttributes: {
46
+ userId: "user_12345",
47
+ organizationRole: "student",
48
+ },
49
+ timeout: 3600,
50
+ }),
51
+ },
52
+ );
39
53
 
40
54
  const { token } = await response.json();
41
55
  ```
42
56
 
43
- **Important:** Do not include personally identifiable information (PII) such as email addresses, full names, or phone numbers in `userAttributes`. Use anonymous identifiers like user IDs or session IDs instead.
57
+ **Important:** Always generate tokens server-side to protect your API credentials. Do not include personally identifiable information (PII) such as email addresses, full names, or phone numbers in `learnerId` or `userAttributes`. Use anonymous identifiers instead.
44
58
 
45
- ### 2. Embed the Lesson
59
+ ### 3. Embed the Lesson (Client-Side)
46
60
 
47
61
  ```html
48
62
  <div id="lesson-container"></div>
49
63
 
50
64
  <script type="module">
51
- import { embedLesson } from '@teacharium/embed-sdk';
65
+ import { embedLesson } from "@teacharium/embed-sdk";
52
66
 
53
67
  const embed = embedLesson({
54
- container: '#lesson-container',
55
- token: 'your-jwt-token-here',
56
- // baseUrl and lessonId are optional - defaults to www.teacharium.io and extracts lessonId from token
57
- width: '100%',
58
- height: '600px',
59
- onLoad: () => console.log('Lesson loaded'),
60
- onComplete: (data) => console.log('Lesson completed', data),
61
- onProgress: (data) => console.log('Progress update', data),
62
- onError: (error) => console.error('Error', error)
68
+ container: "#lesson-container",
69
+ token: token, // JWT token from your server
70
+ width: "100%",
71
+ height: "600px",
72
+ onLoad: () => console.log("Lesson loaded"),
73
+ onComplete: (data) => console.log("Lesson completed", data),
74
+ onProgress: (data) => console.log("Progress update", data),
75
+ onError: (error) => console.error("Error", error),
63
76
  });
64
77
  </script>
65
78
  ```
66
79
 
80
+ The SDK automatically extracts the `lessonId` and `versionId` from the JWT token -- you do not need to pass them separately.
81
+
67
82
  ### Using UMD (Browser Global)
68
83
 
69
84
  ```html
@@ -72,11 +87,11 @@ const { token } = await response.json();
72
87
  <script src="https://unpkg.com/@teacharium/embed-sdk@latest/dist/teacharium-embed-sdk.umd.js"></script>
73
88
  <script>
74
89
  const embed = TeachariumEmbed.embedLesson({
75
- container: '#lesson-container',
76
- token: 'your-jwt-token-here',
90
+ container: "#lesson-container",
91
+ token: "your-jwt-token-here",
77
92
  onComplete: (data) => {
78
- console.log('Lesson completed!', data);
79
- }
93
+ console.log("Lesson completed!", data);
94
+ },
80
95
  });
81
96
  </script>
82
97
  ```
@@ -90,7 +105,7 @@ Creates and embeds a lesson iframe.
90
105
  **Options:**
91
106
 
92
107
  - `container` (required): HTMLElement or CSS selector string for the container
93
- - `token` (required): JWT token from `/api/public/sign-token` (lessonId is automatically extracted from this token)
108
+ - `token` (required): JWT token from `/api/public/sign-token` (lessonId and versionId are automatically extracted)
94
109
  - `baseUrl` (optional): Base URL of your Teacharium instance, default: "https://www.teacharium.io"
95
110
  - `width` (optional): Iframe width, default: "100%"
96
111
  - `height` (optional): Iframe height, default: "600px"
@@ -105,6 +120,7 @@ Creates and embeds a lesson iframe.
105
120
  ### `TeachariumEmbed` Methods
106
121
 
107
122
  #### `destroy()`
123
+
108
124
  Removes the embedded lesson from the page.
109
125
 
110
126
  ```javascript
@@ -112,20 +128,23 @@ embed.destroy();
112
128
  ```
113
129
 
114
130
  #### `reload()`
131
+
115
132
  Reloads the embedded lesson.
116
133
 
117
134
  ```javascript
118
135
  embed.reload();
119
136
  ```
120
137
 
121
- #### `postMessage(type: string, payload?: any)`
138
+ #### `postMessage(type: string, payload?: unknown)`
139
+
122
140
  Send a custom message to the embedded lesson.
123
141
 
124
142
  ```javascript
125
- embed.postMessage('custom:action', { data: 'value' });
143
+ embed.postMessage("custom:action", { data: "value" });
126
144
  ```
127
145
 
128
146
  #### `getIframe()`
147
+
129
148
  Get the iframe element.
130
149
 
131
150
  ```javascript
@@ -135,15 +154,17 @@ const iframe = embed.getIframe();
135
154
  ## Events
136
155
 
137
156
  ### onLoad
157
+
138
158
  Fired when the lesson iframe has loaded.
139
159
 
140
160
  ```javascript
141
161
  onLoad: () => {
142
- console.log('Lesson is ready');
143
- }
162
+ console.log("Lesson is ready");
163
+ };
144
164
  ```
145
165
 
146
166
  ### onComplete
167
+
147
168
  Fired when the user completes the lesson.
148
169
 
149
170
  ```javascript
@@ -152,10 +173,11 @@ onComplete: (data) => {
152
173
  // data.completedAt - ISO timestamp
153
174
  // data.score - Optional score
154
175
  // data.totalSteps - Total number of steps
155
- }
176
+ };
156
177
  ```
157
178
 
158
179
  ### onProgress
180
+
159
181
  Fired when the user progresses through the lesson.
160
182
 
161
183
  ```javascript
@@ -164,61 +186,36 @@ onProgress: (data) => {
164
186
  // data.totalSteps - Total steps
165
187
  // data.sectionIndex - Current section index
166
188
  // data.stepIndex - Current step index
167
- }
189
+ };
168
190
  ```
169
191
 
170
192
  ### onError
193
+
171
194
  Fired when an error occurs.
172
195
 
173
196
  ```javascript
174
197
  onError: (error) => {
175
- console.error('Lesson error:', error.message);
176
- }
198
+ console.error("Lesson error:", error.message);
199
+ };
177
200
  ```
178
201
 
179
202
  ## Security
180
203
 
181
204
  - Always generate tokens on your backend, never expose API keys in client-side code
205
+ - Tokens are tied to specific lesson versions, ensuring users see the exact content you published
182
206
  - Tokens expire based on the `timeout` parameter (default 2 hours, max 24 hours)
183
- - Tokens are tied to specific lessons and organizations
207
+ - Tokens are tied to specific lessons, versions, and organizations
184
208
  - User attributes in tokens cannot be modified without re-signing
185
209
 
186
210
  ## Examples
187
211
 
188
- ### Responsive Embed
189
-
190
- ```javascript
191
- embedLesson({
192
- container: '#lesson',
193
- token: token,
194
- baseUrl: 'https://your-domain.com',
195
- lessonId: lessonId,
196
- width: '100%',
197
- height: '80vh',
198
- className: 'lesson-iframe'
199
- });
200
- ```
201
-
202
- ### With Progress Tracking
203
-
204
- ```javascript
205
- const embed = embedLesson({
206
- container: '#lesson',
207
- token: token,
208
- baseUrl: 'https://your-domain.com',
209
- lessonId: lessonId,
210
- onProgress: (data) => {
211
- const percent = (data.currentStep / data.totalSteps) * 100;
212
- document.querySelector('#progress-bar').style.width = `${percent}%`;
213
- }
214
- });
215
- ```
212
+ See the [Express.js example](../../examples/embed-sdk-express/) for a complete working integration.
216
213
 
217
214
  ### React Example
218
215
 
219
216
  ```jsx
220
- import { useEffect, useRef } from 'react';
221
- import { embedLesson } from '@teacharium/embed-sdk';
217
+ import { useEffect, useRef } from "react";
218
+ import { embedLesson } from "@teacharium/embed-sdk";
222
219
 
223
220
  function LessonEmbed({ token }) {
224
221
  const containerRef = useRef(null);
@@ -229,10 +226,9 @@ function LessonEmbed({ token }) {
229
226
  embedRef.current = embedLesson({
230
227
  container: containerRef.current,
231
228
  token: token,
232
- // lessonId and baseUrl are automatically handled
233
229
  onComplete: (data) => {
234
- console.log('Lesson completed!', data);
235
- }
230
+ console.log("Lesson completed!", data);
231
+ },
236
232
  });
237
233
  }
238
234
 
@@ -243,7 +239,7 @@ function LessonEmbed({ token }) {
243
239
  };
244
240
  }, [token]);
245
241
 
246
- return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />;
242
+ return <div ref={containerRef} style={{ width: "100%", height: "600px" }} />;
247
243
  }
248
244
  ```
249
245
 
package/dist/main.d.ts CHANGED
@@ -1 +1,108 @@
1
- export {}
1
+ /**
2
+ * Teacharium Embed SDK
3
+ *
4
+ * This SDK provides an easy way to embed Teacharium lessons into any website
5
+ * using an iframe with JWT-based authentication.
6
+ *
7
+ * Tokens are generated server-side via the Teacharium Sign Token API and
8
+ * contain both the lessonId and versionId (published version) to embed.
9
+ */
10
+ export interface EmbedOptions {
11
+ /**
12
+ * The container element or selector where the lesson should be embedded
13
+ */
14
+ container: HTMLElement | string;
15
+ /**
16
+ * The JWT token obtained from /api/public/sign-token.
17
+ * The token contains the lessonId and versionId, which are extracted automatically.
18
+ */
19
+ token: string;
20
+ /**
21
+ * The base URL of your Teacharium instance
22
+ * Defaults to "https://www.teacharium.io"
23
+ * @example "https://your-domain.com"
24
+ */
25
+ baseUrl?: string;
26
+ /**
27
+ * Width of the iframe (default: "100%")
28
+ */
29
+ width?: string;
30
+ /**
31
+ * Height of the iframe (default: "600px")
32
+ */
33
+ height?: string;
34
+ /**
35
+ * Additional CSS classes to apply to the iframe
36
+ */
37
+ className?: string;
38
+ /**
39
+ * Callback function called when the lesson is loaded
40
+ */
41
+ onLoad?: () => void;
42
+ /**
43
+ * Callback function called when the lesson is completed
44
+ */
45
+ onComplete?: (data: LessonCompleteData) => void;
46
+ /**
47
+ * Callback function called on lesson progress updates
48
+ */
49
+ onProgress?: (data: LessonProgressData) => void;
50
+ /**
51
+ * Callback function called when an error occurs
52
+ */
53
+ onError?: (error: Error) => void;
54
+ /**
55
+ * Callback function called for every message received from the embedded lesson.
56
+ * Useful for logging or handling custom event types.
57
+ */
58
+ onEvent?: (type: string, payload: unknown) => void;
59
+ }
60
+ export interface LessonCompleteData {
61
+ lessonId: string;
62
+ completedAt: string;
63
+ score?: number;
64
+ totalSteps: number;
65
+ }
66
+ export interface LessonProgressData {
67
+ lessonId: string;
68
+ currentStep: number;
69
+ totalSteps: number;
70
+ sectionIndex: number;
71
+ stepIndex: number;
72
+ }
73
+ export declare class TeachariumEmbed {
74
+ private iframe;
75
+ private container;
76
+ private options;
77
+ constructor(options: EmbedOptions);
78
+ private init;
79
+ private createIframe;
80
+ private setupMessageListener;
81
+ /**
82
+ * Remove the embedded lesson from the page
83
+ */
84
+ destroy(): void;
85
+ /**
86
+ * Send a message to the embedded lesson
87
+ * @param type The message type
88
+ * @param payload The message payload
89
+ */
90
+ postMessage(type: string, payload?: unknown): void;
91
+ /**
92
+ * Reload the embedded lesson
93
+ */
94
+ reload(): void;
95
+ /**
96
+ * Get the iframe element
97
+ */
98
+ getIframe(): HTMLIFrameElement | null;
99
+ }
100
+ /**
101
+ * Factory function to create a new embedded lesson
102
+ */
103
+ export declare function embedLesson(options: EmbedOptions): TeachariumEmbed;
104
+ declare const _default: {
105
+ TeachariumEmbed: typeof TeachariumEmbed;
106
+ embedLesson: typeof embedLesson;
107
+ };
108
+ export default _default;
@@ -21,8 +21,10 @@ class i {
21
21
  if (s = a(e.token).lessonId, !s)
22
22
  throw new Error("Token does not contain lessonId");
23
23
  } catch {
24
- const r = new Error("Failed to extract lessonId from token. Ensure token is valid and contains lessonId.");
25
- throw e.onError?.(r), r;
24
+ const t = new Error(
25
+ "Failed to extract lessonId from token. Ensure token is valid."
26
+ );
27
+ throw e.onError?.(t), t;
26
28
  }
27
29
  this.options = {
28
30
  ...e,
@@ -37,7 +39,9 @@ class i {
37
39
  if (typeof this.options.container == "string") {
38
40
  const e = document.querySelector(this.options.container);
39
41
  if (!e) {
40
- const s = new Error(`Container element not found: ${this.options.container}`);
42
+ const s = new Error(
43
+ `Container element not found: ${this.options.container}`
44
+ );
41
45
  throw this.options.onError?.(s), s;
42
46
  }
43
47
  this.container = e;
@@ -65,7 +69,10 @@ class i {
65
69
  if (e.origin === s)
66
70
  try {
67
71
  const t = typeof e.data == "string" ? JSON.parse(e.data) : e.data;
68
- switch (t.type) {
72
+ switch (this.options.onEvent?.(t.type, t.payload), t.type) {
73
+ case "lesson:loaded":
74
+ this.options.onLoad?.();
75
+ break;
69
76
  case "lesson:complete":
70
77
  this.options.onComplete?.(t.payload);
71
78
  break;
@@ -104,7 +111,10 @@ class i {
104
111
  * Reload the embedded lesson
105
112
  */
106
113
  reload() {
107
- this.iframe && (this.iframe.src = this.iframe.src);
114
+ if (this.iframe) {
115
+ const e = this.iframe.src;
116
+ this.iframe.src = e;
117
+ }
108
118
  }
109
119
  /**
110
120
  * Get the iframe element
@@ -116,12 +126,12 @@ class i {
116
126
  function h(o) {
117
127
  return new i(o);
118
128
  }
119
- const l = {
129
+ const c = {
120
130
  TeachariumEmbed: i,
121
131
  embedLesson: h
122
132
  };
123
133
  export {
124
134
  i as TeachariumEmbed,
125
- l as default,
135
+ c as default,
126
136
  h as embedLesson
127
137
  };
@@ -1 +1 @@
1
- (function(i,r){typeof exports=="object"&&typeof module<"u"?r(exports):typeof define=="function"&&define.amd?define(["exports"],r):(i=typeof globalThis<"u"?globalThis:i||self,r(i.TeachariumEmbed={}))})(this,(function(i){"use strict";function r(n){try{const e=n.split(".");if(e.length!==3)throw new Error("Invalid token format");const t=e[1].replace(/-/g,"+").replace(/_/g,"/"),o=decodeURIComponent(atob(t).split("").map(c=>"%"+("00"+c.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(o)}catch{throw new Error("Failed to decode JWT token")}}class a{iframe=null;container=null;options;constructor(e){let s;try{if(s=r(e.token).lessonId,!s)throw new Error("Token does not contain lessonId")}catch{const o=new Error("Failed to extract lessonId from token. Ensure token is valid and contains lessonId.");throw e.onError?.(o),o}this.options={...e,baseUrl:e.baseUrl||"https://www.teacharium.io",lessonId:s,width:e.width||"100%",height:e.height||"600px",className:e.className||""},this.init()}init(){if(typeof this.options.container=="string"){const e=document.querySelector(this.options.container);if(!e){const s=new Error(`Container element not found: ${this.options.container}`);throw this.options.onError?.(s),s}this.container=e}else this.container=this.options.container;this.createIframe(),this.setupMessageListener()}createIframe(){if(!this.container)throw new Error("Container not initialized");this.iframe=document.createElement("iframe");const e=new URL(`/embed/lesson/${this.options.lessonId}`,this.options.baseUrl);e.searchParams.set("token",this.options.token),this.iframe.src=e.toString(),this.iframe.width=this.options.width,this.iframe.height=this.options.height,this.iframe.style.border="none",this.iframe.style.display="block",this.iframe.setAttribute("allow","autoplay; fullscreen"),this.iframe.setAttribute("allowfullscreen","true"),this.options.className&&(this.iframe.className=this.options.className),this.iframe.addEventListener("load",()=>{this.options.onLoad?.()}),this.container.appendChild(this.iframe)}setupMessageListener(){window.addEventListener("message",e=>{if(this.iframe&&e.source!==this.iframe.contentWindow)return;const s=new URL(this.options.baseUrl).origin;if(e.origin===s)try{const t=typeof e.data=="string"?JSON.parse(e.data):e.data;switch(t.type){case"lesson:complete":this.options.onComplete?.(t.payload);break;case"lesson:progress":this.options.onProgress?.(t.payload);break;case"lesson:error":this.options.onError?.(new Error(t.payload.message));break}}catch(t){console.error("Error parsing message from iframe:",t)}})}destroy(){this.iframe&&this.iframe.parentNode&&(this.iframe.parentNode.removeChild(this.iframe),this.iframe=null)}postMessage(e,s){if(!this.iframe||!this.iframe.contentWindow){console.warn("Cannot post message: iframe not ready");return}const t={type:e,payload:s},o=new URL(this.options.baseUrl).origin;this.iframe.contentWindow.postMessage(t,o)}reload(){this.iframe&&(this.iframe.src=this.iframe.src)}getIframe(){return this.iframe}}function h(n){return new a(n)}const l={TeachariumEmbed:a,embedLesson:h};i.TeachariumEmbed=a,i.default=l,i.embedLesson=h,Object.defineProperties(i,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
1
+ (function(o,i){typeof exports=="object"&&typeof module<"u"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(o=typeof globalThis<"u"?globalThis:o||self,i(o.TeachariumEmbed={}))})(this,(function(o){"use strict";function i(r){try{const e=r.split(".");if(e.length!==3)throw new Error("Invalid token format");const t=e[1].replace(/-/g,"+").replace(/_/g,"/"),a=decodeURIComponent(atob(t).split("").map(c=>"%"+("00"+c.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(a)}catch{throw new Error("Failed to decode JWT token")}}class n{iframe=null;container=null;options;constructor(e){let s;try{if(s=i(e.token).lessonId,!s)throw new Error("Token does not contain lessonId")}catch{const t=new Error("Failed to extract lessonId from token. Ensure token is valid.");throw e.onError?.(t),t}this.options={...e,baseUrl:e.baseUrl||"https://www.teacharium.io",lessonId:s,width:e.width||"100%",height:e.height||"600px",className:e.className||""},this.init()}init(){if(typeof this.options.container=="string"){const e=document.querySelector(this.options.container);if(!e){const s=new Error(`Container element not found: ${this.options.container}`);throw this.options.onError?.(s),s}this.container=e}else this.container=this.options.container;this.createIframe(),this.setupMessageListener()}createIframe(){if(!this.container)throw new Error("Container not initialized");this.iframe=document.createElement("iframe");const e=new URL(`/embed/lesson/${this.options.lessonId}`,this.options.baseUrl);e.searchParams.set("token",this.options.token),this.iframe.src=e.toString(),this.iframe.width=this.options.width,this.iframe.height=this.options.height,this.iframe.style.border="none",this.iframe.style.display="block",this.iframe.setAttribute("allow","autoplay; fullscreen"),this.iframe.setAttribute("allowfullscreen","true"),this.options.className&&(this.iframe.className=this.options.className),this.iframe.addEventListener("load",()=>{this.options.onLoad?.()}),this.container.appendChild(this.iframe)}setupMessageListener(){window.addEventListener("message",e=>{if(this.iframe&&e.source!==this.iframe.contentWindow)return;const s=new URL(this.options.baseUrl).origin;if(e.origin===s)try{const t=typeof e.data=="string"?JSON.parse(e.data):e.data;switch(this.options.onEvent?.(t.type,t.payload),t.type){case"lesson:loaded":this.options.onLoad?.();break;case"lesson:complete":this.options.onComplete?.(t.payload);break;case"lesson:progress":this.options.onProgress?.(t.payload);break;case"lesson:error":this.options.onError?.(new Error(t.payload.message));break}}catch(t){console.error("Error parsing message from iframe:",t)}})}destroy(){this.iframe&&this.iframe.parentNode&&(this.iframe.parentNode.removeChild(this.iframe),this.iframe=null)}postMessage(e,s){if(!this.iframe||!this.iframe.contentWindow){console.warn("Cannot post message: iframe not ready");return}const t={type:e,payload:s},a=new URL(this.options.baseUrl).origin;this.iframe.contentWindow.postMessage(t,a)}reload(){if(this.iframe){const e=this.iframe.src;this.iframe.src=e}}getIframe(){return this.iframe}}function h(r){return new n(r)}const l={TeachariumEmbed:n,embedLesson:h};o.TeachariumEmbed=n,o.default=l,o.embedLesson=h,Object.defineProperties(o,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teacharium/embed-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "SDK for embedding Teacharium lessons via iframe",
5
5
  "type": "module",
6
6
  "files": [