@teacharium/embed-sdk 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 ADDED
@@ -0,0 +1,252 @@
1
+ # Teacharium Embed SDK
2
+
3
+ A lightweight JavaScript SDK for embedding Teacharium lessons into any website using secure iframe-based embedding.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @teacharium/embed-sdk
9
+ ```
10
+
11
+ Or use directly via CDN:
12
+
13
+ ```html
14
+ <script src="https://unpkg.com/@teacharium/embed-sdk@latest/dist/teacharium-embed-sdk.umd.js"></script>
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ### 1. Obtain a JWT Token
20
+
21
+ First, generate a signed token from your backend using the Teacharium API:
22
+
23
+ ```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'
35
+ },
36
+ timeout: 3600
37
+ })
38
+ });
39
+
40
+ const { token } = await response.json();
41
+ ```
42
+
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.
44
+
45
+ ### 2. Embed the Lesson
46
+
47
+ ```html
48
+ <div id="lesson-container"></div>
49
+
50
+ <script type="module">
51
+ import { embedLesson } from '@teacharium/embed-sdk';
52
+
53
+ 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)
63
+ });
64
+ </script>
65
+ ```
66
+
67
+ ### Using UMD (Browser Global)
68
+
69
+ ```html
70
+ <div id="lesson-container"></div>
71
+
72
+ <script src="https://unpkg.com/@teacharium/embed-sdk@latest/dist/teacharium-embed-sdk.umd.js"></script>
73
+ <script>
74
+ const embed = TeachariumEmbed.embedLesson({
75
+ container: '#lesson-container',
76
+ token: 'your-jwt-token-here',
77
+ onComplete: (data) => {
78
+ console.log('Lesson completed!', data);
79
+ }
80
+ });
81
+ </script>
82
+ ```
83
+
84
+ ## API Reference
85
+
86
+ ### `embedLesson(options: EmbedOptions)`
87
+
88
+ Creates and embeds a lesson iframe.
89
+
90
+ **Options:**
91
+
92
+ - `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)
94
+ - `baseUrl` (optional): Base URL of your Teacharium instance, default: "https://www.teacharium.io"
95
+ - `width` (optional): Iframe width, default: "100%"
96
+ - `height` (optional): Iframe height, default: "600px"
97
+ - `className` (optional): Additional CSS classes for the iframe
98
+ - `onLoad` (optional): Callback when lesson loads
99
+ - `onComplete` (optional): Callback when lesson is completed
100
+ - `onProgress` (optional): Callback for progress updates
101
+ - `onError` (optional): Callback for errors
102
+
103
+ **Returns:** `TeachariumEmbed` instance
104
+
105
+ ### `TeachariumEmbed` Methods
106
+
107
+ #### `destroy()`
108
+ Removes the embedded lesson from the page.
109
+
110
+ ```javascript
111
+ embed.destroy();
112
+ ```
113
+
114
+ #### `reload()`
115
+ Reloads the embedded lesson.
116
+
117
+ ```javascript
118
+ embed.reload();
119
+ ```
120
+
121
+ #### `postMessage(type: string, payload?: any)`
122
+ Send a custom message to the embedded lesson.
123
+
124
+ ```javascript
125
+ embed.postMessage('custom:action', { data: 'value' });
126
+ ```
127
+
128
+ #### `getIframe()`
129
+ Get the iframe element.
130
+
131
+ ```javascript
132
+ const iframe = embed.getIframe();
133
+ ```
134
+
135
+ ## Events
136
+
137
+ ### onLoad
138
+ Fired when the lesson iframe has loaded.
139
+
140
+ ```javascript
141
+ onLoad: () => {
142
+ console.log('Lesson is ready');
143
+ }
144
+ ```
145
+
146
+ ### onComplete
147
+ Fired when the user completes the lesson.
148
+
149
+ ```javascript
150
+ onComplete: (data) => {
151
+ // data.lessonId - The lesson ID
152
+ // data.completedAt - ISO timestamp
153
+ // data.score - Optional score
154
+ // data.totalSteps - Total number of steps
155
+ }
156
+ ```
157
+
158
+ ### onProgress
159
+ Fired when the user progresses through the lesson.
160
+
161
+ ```javascript
162
+ onProgress: (data) => {
163
+ // data.currentStep - Current step number
164
+ // data.totalSteps - Total steps
165
+ // data.sectionIndex - Current section index
166
+ // data.stepIndex - Current step index
167
+ }
168
+ ```
169
+
170
+ ### onError
171
+ Fired when an error occurs.
172
+
173
+ ```javascript
174
+ onError: (error) => {
175
+ console.error('Lesson error:', error.message);
176
+ }
177
+ ```
178
+
179
+ ## Security
180
+
181
+ - Always generate tokens on your backend, never expose API keys in client-side code
182
+ - Tokens expire based on the `timeout` parameter (default 2 hours, max 24 hours)
183
+ - Tokens are tied to specific lessons and organizations
184
+ - User attributes in tokens cannot be modified without re-signing
185
+
186
+ ## Examples
187
+
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
+ ```
216
+
217
+ ### React Example
218
+
219
+ ```jsx
220
+ import { useEffect, useRef } from 'react';
221
+ import { embedLesson } from '@teacharium/embed-sdk';
222
+
223
+ function LessonEmbed({ token }) {
224
+ const containerRef = useRef(null);
225
+ const embedRef = useRef(null);
226
+
227
+ useEffect(() => {
228
+ if (containerRef.current && token) {
229
+ embedRef.current = embedLesson({
230
+ container: containerRef.current,
231
+ token: token,
232
+ // lessonId and baseUrl are automatically handled
233
+ onComplete: (data) => {
234
+ console.log('Lesson completed!', data);
235
+ }
236
+ });
237
+ }
238
+
239
+ return () => {
240
+ if (embedRef.current) {
241
+ embedRef.current.destroy();
242
+ }
243
+ };
244
+ }, [token]);
245
+
246
+ return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />;
247
+ }
248
+ ```
249
+
250
+ ## License
251
+
252
+ MIT
package/dist/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,127 @@
1
+ function a(o) {
2
+ try {
3
+ const e = o.split(".");
4
+ if (e.length !== 3)
5
+ throw new Error("Invalid token format");
6
+ const t = e[1].replace(/-/g, "+").replace(/_/g, "/"), r = decodeURIComponent(
7
+ atob(t).split("").map((n) => "%" + ("00" + n.charCodeAt(0).toString(16)).slice(-2)).join("")
8
+ );
9
+ return JSON.parse(r);
10
+ } catch {
11
+ throw new Error("Failed to decode JWT token");
12
+ }
13
+ }
14
+ class i {
15
+ iframe = null;
16
+ container = null;
17
+ options;
18
+ constructor(e) {
19
+ let s;
20
+ try {
21
+ if (s = a(e.token).lessonId, !s)
22
+ throw new Error("Token does not contain lessonId");
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;
26
+ }
27
+ this.options = {
28
+ ...e,
29
+ baseUrl: e.baseUrl || "https://www.teacharium.io",
30
+ lessonId: s,
31
+ width: e.width || "100%",
32
+ height: e.height || "600px",
33
+ className: e.className || ""
34
+ }, this.init();
35
+ }
36
+ init() {
37
+ if (typeof this.options.container == "string") {
38
+ const e = document.querySelector(this.options.container);
39
+ if (!e) {
40
+ const s = new Error(`Container element not found: ${this.options.container}`);
41
+ throw this.options.onError?.(s), s;
42
+ }
43
+ this.container = e;
44
+ } else
45
+ this.container = this.options.container;
46
+ this.createIframe(), this.setupMessageListener();
47
+ }
48
+ createIframe() {
49
+ if (!this.container)
50
+ throw new Error("Container not initialized");
51
+ this.iframe = document.createElement("iframe");
52
+ const e = new URL(
53
+ `/embed/lesson/${this.options.lessonId}`,
54
+ this.options.baseUrl
55
+ );
56
+ 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", () => {
57
+ this.options.onLoad?.();
58
+ }), this.container.appendChild(this.iframe);
59
+ }
60
+ setupMessageListener() {
61
+ window.addEventListener("message", (e) => {
62
+ if (this.iframe && e.source !== this.iframe.contentWindow)
63
+ return;
64
+ const s = new URL(this.options.baseUrl).origin;
65
+ if (e.origin === s)
66
+ try {
67
+ const t = typeof e.data == "string" ? JSON.parse(e.data) : e.data;
68
+ switch (t.type) {
69
+ case "lesson:complete":
70
+ this.options.onComplete?.(t.payload);
71
+ break;
72
+ case "lesson:progress":
73
+ this.options.onProgress?.(t.payload);
74
+ break;
75
+ case "lesson:error":
76
+ this.options.onError?.(new Error(t.payload.message));
77
+ break;
78
+ }
79
+ } catch (t) {
80
+ console.error("Error parsing message from iframe:", t);
81
+ }
82
+ });
83
+ }
84
+ /**
85
+ * Remove the embedded lesson from the page
86
+ */
87
+ destroy() {
88
+ this.iframe && this.iframe.parentNode && (this.iframe.parentNode.removeChild(this.iframe), this.iframe = null);
89
+ }
90
+ /**
91
+ * Send a message to the embedded lesson
92
+ * @param type The message type
93
+ * @param payload The message payload
94
+ */
95
+ postMessage(e, s) {
96
+ if (!this.iframe || !this.iframe.contentWindow) {
97
+ console.warn("Cannot post message: iframe not ready");
98
+ return;
99
+ }
100
+ const t = { type: e, payload: s }, r = new URL(this.options.baseUrl).origin;
101
+ this.iframe.contentWindow.postMessage(t, r);
102
+ }
103
+ /**
104
+ * Reload the embedded lesson
105
+ */
106
+ reload() {
107
+ this.iframe && (this.iframe.src = this.iframe.src);
108
+ }
109
+ /**
110
+ * Get the iframe element
111
+ */
112
+ getIframe() {
113
+ return this.iframe;
114
+ }
115
+ }
116
+ function h(o) {
117
+ return new i(o);
118
+ }
119
+ const l = {
120
+ TeachariumEmbed: i,
121
+ embedLesson: h
122
+ };
123
+ export {
124
+ i as TeachariumEmbed,
125
+ l as default,
126
+ h as embedLesson
127
+ };
@@ -0,0 +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"}})}));
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@teacharium/embed-sdk",
3
+ "version": "0.1.0",
4
+ "description": "SDK for embedding Teacharium lessons via iframe",
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "dist/teacharium-embed-sdk.umd.js",
10
+ "module": "dist/teacharium-embed-sdk.es.js",
11
+ "types": "dist/main.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/main.d.ts",
15
+ "import": "./dist/teacharium-embed-sdk.es.js",
16
+ "require": "./dist/teacharium-embed-sdk.umd.js"
17
+ }
18
+ },
19
+ "keywords": [
20
+ "teacharium",
21
+ "embed",
22
+ "iframe",
23
+ "lessons",
24
+ "elearning"
25
+ ],
26
+ "devDependencies": {
27
+ "@eslint/js": "^9.35.0",
28
+ "eslint": "^9.35.0",
29
+ "globals": "^16.4.0",
30
+ "typescript": "~5.8.3",
31
+ "typescript-eslint": "^8.43.0",
32
+ "vite": "^7.1.5",
33
+ "vite-plugin-dts": "^4.5.4"
34
+ },
35
+ "scripts": {
36
+ "dev": "tsc -b && vite build --watch",
37
+ "build:watch": "tsc -b && vite build --watch",
38
+ "build": "tsc -b && vite build",
39
+ "type-check": "tsc --noEmit",
40
+ "lint": "eslint ."
41
+ }
42
+ }