scorm-player 1.0.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 +128 -0
- package/dist/index.d.mts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +128 -0
- package/dist/index.mjs +88 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# SCORM Player 2004
|
|
2
|
+
|
|
3
|
+
scorm-player is a universal module for working with SCORM 2004 packages. It works with Next.js, React, and any Node.js project.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Upload and unpack SCORM 2004 packages on the server
|
|
8
|
+
- Save files to Supabase Storage with a customizable folder
|
|
9
|
+
- Generate launch URLs for lessons or tests
|
|
10
|
+
- React component ScormPlayer to embed SCORM content
|
|
11
|
+
- Track user progress and save it to a database
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install scorm-player
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
React and ReactDOM must be installed in your project:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install react react-dom
|
|
23
|
+
npm install --save-dev @types/react @types/react-dom
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Backend: SCORM unpacking and uploading
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { unpackSCORM, parseManifest } from "scorm-player/backend";
|
|
30
|
+
import { SupabaseAdapter } from "scorm-player/backend/storageAdapters/supabaseAdapter";
|
|
31
|
+
|
|
32
|
+
export async function uploadSCORMCourse(file: File, folderName: string) {
|
|
33
|
+
const { files, manifestXml } = await unpackSCORM(file);
|
|
34
|
+
const launchUrl = parseManifest(manifestXml);
|
|
35
|
+
|
|
36
|
+
const adapter = new SupabaseAdapter(
|
|
37
|
+
process.env.SUPABASE_URL!,
|
|
38
|
+
process.env.SUPABASE_KEY!,
|
|
39
|
+
folderName
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
await adapter.uploadFolder(folderName, files);
|
|
43
|
+
|
|
44
|
+
return launchUrl;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Frontend: React ScormPlayer component
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { ScormPlayer } from "scorm-player/frontend";
|
|
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
|
+
```
|
|
71
|
+
|
|
72
|
+
- `launchUrl` — public URL to the main SCORM lesson HTML file
|
|
73
|
+
- `saveProgress` — callback to save user progress
|
|
74
|
+
|
|
75
|
+
## Supabase Storage
|
|
76
|
+
|
|
77
|
+
Create a bucket, e.g., scorm-courses
|
|
78
|
+
|
|
79
|
+
You can set a custom folder name:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const adapter = new SupabaseAdapter(
|
|
83
|
+
process.env.SUPABASE_URL!,
|
|
84
|
+
process.env.SUPABASE_KEY!,
|
|
85
|
+
"my_custom_folder"
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- All SCORM files will be uploaded to the specified folder
|
|
90
|
+
- `getFileUrl(path)` returns a public link to the file
|
|
91
|
+
|
|
92
|
+
## TypeScript typings
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { SCORMState } from "scorm-player/types/scorm";
|
|
96
|
+
|
|
97
|
+
const progress: SCORMState = {
|
|
98
|
+
"cmi.location": "page_3",
|
|
99
|
+
"cmi.completion_status": "incomplete",
|
|
100
|
+
"cmi.score.raw": 80,
|
|
101
|
+
};
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Features
|
|
105
|
+
|
|
106
|
+
- Supports SCORM 2004 API (API_1484_11)
|
|
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
|
|
110
|
+
|
|
111
|
+
## Project structure example
|
|
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
|
+
```
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
interface ParsedSCORM {
|
|
4
|
+
files: Record<string, string>;
|
|
5
|
+
manifestXml: string;
|
|
6
|
+
}
|
|
7
|
+
declare function unpackSCORM(zipFile: File | Buffer): Promise<ParsedSCORM>;
|
|
8
|
+
|
|
9
|
+
declare function parseManifest(manifestXml: string): string;
|
|
10
|
+
|
|
11
|
+
declare class SupabaseAdapter {
|
|
12
|
+
client: SupabaseClient;
|
|
13
|
+
rootFolder: string;
|
|
14
|
+
constructor(supabaseUrl: string, supabaseKey: string, rootFolder?: string);
|
|
15
|
+
uploadFile(path: string, content: string | Buffer): Promise<string>;
|
|
16
|
+
uploadFolder(folderPath: string, files: Record<string, string | Buffer>): Promise<void>;
|
|
17
|
+
getFileUrl(path: string): string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface SCORMState {
|
|
21
|
+
"cmi.location"?: string;
|
|
22
|
+
"cmi.completion_status"?: string;
|
|
23
|
+
"cmi.success_status"?: string;
|
|
24
|
+
"cmi.score.raw"?: number;
|
|
25
|
+
"cmi.progress_measure"?: number;
|
|
26
|
+
"cmi.suspend_data"?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ScormPlayerProps {
|
|
30
|
+
launchUrl: string;
|
|
31
|
+
userId: string;
|
|
32
|
+
courseId: string;
|
|
33
|
+
lessonId: string;
|
|
34
|
+
saveProgress: (data: SCORMState) => void;
|
|
35
|
+
}
|
|
36
|
+
declare function ScormPlayer({ launchUrl, userId, courseId, lessonId, saveProgress, }: ScormPlayerProps): JSX.Element;
|
|
37
|
+
|
|
38
|
+
export { type ParsedSCORM, type SCORMState, ScormPlayer, SupabaseAdapter, parseManifest, unpackSCORM };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
interface ParsedSCORM {
|
|
4
|
+
files: Record<string, string>;
|
|
5
|
+
manifestXml: string;
|
|
6
|
+
}
|
|
7
|
+
declare function unpackSCORM(zipFile: File | Buffer): Promise<ParsedSCORM>;
|
|
8
|
+
|
|
9
|
+
declare function parseManifest(manifestXml: string): string;
|
|
10
|
+
|
|
11
|
+
declare class SupabaseAdapter {
|
|
12
|
+
client: SupabaseClient;
|
|
13
|
+
rootFolder: string;
|
|
14
|
+
constructor(supabaseUrl: string, supabaseKey: string, rootFolder?: string);
|
|
15
|
+
uploadFile(path: string, content: string | Buffer): Promise<string>;
|
|
16
|
+
uploadFolder(folderPath: string, files: Record<string, string | Buffer>): Promise<void>;
|
|
17
|
+
getFileUrl(path: string): string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface SCORMState {
|
|
21
|
+
"cmi.location"?: string;
|
|
22
|
+
"cmi.completion_status"?: string;
|
|
23
|
+
"cmi.success_status"?: string;
|
|
24
|
+
"cmi.score.raw"?: number;
|
|
25
|
+
"cmi.progress_measure"?: number;
|
|
26
|
+
"cmi.suspend_data"?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ScormPlayerProps {
|
|
30
|
+
launchUrl: string;
|
|
31
|
+
userId: string;
|
|
32
|
+
courseId: string;
|
|
33
|
+
lessonId: string;
|
|
34
|
+
saveProgress: (data: SCORMState) => void;
|
|
35
|
+
}
|
|
36
|
+
declare function ScormPlayer({ launchUrl, userId, courseId, lessonId, saveProgress, }: ScormPlayerProps): JSX.Element;
|
|
37
|
+
|
|
38
|
+
export { type ParsedSCORM, type SCORMState, ScormPlayer, SupabaseAdapter, parseManifest, unpackSCORM };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
ScormPlayer: () => ScormPlayer,
|
|
34
|
+
SupabaseAdapter: () => SupabaseAdapter,
|
|
35
|
+
parseManifest: () => parseManifest,
|
|
36
|
+
unpackSCORM: () => unpackSCORM
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(src_exports);
|
|
39
|
+
|
|
40
|
+
// src/backend/unpackSCORM.ts
|
|
41
|
+
var import_jszip = __toESM(require("jszip"));
|
|
42
|
+
async function unpackSCORM(zipFile) {
|
|
43
|
+
const zip = await import_jszip.default.loadAsync(zipFile);
|
|
44
|
+
const files = {};
|
|
45
|
+
for (const filename of Object.keys(zip.files)) {
|
|
46
|
+
const fileData = await zip.files[filename].async("string");
|
|
47
|
+
files[filename] = fileData;
|
|
48
|
+
}
|
|
49
|
+
const manifestXml = files["imsmanifest.xml"];
|
|
50
|
+
if (!manifestXml)
|
|
51
|
+
throw new Error("imsmanifest.xml not found in SCORM package");
|
|
52
|
+
return { files, manifestXml };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/backend/parseManifest.ts
|
|
56
|
+
function parseManifest(manifestXml) {
|
|
57
|
+
const parser = new DOMParser();
|
|
58
|
+
const xml = parser.parseFromString(manifestXml, "text/xml");
|
|
59
|
+
const resource = xml.querySelector("resource");
|
|
60
|
+
if (!resource)
|
|
61
|
+
throw new Error("No <resource> found in manifest");
|
|
62
|
+
return resource.getAttribute("href") || "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/backend/storageAdapters/supabaseAdapter.ts
|
|
66
|
+
var import_supabase_js = require("@supabase/supabase-js");
|
|
67
|
+
var SupabaseAdapter = class {
|
|
68
|
+
constructor(supabaseUrl, supabaseKey, rootFolder = "scorm-courses") {
|
|
69
|
+
this.client = (0, import_supabase_js.createClient)(supabaseUrl, supabaseKey);
|
|
70
|
+
this.rootFolder = rootFolder;
|
|
71
|
+
}
|
|
72
|
+
async uploadFile(path, content) {
|
|
73
|
+
const fullPath = `${this.rootFolder}/${path}`;
|
|
74
|
+
await this.client.storage.from(this.rootFolder).upload(path, content, { upsert: true });
|
|
75
|
+
return fullPath;
|
|
76
|
+
}
|
|
77
|
+
async uploadFolder(folderPath, files) {
|
|
78
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
79
|
+
const fullPath = `${folderPath}/${filePath}`;
|
|
80
|
+
await this.uploadFile(fullPath, content);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
getFileUrl(path) {
|
|
84
|
+
return this.client.storage.from(this.rootFolder).getPublicUrl(path).data.publicUrl;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/frontend/ScormPlayer.tsx
|
|
89
|
+
var import_react = require("react");
|
|
90
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
91
|
+
function ScormPlayer({
|
|
92
|
+
launchUrl,
|
|
93
|
+
userId,
|
|
94
|
+
courseId,
|
|
95
|
+
lessonId,
|
|
96
|
+
saveProgress
|
|
97
|
+
}) {
|
|
98
|
+
const iframeRef = (0, import_react.useRef)(null);
|
|
99
|
+
const [scormState, setScormState] = (0, import_react.useState)({});
|
|
100
|
+
(0, import_react.useEffect)(() => {
|
|
101
|
+
window.API_1484_11 = {
|
|
102
|
+
Initialize: () => "true",
|
|
103
|
+
Terminate: () => "true",
|
|
104
|
+
GetValue: (key) => scormState[key] || "",
|
|
105
|
+
SetValue: (key, value) => {
|
|
106
|
+
const updatedState = { ...scormState, [key]: value };
|
|
107
|
+
setScormState(updatedState);
|
|
108
|
+
saveProgress(updatedState);
|
|
109
|
+
return "true";
|
|
110
|
+
},
|
|
111
|
+
Commit: () => {
|
|
112
|
+
saveProgress(scormState);
|
|
113
|
+
return "true";
|
|
114
|
+
},
|
|
115
|
+
GetLastError: () => 0,
|
|
116
|
+
GetErrorString: () => "",
|
|
117
|
+
GetDiagnostic: () => ""
|
|
118
|
+
};
|
|
119
|
+
}, [scormState, saveProgress]);
|
|
120
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("iframe", { ref: iframeRef, src: launchUrl, width: "100%", height: "600px" });
|
|
121
|
+
}
|
|
122
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
123
|
+
0 && (module.exports = {
|
|
124
|
+
ScormPlayer,
|
|
125
|
+
SupabaseAdapter,
|
|
126
|
+
parseManifest,
|
|
127
|
+
unpackSCORM
|
|
128
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/backend/unpackSCORM.ts
|
|
2
|
+
import JSZip from "jszip";
|
|
3
|
+
async function unpackSCORM(zipFile) {
|
|
4
|
+
const zip = await JSZip.loadAsync(zipFile);
|
|
5
|
+
const files = {};
|
|
6
|
+
for (const filename of Object.keys(zip.files)) {
|
|
7
|
+
const fileData = await zip.files[filename].async("string");
|
|
8
|
+
files[filename] = fileData;
|
|
9
|
+
}
|
|
10
|
+
const manifestXml = files["imsmanifest.xml"];
|
|
11
|
+
if (!manifestXml)
|
|
12
|
+
throw new Error("imsmanifest.xml not found in SCORM package");
|
|
13
|
+
return { files, manifestXml };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/backend/parseManifest.ts
|
|
17
|
+
function parseManifest(manifestXml) {
|
|
18
|
+
const parser = new DOMParser();
|
|
19
|
+
const xml = parser.parseFromString(manifestXml, "text/xml");
|
|
20
|
+
const resource = xml.querySelector("resource");
|
|
21
|
+
if (!resource)
|
|
22
|
+
throw new Error("No <resource> found in manifest");
|
|
23
|
+
return resource.getAttribute("href") || "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/backend/storageAdapters/supabaseAdapter.ts
|
|
27
|
+
import { createClient } from "@supabase/supabase-js";
|
|
28
|
+
var SupabaseAdapter = class {
|
|
29
|
+
constructor(supabaseUrl, supabaseKey, rootFolder = "scorm-courses") {
|
|
30
|
+
this.client = createClient(supabaseUrl, supabaseKey);
|
|
31
|
+
this.rootFolder = rootFolder;
|
|
32
|
+
}
|
|
33
|
+
async uploadFile(path, content) {
|
|
34
|
+
const fullPath = `${this.rootFolder}/${path}`;
|
|
35
|
+
await this.client.storage.from(this.rootFolder).upload(path, content, { upsert: true });
|
|
36
|
+
return fullPath;
|
|
37
|
+
}
|
|
38
|
+
async uploadFolder(folderPath, files) {
|
|
39
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
40
|
+
const fullPath = `${folderPath}/${filePath}`;
|
|
41
|
+
await this.uploadFile(fullPath, content);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
getFileUrl(path) {
|
|
45
|
+
return this.client.storage.from(this.rootFolder).getPublicUrl(path).data.publicUrl;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/frontend/ScormPlayer.tsx
|
|
50
|
+
import { useEffect, useRef, useState } from "react";
|
|
51
|
+
import { jsx } from "react/jsx-runtime";
|
|
52
|
+
function ScormPlayer({
|
|
53
|
+
launchUrl,
|
|
54
|
+
userId,
|
|
55
|
+
courseId,
|
|
56
|
+
lessonId,
|
|
57
|
+
saveProgress
|
|
58
|
+
}) {
|
|
59
|
+
const iframeRef = useRef(null);
|
|
60
|
+
const [scormState, setScormState] = useState({});
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
window.API_1484_11 = {
|
|
63
|
+
Initialize: () => "true",
|
|
64
|
+
Terminate: () => "true",
|
|
65
|
+
GetValue: (key) => scormState[key] || "",
|
|
66
|
+
SetValue: (key, value) => {
|
|
67
|
+
const updatedState = { ...scormState, [key]: value };
|
|
68
|
+
setScormState(updatedState);
|
|
69
|
+
saveProgress(updatedState);
|
|
70
|
+
return "true";
|
|
71
|
+
},
|
|
72
|
+
Commit: () => {
|
|
73
|
+
saveProgress(scormState);
|
|
74
|
+
return "true";
|
|
75
|
+
},
|
|
76
|
+
GetLastError: () => 0,
|
|
77
|
+
GetErrorString: () => "",
|
|
78
|
+
GetDiagnostic: () => ""
|
|
79
|
+
};
|
|
80
|
+
}, [scormState, saveProgress]);
|
|
81
|
+
return /* @__PURE__ */ jsx("iframe", { ref: iframeRef, src: launchUrl, width: "100%", height: "600px" });
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
ScormPlayer,
|
|
85
|
+
SupabaseAdapter,
|
|
86
|
+
parseManifest,
|
|
87
|
+
unpackSCORM
|
|
88
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scorm-player",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SCORM 2004 player module for Next.js and other frameworks, with backend unpacking and SCORM API support.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --out-dir dist",
|
|
12
|
+
"clean": "rm -rf dist"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"scorm",
|
|
16
|
+
"scorm2004",
|
|
17
|
+
"nextjs",
|
|
18
|
+
"react",
|
|
19
|
+
"player",
|
|
20
|
+
"elearning"
|
|
21
|
+
],
|
|
22
|
+
"author": "Shmelev Sergei",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@supabase/supabase-js": "^2.9.0",
|
|
26
|
+
"jszip": "^3.10.1"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": "^18.3.1",
|
|
30
|
+
"react-dom": "^18.3.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "^18.3.27",
|
|
34
|
+
"@types/react-dom": "^18.3.7",
|
|
35
|
+
"tsup": "^7.3.0",
|
|
36
|
+
"typescript": "^5.2.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/shmelevsergei/scorm-player-2004.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/shmelevsergei/scorm-player-2004/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/shmelevsergei/scorm-player-2004#readme"
|
|
46
|
+
}
|