scorm-player 1.0.1 → 1.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 +114 -83
- package/dist/index.d.mts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +38 -13
- package/dist/index.mjs +38 -13
- package/package.json +15 -9
package/README.md
CHANGED
|
@@ -1,128 +1,159 @@
|
|
|
1
|
-
# SCORM Player 2004
|
|
1
|
+
# SCORM Player 2004 — Node/Browser Compatible Loader & Parser
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A lightweight SCORM 2004 unpacker + manifest parser + storage adapter system.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This package allows you to:
|
|
6
6
|
|
|
7
|
-
- Upload
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- Upload SCORM 2004 ZIP packages
|
|
8
|
+
- Unpack them server-side (Node.js, Next.js server actions)
|
|
9
|
+
- Parse imsmanifest.xml (using a Node-compatible DOM parser)
|
|
10
|
+
- Save unpacked files to any storage (Supabase included)
|
|
11
|
+
- Render SCORM content inside a React <ScormPlayer /> component
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## 🚀 Features
|
|
14
|
+
|
|
15
|
+
✔ Supports SCORM 2004 ZIP packages
|
|
16
|
+
✔ Works in Node.js (backend) and Browser (frontend)
|
|
17
|
+
✔ SCORM ZIP unpacking using JSZip
|
|
18
|
+
✔ Manifest parsing using xmldom (no browser APIs)
|
|
19
|
+
✔ Supabase storage adapter included
|
|
20
|
+
✔ Ready for Next.js — Server Actions + Client Components
|
|
21
|
+
✔ Works with React 18 and React 19
|
|
22
|
+
✔ Published as a reusable NPM module
|
|
23
|
+
|
|
24
|
+
## 📦 Installation
|
|
14
25
|
|
|
15
26
|
```bash
|
|
16
27
|
npm install scorm-player
|
|
17
28
|
```
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
The package requires:
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
- react >= 18
|
|
33
|
+
|
|
34
|
+
React is declared as a peer dependency, so it will use whatever version the project already has.
|
|
35
|
+
|
|
36
|
+
## 📁 Package Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
src/
|
|
40
|
+
backend/
|
|
41
|
+
unpackSCORM.ts
|
|
42
|
+
parseManifest.ts
|
|
43
|
+
storageAdapters/
|
|
44
|
+
supabaseAdapter.ts
|
|
45
|
+
frontend/
|
|
46
|
+
ScormPlayer.tsx
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You can import backend utilities or frontend components separately:
|
|
50
|
+
|
|
51
|
+
### Backend
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { unpackSCORM, parseManifest, SupabaseAdapter } from "scorm-player";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Frontend
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { ScormPlayer } from "scorm-player";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 🧭 Usage — Upload SCORM Package (Next.js Server Action)
|
|
64
|
+
|
|
65
|
+
### Client Component (upload button)
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
"use client";
|
|
69
|
+
|
|
70
|
+
import { useState } from "react";
|
|
71
|
+
import { uploadSCORMCourse } from "./actions/uploadSCORMCourse";
|
|
72
|
+
|
|
73
|
+
export default function ScormPage() {
|
|
74
|
+
const [file, setFile] = useState<File | null>(null);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div>
|
|
78
|
+
<input
|
|
79
|
+
type='file'
|
|
80
|
+
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
|
81
|
+
/>
|
|
82
|
+
<button onClick={() => file && uploadSCORMCourse(file)}>
|
|
83
|
+
Upload
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
24
88
|
```
|
|
25
89
|
|
|
26
|
-
|
|
90
|
+
### Server Action (unpack + save SCORM)
|
|
27
91
|
|
|
28
92
|
```typescript
|
|
29
|
-
|
|
30
|
-
|
|
93
|
+
"use server";
|
|
94
|
+
|
|
95
|
+
import { unpackSCORM, parseManifest, SupabaseAdapter } from "scorm-player";
|
|
31
96
|
|
|
32
|
-
export async function uploadSCORMCourse(file: File
|
|
33
|
-
const
|
|
97
|
+
export async function uploadSCORMCourse(file: File) {
|
|
98
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
99
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
100
|
+
|
|
101
|
+
const { files, manifestXml } = await unpackSCORM(buffer);
|
|
34
102
|
const launchUrl = parseManifest(manifestXml);
|
|
35
103
|
|
|
36
104
|
const adapter = new SupabaseAdapter(
|
|
37
105
|
process.env.SUPABASE_URL!,
|
|
38
106
|
process.env.SUPABASE_KEY!,
|
|
39
|
-
|
|
107
|
+
"courses"
|
|
40
108
|
);
|
|
41
109
|
|
|
42
|
-
await adapter.uploadFolder(
|
|
110
|
+
await adapter.uploadFolder("courses", files);
|
|
43
111
|
|
|
44
112
|
return launchUrl;
|
|
45
113
|
}
|
|
46
114
|
```
|
|
47
115
|
|
|
48
|
-
##
|
|
116
|
+
## 🖥️ Displaying SCORM Content
|
|
49
117
|
|
|
50
118
|
```typescript
|
|
51
|
-
import { ScormPlayer } from "scorm-player
|
|
52
|
-
|
|
53
|
-
<ScormPlayer
|
|
54
|
-
launchUrl='https://xyz.supabase.co/storage/v1/object/public/my_custom_folder/index.html'
|
|
55
|
-
userId={user.id}
|
|
56
|
-
courseId={course.id}
|
|
57
|
-
lessonId={lesson.id}
|
|
58
|
-
saveProgress={async (data) => {
|
|
59
|
-
await fetch("/api/scorm/saveProgress", {
|
|
60
|
-
method: "POST",
|
|
61
|
-
body: JSON.stringify({
|
|
62
|
-
userId: user.id,
|
|
63
|
-
courseId: course.id,
|
|
64
|
-
lessonId: lesson.id,
|
|
65
|
-
data,
|
|
66
|
-
}),
|
|
67
|
-
});
|
|
68
|
-
}}
|
|
69
|
-
/>;
|
|
70
|
-
```
|
|
119
|
+
import { ScormPlayer } from "scorm-player";
|
|
71
120
|
|
|
72
|
-
|
|
73
|
-
|
|
121
|
+
export default function PlayerPage() {
|
|
122
|
+
return <ScormPlayer launchUrl='courses/index.html' />;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
74
125
|
|
|
75
|
-
|
|
126
|
+
The component uses an <iframe /> to show SCORM content.
|
|
76
127
|
|
|
77
|
-
|
|
128
|
+
## 🗃️ Supabase Storage Adapter
|
|
78
129
|
|
|
79
|
-
|
|
130
|
+
Uploads all extracted SCORM files:
|
|
80
131
|
|
|
81
132
|
```typescript
|
|
82
|
-
const adapter = new SupabaseAdapter(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"my_custom_folder"
|
|
86
|
-
);
|
|
133
|
+
const adapter = new SupabaseAdapter(SUPABASE_URL, SUPABASE_KEY, "folderName");
|
|
134
|
+
|
|
135
|
+
await adapter.uploadFolder("folderName", files);
|
|
87
136
|
```
|
|
88
137
|
|
|
89
|
-
|
|
90
|
-
- `getFileUrl(path)` returns a public link to the file
|
|
138
|
+
You can also write your own adapters (S3, GCP, local FS, etc.).
|
|
91
139
|
|
|
92
|
-
##
|
|
140
|
+
## 🧩 API Reference
|
|
93
141
|
|
|
94
|
-
|
|
95
|
-
|
|
142
|
+
- `unpackSCORM(zipData: Buffer | Uint8Array)` - Unpacks a SCORM ZIP file into a dictionary of files.
|
|
143
|
+
- `parseManifest(manifestXml: string)` - Locates the SCORM launch URL from imsmanifest.xml.
|
|
144
|
+
- `SupabaseAdapter` - Uploads unpacked files into Supabase Storage.
|
|
145
|
+
- `<ScormPlayer launchUrl="..." />` - Renders SCORM content inside an iframe.
|
|
96
146
|
|
|
97
|
-
|
|
98
|
-
"cmi.location": "page_3",
|
|
99
|
-
"cmi.completion_status": "incomplete",
|
|
100
|
-
"cmi.score.raw": 80,
|
|
101
|
-
};
|
|
102
|
-
```
|
|
147
|
+
## 🛠️ Requirements
|
|
103
148
|
|
|
104
|
-
|
|
149
|
+
- Node.js 18+
|
|
150
|
+
- React 18 or newer
|
|
151
|
+
- Next.js (optional but supported)
|
|
105
152
|
|
|
106
|
-
|
|
107
|
-
- Can be used as an NPM package in any React/Next.js project
|
|
108
|
-
- Frontend component and backend functions are fully decoupled
|
|
109
|
-
- Custom folders in Supabase allow organizing courses flexibly
|
|
153
|
+
## 📄 License
|
|
110
154
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
src/
|
|
115
|
-
├─ backend/
|
|
116
|
-
│ ├─ unpackSCORM.ts
|
|
117
|
-
│ ├─ parseManifest.ts
|
|
118
|
-
│ └─ storageAdapters/supabaseAdapter.ts
|
|
119
|
-
├─ frontend/
|
|
120
|
-
│ └─ ScormPlayer.tsx
|
|
121
|
-
├─ types/
|
|
122
|
-
│ └─ scorm.ts
|
|
123
|
-
└─ index.ts
|
|
124
|
-
```
|
|
155
|
+
MIT License.
|
|
125
156
|
|
|
126
|
-
##
|
|
157
|
+
## 🎉 Done
|
|
127
158
|
|
|
128
|
-
|
|
159
|
+
Your SCORM Player is ready to use.
|
package/dist/index.d.mts
CHANGED
|
@@ -4,7 +4,7 @@ interface ParsedSCORM {
|
|
|
4
4
|
files: Record<string, string>;
|
|
5
5
|
manifestXml: string;
|
|
6
6
|
}
|
|
7
|
-
declare function unpackSCORM(
|
|
7
|
+
declare function unpackSCORM(zipData: Buffer | Uint8Array): Promise<ParsedSCORM>;
|
|
8
8
|
|
|
9
9
|
declare function parseManifest(manifestXml: string): string;
|
|
10
10
|
|
|
@@ -12,9 +12,11 @@ declare class SupabaseAdapter {
|
|
|
12
12
|
client: SupabaseClient;
|
|
13
13
|
rootFolder: string;
|
|
14
14
|
constructor(supabaseUrl: string, supabaseKey: string, rootFolder?: string);
|
|
15
|
+
generateUniqueFolder(baseName: string): string;
|
|
15
16
|
uploadFile(path: string, content: string | Buffer): Promise<string>;
|
|
16
|
-
uploadFolder(
|
|
17
|
+
uploadFolder(files: Record<string, string | Buffer>, baseFolderName?: string): Promise<string>;
|
|
17
18
|
getFileUrl(path: string): string;
|
|
19
|
+
getLaunchUrl(folderName: string, launchPath: string): string;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
interface SCORMState {
|
|
@@ -35,4 +37,4 @@ interface ScormPlayerProps {
|
|
|
35
37
|
}
|
|
36
38
|
declare function ScormPlayer({ launchUrl, userId, courseId, lessonId, saveProgress, }: ScormPlayerProps): JSX.Element;
|
|
37
39
|
|
|
38
|
-
export { type ParsedSCORM,
|
|
40
|
+
export { type ParsedSCORM, ScormPlayer, SupabaseAdapter, parseManifest, unpackSCORM };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ interface ParsedSCORM {
|
|
|
4
4
|
files: Record<string, string>;
|
|
5
5
|
manifestXml: string;
|
|
6
6
|
}
|
|
7
|
-
declare function unpackSCORM(
|
|
7
|
+
declare function unpackSCORM(zipData: Buffer | Uint8Array): Promise<ParsedSCORM>;
|
|
8
8
|
|
|
9
9
|
declare function parseManifest(manifestXml: string): string;
|
|
10
10
|
|
|
@@ -12,9 +12,11 @@ declare class SupabaseAdapter {
|
|
|
12
12
|
client: SupabaseClient;
|
|
13
13
|
rootFolder: string;
|
|
14
14
|
constructor(supabaseUrl: string, supabaseKey: string, rootFolder?: string);
|
|
15
|
+
generateUniqueFolder(baseName: string): string;
|
|
15
16
|
uploadFile(path: string, content: string | Buffer): Promise<string>;
|
|
16
|
-
uploadFolder(
|
|
17
|
+
uploadFolder(files: Record<string, string | Buffer>, baseFolderName?: string): Promise<string>;
|
|
17
18
|
getFileUrl(path: string): string;
|
|
19
|
+
getLaunchUrl(folderName: string, launchPath: string): string;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
interface SCORMState {
|
|
@@ -35,4 +37,4 @@ interface ScormPlayerProps {
|
|
|
35
37
|
}
|
|
36
38
|
declare function ScormPlayer({ launchUrl, userId, courseId, lessonId, saveProgress, }: ScormPlayerProps): JSX.Element;
|
|
37
39
|
|
|
38
|
-
export { type ParsedSCORM,
|
|
40
|
+
export { type ParsedSCORM, ScormPlayer, SupabaseAdapter, parseManifest, unpackSCORM };
|
package/dist/index.js
CHANGED
|
@@ -39,50 +39,75 @@ module.exports = __toCommonJS(src_exports);
|
|
|
39
39
|
|
|
40
40
|
// src/backend/unpackSCORM.ts
|
|
41
41
|
var import_jszip = __toESM(require("jszip"));
|
|
42
|
-
async function unpackSCORM(
|
|
43
|
-
const zip = await import_jszip.default.loadAsync(
|
|
42
|
+
async function unpackSCORM(zipData) {
|
|
43
|
+
const zip = await import_jszip.default.loadAsync(zipData);
|
|
44
44
|
const files = {};
|
|
45
45
|
for (const filename of Object.keys(zip.files)) {
|
|
46
|
-
const
|
|
47
|
-
|
|
46
|
+
const file = zip.files[filename];
|
|
47
|
+
if (!file)
|
|
48
|
+
continue;
|
|
49
|
+
const content = await file.async("string");
|
|
50
|
+
files[filename] = content;
|
|
48
51
|
}
|
|
49
52
|
const manifestXml = files["imsmanifest.xml"];
|
|
50
|
-
if (!manifestXml)
|
|
53
|
+
if (!manifestXml) {
|
|
51
54
|
throw new Error("imsmanifest.xml not found in SCORM package");
|
|
55
|
+
}
|
|
52
56
|
return { files, manifestXml };
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
// src/backend/parseManifest.ts
|
|
60
|
+
var import_xmldom = require("xmldom");
|
|
56
61
|
function parseManifest(manifestXml) {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
if (!
|
|
61
|
-
throw new Error("
|
|
62
|
-
|
|
62
|
+
const xmlDoc = new import_xmldom.DOMParser().parseFromString(manifestXml, "text/xml");
|
|
63
|
+
const resources = xmlDoc.getElementsByTagName("resource");
|
|
64
|
+
const firstResource = resources[0];
|
|
65
|
+
if (!firstResource) {
|
|
66
|
+
throw new Error("SCORM manifest has no <resource> element");
|
|
67
|
+
}
|
|
68
|
+
const launch = firstResource.getAttribute("href");
|
|
69
|
+
if (!launch) {
|
|
70
|
+
throw new Error("SCORM launch URL (href) not found in manifest");
|
|
71
|
+
}
|
|
72
|
+
return launch;
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
// src/backend/storageAdapters/supabaseAdapter.ts
|
|
66
76
|
var import_supabase_js = require("@supabase/supabase-js");
|
|
77
|
+
var import_crypto = require("crypto");
|
|
67
78
|
var SupabaseAdapter = class {
|
|
68
79
|
constructor(supabaseUrl, supabaseKey, rootFolder = "scorm-courses") {
|
|
69
80
|
this.client = (0, import_supabase_js.createClient)(supabaseUrl, supabaseKey);
|
|
70
81
|
this.rootFolder = rootFolder;
|
|
71
82
|
}
|
|
83
|
+
// Генерация уникального имени папки
|
|
84
|
+
generateUniqueFolder(baseName) {
|
|
85
|
+
const id = (0, import_crypto.randomUUID)();
|
|
86
|
+
return `${baseName}-${id}`;
|
|
87
|
+
}
|
|
72
88
|
async uploadFile(path, content) {
|
|
73
89
|
const fullPath = `${this.rootFolder}/${path}`;
|
|
74
90
|
await this.client.storage.from(this.rootFolder).upload(path, content, { upsert: true });
|
|
75
91
|
return fullPath;
|
|
76
92
|
}
|
|
77
|
-
|
|
93
|
+
// Принимаем files и optional baseName для папки
|
|
94
|
+
async uploadFolder(files, baseFolderName = "course") {
|
|
95
|
+
const uniqueFolder = this.generateUniqueFolder(baseFolderName);
|
|
78
96
|
for (const [filePath, content] of Object.entries(files)) {
|
|
79
|
-
const fullPath = `${
|
|
97
|
+
const fullPath = `${uniqueFolder}/${filePath}`;
|
|
80
98
|
await this.uploadFile(fullPath, content);
|
|
81
99
|
}
|
|
100
|
+
return uniqueFolder;
|
|
82
101
|
}
|
|
102
|
+
// Получаем публичный URL полного пути к файлу
|
|
83
103
|
getFileUrl(path) {
|
|
84
104
|
return this.client.storage.from(this.rootFolder).getPublicUrl(path).data.publicUrl;
|
|
85
105
|
}
|
|
106
|
+
// Удобная функция для получения URL к launch файлу
|
|
107
|
+
getLaunchUrl(folderName, launchPath) {
|
|
108
|
+
const fullPath = `${folderName}/${launchPath}`;
|
|
109
|
+
return this.getFileUrl(fullPath);
|
|
110
|
+
}
|
|
86
111
|
};
|
|
87
112
|
|
|
88
113
|
// src/frontend/ScormPlayer.tsx
|
package/dist/index.mjs
CHANGED
|
@@ -1,49 +1,74 @@
|
|
|
1
1
|
// src/backend/unpackSCORM.ts
|
|
2
2
|
import JSZip from "jszip";
|
|
3
|
-
async function unpackSCORM(
|
|
4
|
-
const zip = await JSZip.loadAsync(
|
|
3
|
+
async function unpackSCORM(zipData) {
|
|
4
|
+
const zip = await JSZip.loadAsync(zipData);
|
|
5
5
|
const files = {};
|
|
6
6
|
for (const filename of Object.keys(zip.files)) {
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const file = zip.files[filename];
|
|
8
|
+
if (!file)
|
|
9
|
+
continue;
|
|
10
|
+
const content = await file.async("string");
|
|
11
|
+
files[filename] = content;
|
|
9
12
|
}
|
|
10
13
|
const manifestXml = files["imsmanifest.xml"];
|
|
11
|
-
if (!manifestXml)
|
|
14
|
+
if (!manifestXml) {
|
|
12
15
|
throw new Error("imsmanifest.xml not found in SCORM package");
|
|
16
|
+
}
|
|
13
17
|
return { files, manifestXml };
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
// src/backend/parseManifest.ts
|
|
21
|
+
import { DOMParser } from "xmldom";
|
|
17
22
|
function parseManifest(manifestXml) {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
22
|
-
throw new Error("
|
|
23
|
-
|
|
23
|
+
const xmlDoc = new DOMParser().parseFromString(manifestXml, "text/xml");
|
|
24
|
+
const resources = xmlDoc.getElementsByTagName("resource");
|
|
25
|
+
const firstResource = resources[0];
|
|
26
|
+
if (!firstResource) {
|
|
27
|
+
throw new Error("SCORM manifest has no <resource> element");
|
|
28
|
+
}
|
|
29
|
+
const launch = firstResource.getAttribute("href");
|
|
30
|
+
if (!launch) {
|
|
31
|
+
throw new Error("SCORM launch URL (href) not found in manifest");
|
|
32
|
+
}
|
|
33
|
+
return launch;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
// src/backend/storageAdapters/supabaseAdapter.ts
|
|
27
37
|
import { createClient } from "@supabase/supabase-js";
|
|
38
|
+
import { randomUUID } from "crypto";
|
|
28
39
|
var SupabaseAdapter = class {
|
|
29
40
|
constructor(supabaseUrl, supabaseKey, rootFolder = "scorm-courses") {
|
|
30
41
|
this.client = createClient(supabaseUrl, supabaseKey);
|
|
31
42
|
this.rootFolder = rootFolder;
|
|
32
43
|
}
|
|
44
|
+
// Генерация уникального имени папки
|
|
45
|
+
generateUniqueFolder(baseName) {
|
|
46
|
+
const id = randomUUID();
|
|
47
|
+
return `${baseName}-${id}`;
|
|
48
|
+
}
|
|
33
49
|
async uploadFile(path, content) {
|
|
34
50
|
const fullPath = `${this.rootFolder}/${path}`;
|
|
35
51
|
await this.client.storage.from(this.rootFolder).upload(path, content, { upsert: true });
|
|
36
52
|
return fullPath;
|
|
37
53
|
}
|
|
38
|
-
|
|
54
|
+
// Принимаем files и optional baseName для папки
|
|
55
|
+
async uploadFolder(files, baseFolderName = "course") {
|
|
56
|
+
const uniqueFolder = this.generateUniqueFolder(baseFolderName);
|
|
39
57
|
for (const [filePath, content] of Object.entries(files)) {
|
|
40
|
-
const fullPath = `${
|
|
58
|
+
const fullPath = `${uniqueFolder}/${filePath}`;
|
|
41
59
|
await this.uploadFile(fullPath, content);
|
|
42
60
|
}
|
|
61
|
+
return uniqueFolder;
|
|
43
62
|
}
|
|
63
|
+
// Получаем публичный URL полного пути к файлу
|
|
44
64
|
getFileUrl(path) {
|
|
45
65
|
return this.client.storage.from(this.rootFolder).getPublicUrl(path).data.publicUrl;
|
|
46
66
|
}
|
|
67
|
+
// Удобная функция для получения URL к launch файлу
|
|
68
|
+
getLaunchUrl(folderName, launchPath) {
|
|
69
|
+
const fullPath = `${folderName}/${launchPath}`;
|
|
70
|
+
return this.getFileUrl(fullPath);
|
|
71
|
+
}
|
|
47
72
|
};
|
|
48
73
|
|
|
49
74
|
// src/frontend/ScormPlayer.tsx
|
package/package.json
CHANGED
|
@@ -1,39 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scorm-player",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "SCORM 2004 player
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "SCORM 2004 player, unpacker and manifest parser for Node.js / Next.js with storage adapters and React iframe renderer.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
7
8
|
"files": [
|
|
8
9
|
"dist"
|
|
9
10
|
],
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "tsup src/index.ts --format cjs,esm --dts --out-dir dist",
|
|
12
|
-
"clean": "rm -rf dist"
|
|
13
|
+
"clean": "rm -rf dist",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
13
15
|
},
|
|
14
16
|
"keywords": [
|
|
15
17
|
"scorm",
|
|
16
18
|
"scorm2004",
|
|
19
|
+
"scorm player",
|
|
17
20
|
"nextjs",
|
|
18
21
|
"react",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
22
|
+
"elearning",
|
|
23
|
+
"lms",
|
|
24
|
+
"imsmanifest"
|
|
21
25
|
],
|
|
22
26
|
"author": "Shmelev Sergei",
|
|
23
27
|
"license": "MIT",
|
|
24
28
|
"dependencies": {
|
|
25
29
|
"@supabase/supabase-js": "^2.9.0",
|
|
26
|
-
"jszip": "^3.10.1"
|
|
30
|
+
"jszip": "^3.10.1",
|
|
31
|
+
"xmldom": "^0.6.0"
|
|
27
32
|
},
|
|
28
33
|
"peerDependencies": {
|
|
29
|
-
"react": ">=18 <
|
|
30
|
-
"react-dom": ">=18 <
|
|
34
|
+
"react": ">=18 <21",
|
|
35
|
+
"react-dom": ">=18 <21"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
33
38
|
"@types/react": "^18.3.27",
|
|
34
39
|
"@types/react-dom": "^18.3.7",
|
|
40
|
+
"@types/xmldom": "^0.1.34",
|
|
35
41
|
"tsup": "^7.3.0",
|
|
36
|
-
"typescript": "^5.
|
|
42
|
+
"typescript": "^5.4.5"
|
|
37
43
|
},
|
|
38
44
|
"repository": {
|
|
39
45
|
"type": "git",
|