scorm-player 1.2.3 → 1.2.4
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 +114 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,15 @@ A lightweight SCORM 2004 unpacker + manifest parser + storage adapter system.
|
|
|
4
4
|
|
|
5
5
|
This package allows you to:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
Upload SCORM 2004 ZIP packages
|
|
8
|
+
|
|
9
|
+
Unpack them server-side (Node.js, Next.js server actions)
|
|
10
|
+
|
|
11
|
+
Parse imsmanifest.xml (using a Node-compatible DOM parser)
|
|
12
|
+
|
|
13
|
+
Save unpacked files to any storage (Supabase included)
|
|
14
|
+
|
|
15
|
+
Render SCORM content inside a React <ScormPlayer /> component or as a full-page course
|
|
12
16
|
|
|
13
17
|
## 🚀 Features
|
|
14
18
|
|
|
@@ -29,7 +33,7 @@ npm install scorm-player
|
|
|
29
33
|
|
|
30
34
|
The package requires:
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
react >= 18
|
|
33
37
|
|
|
34
38
|
React is declared as a peer dependency, so it will use whatever version the project already has.
|
|
35
39
|
|
|
@@ -60,54 +64,81 @@ import { unpackSCORM, parseManifest, SupabaseAdapter } from "scorm-player";
|
|
|
60
64
|
import { ScormPlayer } from "scorm-player";
|
|
61
65
|
```
|
|
62
66
|
|
|
63
|
-
## 🧭 Usage — Upload SCORM Package (Next.js Server Action)
|
|
67
|
+
## 🧭 Usage — Upload a SCORM Package (Next.js Server Action)
|
|
64
68
|
|
|
65
|
-
### Client Component (
|
|
69
|
+
### Client Component (Upload Button + Get Launch Link)
|
|
66
70
|
|
|
67
71
|
```typescript
|
|
68
72
|
"use client";
|
|
69
73
|
|
|
70
74
|
import { useState } from "react";
|
|
71
75
|
import { uploadSCORMCourse } from "./actions/uploadSCORMCourse";
|
|
76
|
+
import { ScormPlayer } from "scorm-player";
|
|
72
77
|
|
|
73
78
|
export default function ScormPage() {
|
|
74
79
|
const [file, setFile] = useState<File | null>(null);
|
|
80
|
+
const [link, setLink] = useState<string>("");
|
|
81
|
+
|
|
82
|
+
const handleUpload = async () => {
|
|
83
|
+
if (!file) return;
|
|
84
|
+
|
|
85
|
+
// Get the SCORM launch link after uploading
|
|
86
|
+
const launchLink = await uploadSCORMCourse(file, "tests");
|
|
87
|
+
setLink(launchLink);
|
|
88
|
+
console.log("SCORM launch link:", launchLink);
|
|
89
|
+
};
|
|
75
90
|
|
|
76
91
|
return (
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Upload
|
|
84
|
-
</
|
|
85
|
-
|
|
92
|
+
<>
|
|
93
|
+
<div>
|
|
94
|
+
<input
|
|
95
|
+
type='file'
|
|
96
|
+
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
|
97
|
+
/>
|
|
98
|
+
<button onClick={handleUpload}>Upload</button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Embed SCORM in an iframe */}
|
|
102
|
+
{link && (
|
|
103
|
+
<ScormPlayer
|
|
104
|
+
launchUrl={link}
|
|
105
|
+
saveProgress={(data) =>
|
|
106
|
+
console.log("SCORM Progress:", data)
|
|
107
|
+
}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
</>
|
|
86
111
|
);
|
|
87
112
|
}
|
|
88
113
|
```
|
|
89
114
|
|
|
90
|
-
### Server Action (
|
|
115
|
+
### Server Action (Unpack + Save SCORM)
|
|
91
116
|
|
|
92
117
|
```typescript
|
|
93
118
|
"use server";
|
|
94
119
|
|
|
95
120
|
import { unpackSCORM, parseManifest, SupabaseAdapter } from "scorm-player";
|
|
96
121
|
|
|
97
|
-
export async function uploadSCORMCourse(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
export async function uploadSCORMCourse(
|
|
123
|
+
file: File,
|
|
124
|
+
baseFolder: string = "course"
|
|
125
|
+
) {
|
|
126
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
101
127
|
const { files, manifestXml } = await unpackSCORM(buffer);
|
|
102
|
-
|
|
128
|
+
|
|
129
|
+
const launchPath = parseManifest(manifestXml); // path to index.html
|
|
103
130
|
|
|
104
131
|
const adapter = new SupabaseAdapter(
|
|
105
|
-
process.env.
|
|
106
|
-
process.env.
|
|
107
|
-
|
|
132
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
133
|
+
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_KEY!,
|
|
134
|
+
baseFolder
|
|
108
135
|
);
|
|
109
136
|
|
|
110
|
-
|
|
137
|
+
// Upload SCORM files and get a unique folder
|
|
138
|
+
const uniqueFolder = await adapter.uploadFolder(files, baseFolder);
|
|
139
|
+
|
|
140
|
+
// Form the full launch URL via API route
|
|
141
|
+
const launchUrl = `/api/scorm/${uniqueFolder}/${launchPath}`;
|
|
111
142
|
|
|
112
143
|
return launchUrl;
|
|
113
144
|
}
|
|
@@ -115,45 +146,84 @@ export async function uploadSCORMCourse(file: File) {
|
|
|
115
146
|
|
|
116
147
|
## 🖥️ Displaying SCORM Content
|
|
117
148
|
|
|
149
|
+
### 1️⃣ In an iframe (embedded mode)
|
|
150
|
+
|
|
118
151
|
```typescript
|
|
119
152
|
import { ScormPlayer } from "scorm-player";
|
|
120
153
|
|
|
121
154
|
export default function PlayerPage() {
|
|
122
|
-
|
|
155
|
+
const launchUrl = "/api/scorm/abc123/index.html";
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<ScormPlayer
|
|
159
|
+
launchUrl={launchUrl}
|
|
160
|
+
saveProgress={(data) => console.log("SCORM Progress:", data)}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
123
163
|
}
|
|
124
164
|
```
|
|
125
165
|
|
|
126
|
-
|
|
166
|
+
### 2️⃣ Full-page mode (open in a separate tab)
|
|
127
167
|
|
|
128
|
-
|
|
168
|
+
Simply redirect the user to the SCORM API route:
|
|
129
169
|
|
|
130
|
-
|
|
170
|
+
```typescript
|
|
171
|
+
"use client";
|
|
172
|
+
import { useEffect } from "react";
|
|
173
|
+
|
|
174
|
+
export default function LaunchScormPage({
|
|
175
|
+
searchParams,
|
|
176
|
+
}: {
|
|
177
|
+
searchParams: { launch: string };
|
|
178
|
+
}) {
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (searchParams.launch) {
|
|
181
|
+
// Add query param ?mode=page for full-page mode
|
|
182
|
+
window.location.href = `${searchParams.launch}?mode=page`;
|
|
183
|
+
}
|
|
184
|
+
}, [searchParams.launch]);
|
|
185
|
+
|
|
186
|
+
return <p>Loading SCORM course...</p>;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Usage example:
|
|
131
191
|
|
|
132
192
|
```typescript
|
|
133
|
-
|
|
193
|
+
// After uploading
|
|
194
|
+
const launchLink = await uploadSCORMCourse(file, "tests");
|
|
195
|
+
|
|
196
|
+
// Embedded mode
|
|
197
|
+
<ScormPlayer launchUrl={launchLink} saveProgress={...} />
|
|
134
198
|
|
|
135
|
-
|
|
199
|
+
// Full-page mode
|
|
200
|
+
window.open(`${launchLink}?mode=page`, "_blank");
|
|
136
201
|
```
|
|
137
202
|
|
|
138
|
-
|
|
203
|
+
In full-page mode, no additional Next.js component is required — the SCORM content renders directly through the /api/scorm/... route.
|
|
204
|
+
|
|
205
|
+
## 🗃️ Supabase Storage Adapter
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const adapter = new SupabaseAdapter(SUPABASE_URL, SUPABASE_KEY, "folderName");
|
|
209
|
+
await adapter.uploadFolder(files, "folderName");
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
You can also write your own adapters for S3, GCP, local FS, etc.
|
|
139
213
|
|
|
140
214
|
## 🧩 API Reference
|
|
141
215
|
|
|
142
|
-
- `unpackSCORM(zipData: Buffer | Uint8Array)`
|
|
143
|
-
- `parseManifest(manifestXml: string)`
|
|
144
|
-
- `SupabaseAdapter`
|
|
145
|
-
- `<ScormPlayer launchUrl="..." />`
|
|
216
|
+
- `unpackSCORM(zipData: Buffer | Uint8Array)` — Unpacks a SCORM ZIP file into a dictionary of files.
|
|
217
|
+
- `parseManifest(manifestXml: string)` — Locates the SCORM launch URL from imsmanifest.xml.
|
|
218
|
+
- `SupabaseAdapter` — Uploads unpacked files into Supabase Storage.
|
|
219
|
+
- `<ScormPlayer launchUrl="..." />` — Renders SCORM content inside an iframe.
|
|
146
220
|
|
|
147
221
|
## 🛠️ Requirements
|
|
148
222
|
|
|
149
223
|
- Node.js 18+
|
|
150
|
-
- React 18
|
|
151
|
-
- Next.js (
|
|
224
|
+
- React 18+
|
|
225
|
+
- Next.js 15+ (App Router)
|
|
152
226
|
|
|
153
227
|
## 📄 License
|
|
154
228
|
|
|
155
229
|
MIT License.
|
|
156
|
-
|
|
157
|
-
## 🎉 Done
|
|
158
|
-
|
|
159
|
-
Your SCORM Player is ready to use.
|
package/package.json
CHANGED