@missing-elements/h5p-offline-player 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/LICENSE +21 -0
- package/README.md +273 -0
- package/dist/VERSION +1 -0
- package/dist/frame-assets/MANIFEST.json +4 -0
- package/dist/frame-assets/fonts/h5p-core-30.woff2 +0 -0
- package/dist/frame-assets/fonts/h5p-hub-publish.woff +0 -0
- package/dist/frame-assets/fonts/h5p-hub-publish.woff2 +0 -0
- package/dist/frame-assets/fonts/h5p-theme.woff2 +0 -0
- package/dist/frame-assets/fonts/inter/Inter-ExtraBold.woff2 +0 -0
- package/dist/frame-assets/fonts/inter/Inter-ExtraBoldItalic.woff2 +0 -0
- package/dist/frame-assets/fonts/inter/LICENSE.txt +92 -0
- package/dist/frame-assets/fonts/inter/inter-v18-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-600.woff2 +0 -0
- package/dist/frame-assets/fonts/inter/inter-v18-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-600italic.woff2 +0 -0
- package/dist/frame-assets/fonts/inter/inter-v18-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-italic.woff2 +0 -0
- package/dist/frame-assets/fonts/inter/inter-v18-cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese-regular.woff2 +0 -0
- package/dist/frame-assets/fonts/open-sans/OFL.txt +88 -0
- package/dist/frame-assets/fonts/open-sans/open-sans-v40-cyrillic_cyrillic-ext_greek_greek-ext_hebrew_latin_latin-ext_math_symbols_vietnamese-600.woff2 +0 -0
- package/dist/frame-assets/fonts/open-sans/open-sans-v40-cyrillic_cyrillic-ext_greek_greek-ext_hebrew_latin_latin-ext_math_symbols_vietnamese-600italic.woff2 +0 -0
- package/dist/frame-assets/fonts/open-sans/open-sans-v40-cyrillic_cyrillic-ext_greek_greek-ext_hebrew_latin_latin-ext_math_symbols_vietnamese-700.woff2 +0 -0
- package/dist/frame-assets/fonts/open-sans/open-sans-v40-cyrillic_cyrillic-ext_greek_greek-ext_hebrew_latin_latin-ext_math_symbols_vietnamese-700italic.woff2 +0 -0
- package/dist/frame-assets/fonts/open-sans/open-sans-v40-cyrillic_cyrillic-ext_greek_greek-ext_hebrew_latin_latin-ext_math_symbols_vietnamese-italic.woff2 +0 -0
- package/dist/frame-assets/fonts/open-sans/open-sans-v40-cyrillic_cyrillic-ext_greek_greek-ext_hebrew_latin_latin-ext_math_symbols_vietnamese-regular.woff2 +0 -0
- package/dist/frame-assets/frame.bundle.js +1 -0
- package/dist/frame-assets/images/h5p.svg +16 -0
- package/dist/frame-assets/images/throbber.gif +0 -0
- package/dist/frame-assets/main.bundle.js +2 -0
- package/dist/frame-assets/styles/h5p-fonts.css +103 -0
- package/dist/frame-assets/styles/h5p.css +1643 -0
- package/dist/h5p-player.js +1780 -0
- package/dist/h5p-sw-mount.js +88 -0
- package/dist/h5p-sw.js +88 -0
- package/package.json +81 -0
- package/types/h5p-offline-player.d.ts +154 -0
- package/types/jobs/chunk-writer.d.ts +16 -0
- package/types/jobs/jobs-worker.d.ts +1 -0
- package/types/node/tsconfig.node.tsbuildinfo +1 -0
- package/types/node/vite.config.d.ts +6 -0
- package/types/node/vite.demo.config.d.ts +2 -0
- package/types/node/vite.plugins.d.ts +19 -0
- package/types/shared/chunk-store.d.ts +104 -0
- package/types/shared/constants.d.ts +67 -0
- package/types/shared/entry-names.d.ts +29 -0
- package/types/shared/eviction.d.ts +16 -0
- package/types/shared/forward-index.d.ts +85 -0
- package/types/shared/idb.d.ts +9 -0
- package/types/shared/locks.d.ts +14 -0
- package/types/shared/mime.d.ts +7 -0
- package/types/shared/mp4.d.ts +41 -0
- package/types/shared/pkg-id.d.ts +21 -0
- package/types/shared/protocol.d.ts +232 -0
- package/types/shared/range.d.ts +16 -0
- package/types/shared/source.d.ts +57 -0
- package/types/shared/stream-utils.d.ts +7 -0
- package/types/sw/frame-document.d.ts +19 -0
- package/types/sw/mount.d.ts +17 -0
- package/types/sw/package-reader.d.ts +242 -0
- package/types/sw/routes.d.ts +26 -0
- package/types/sw/stream-utils.d.ts +9 -0
- package/types/sw/sw-entry.d.ts +1 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { type FileEntry } from '@zip.js/zip.js';
|
|
2
|
+
import { type MissingLibraries, type PrefetchEntry } from '../shared/protocol';
|
|
3
|
+
import { type SourceHandle } from '../shared/source';
|
|
4
|
+
import type { ByteRange } from '../shared/range';
|
|
5
|
+
import type { ForwardIndexSnapshot } from '../shared/forward-index';
|
|
6
|
+
/**
|
|
7
|
+
* The package reader: a zip central directory turned into a name → entry index, plus the decision
|
|
8
|
+
* of how each entry gets served. It does not walk dependencies — h5p-standalone resolves
|
|
9
|
+
* `preloadedDependencies` itself and asks for the files it needs by name.
|
|
10
|
+
*/
|
|
11
|
+
export type Strategy =
|
|
12
|
+
/** Small, or text the runtime has to parse whole: inflate once into the cache, then serve. */
|
|
13
|
+
{
|
|
14
|
+
kind: 'inline';
|
|
15
|
+
}
|
|
16
|
+
/** Large and stored (method 0): the bytes are already flat in the archive, so slice them. */
|
|
17
|
+
| {
|
|
18
|
+
kind: 'slice';
|
|
19
|
+
}
|
|
20
|
+
/** Large and deflated (method 8): the Jobs worker inflates into chunks, served progressively. */
|
|
21
|
+
| {
|
|
22
|
+
kind: 'chunked';
|
|
23
|
+
};
|
|
24
|
+
/** The parts of `h5p.json` the player needs. Everything in it comes from an untrusted archive. */
|
|
25
|
+
export interface PackageManifest {
|
|
26
|
+
title?: string;
|
|
27
|
+
mainLibrary?: string;
|
|
28
|
+
preloadedDependencies?: LibraryDependency[];
|
|
29
|
+
}
|
|
30
|
+
export interface LibraryDependency {
|
|
31
|
+
machineName: string;
|
|
32
|
+
majorVersion: string | number;
|
|
33
|
+
minorVersion: string | number;
|
|
34
|
+
}
|
|
35
|
+
export interface IndexedEntry {
|
|
36
|
+
/** Normalised name, the form the virtual server routes on. */
|
|
37
|
+
name: string;
|
|
38
|
+
size: number;
|
|
39
|
+
compressedSize: number;
|
|
40
|
+
method: number;
|
|
41
|
+
strategy: Strategy;
|
|
42
|
+
/** The zip.js entry, for an index read from the central directory. Absent for a forward entry. */
|
|
43
|
+
zip?: FileEntry;
|
|
44
|
+
/** Where the compressed bytes begin, when the local header has already been read. */
|
|
45
|
+
dataStart?: number;
|
|
46
|
+
encrypted?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* An entry together with the archive it lives in. A package may be served from more than one:
|
|
50
|
+
* content from the one the user opened, libraries from a bundle attached to fill its gaps.
|
|
51
|
+
*/
|
|
52
|
+
export interface LocatedEntry {
|
|
53
|
+
reader: PackageReader;
|
|
54
|
+
entry: IndexedEntry;
|
|
55
|
+
}
|
|
56
|
+
export declare class PackageReader {
|
|
57
|
+
private readonly handle;
|
|
58
|
+
readonly pkgId: string;
|
|
59
|
+
readonly entries: Map<string, IndexedEntry>;
|
|
60
|
+
readonly rejected: string[];
|
|
61
|
+
manifest: PackageManifest;
|
|
62
|
+
/**
|
|
63
|
+
* Built from the forward index of an archive still downloading. Entries arrive as the download
|
|
64
|
+
* does — `absorb()` takes them in — and every answer is provisional until the archive is whole
|
|
65
|
+
* and a reader from its central directory replaces this one.
|
|
66
|
+
*/
|
|
67
|
+
readonly partial: boolean;
|
|
68
|
+
/** For a partial reader: how far the archive is accounted for, and whether its index is complete. */
|
|
69
|
+
parsedTo: number;
|
|
70
|
+
forwardDone: boolean;
|
|
71
|
+
private forwardStopped;
|
|
72
|
+
private absorbed;
|
|
73
|
+
/** Top-level folder of the newest entry: the one folder that may still be receiving files. */
|
|
74
|
+
private lastTopFolder;
|
|
75
|
+
/** Archives consulted, in order, for an entry this one does not have. */
|
|
76
|
+
private readonly fallbacks;
|
|
77
|
+
/** Library folders reachable from here, built on first use and dropped when `use()` adds more. */
|
|
78
|
+
private librariesByName;
|
|
79
|
+
/** Data offsets of stored entries, each read from its local header once. */
|
|
80
|
+
private readonly dataRanges;
|
|
81
|
+
private constructor();
|
|
82
|
+
get title(): string | undefined;
|
|
83
|
+
static open(pkgId: string, handle: SourceHandle, options?: {
|
|
84
|
+
requireLibraries?: boolean;
|
|
85
|
+
}): Promise<PackageReader>;
|
|
86
|
+
/**
|
|
87
|
+
* A reader over an archive that is still downloading, from the entries its local headers have
|
|
88
|
+
* given up so far. Nothing is asserted here: what is missing may simply not have arrived.
|
|
89
|
+
*/
|
|
90
|
+
static fromForwardIndex(pkgId: string, handle: SourceHandle, snapshot: ForwardIndexSnapshot): Promise<PackageReader>;
|
|
91
|
+
/**
|
|
92
|
+
* Takes in the entries a newer snapshot has and this reader does not. Snapshots are cumulative
|
|
93
|
+
* and in archive order, so only the tail is new; the same first-occurrence rule as the full
|
|
94
|
+
* index applies, so a later duplicate can never shadow an entry already served.
|
|
95
|
+
*/
|
|
96
|
+
absorb(snapshot: ForwardIndexSnapshot): Promise<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* For a partial reader: whether the runtime could boot from what has arrived — `h5p.json`, and
|
|
99
|
+
* for every dependency it declares a folder that is present and finished arriving. A folder has
|
|
100
|
+
* finished once an entry of a later folder has been seen, since exporters write a folder's files
|
|
101
|
+
* together; only the newest folder may still be growing. An index the scanner had to give up on
|
|
102
|
+
* never says ready: what it could not see must wait for the central directory.
|
|
103
|
+
*/
|
|
104
|
+
bootReady(): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* For a partial reader: a miss that can be answered without waiting for more of the archive.
|
|
107
|
+
* True when the entry's top-level folder has already finished arriving, or when the request
|
|
108
|
+
* is h5p-standalone's versioned probe on a package that keeps that library unversioned.
|
|
109
|
+
*/
|
|
110
|
+
provablyAbsent(name: string): boolean;
|
|
111
|
+
private folderComplete;
|
|
112
|
+
private hasFolder;
|
|
113
|
+
/** Adds an archive to consult for entries this one does not carry. */
|
|
114
|
+
use(reader: PackageReader): void;
|
|
115
|
+
/**
|
|
116
|
+
* Looks an entry up in this archive, then in each attached one.
|
|
117
|
+
*
|
|
118
|
+
* A miss on a library path is retried against a compatible version of the same library, so
|
|
119
|
+
* content asking for `H5P.Text-1.0/...` is served from `H5P.Text-1.1/...` when that is what
|
|
120
|
+
* the attached bundle happens to ship.
|
|
121
|
+
*/
|
|
122
|
+
get(name: string): LocatedEntry | undefined;
|
|
123
|
+
/** Exact lookup, this archive first and then each attached one, in order. */
|
|
124
|
+
private locate;
|
|
125
|
+
private locateCompatible;
|
|
126
|
+
/**
|
|
127
|
+
* Library folders reachable from here, this archive's own taking precedence.
|
|
128
|
+
*
|
|
129
|
+
* Memoised, because this runs on the miss path and misses are not rare: h5p-standalone probes
|
|
130
|
+
* `<Library>-<major>.<minor>/library.json` on purpose to learn whether a package uses versioned
|
|
131
|
+
* folders, so every load walks through here several times. Rebuilding a map over every entry
|
|
132
|
+
* name — two thousand of them in a real package — on each of those is work that never changes
|
|
133
|
+
* its answer. The entry set is fixed after `open`; only `use()` can change the result, and it
|
|
134
|
+
* drops the cache.
|
|
135
|
+
*/
|
|
136
|
+
private availableLibraries;
|
|
137
|
+
/** Every entry name reachable through this reader, including the attached archives. */
|
|
138
|
+
entryNames(): Set<string>;
|
|
139
|
+
/** What `h5p.json` declares but nothing reachable from here provides. */
|
|
140
|
+
missingLibraries(): MissingLibraries | null;
|
|
141
|
+
/** True when another archive is supplying entries this one does not have. */
|
|
142
|
+
get hasFallbacks(): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* The manifest as the runtime should see it once a library bundle is attached.
|
|
145
|
+
*
|
|
146
|
+
* A content-only export does not just drop the library folders, it also strips
|
|
147
|
+
* `preloadedDependencies` down to the main library — so the runtime would load Interactive
|
|
148
|
+
* Video and none of the interaction types used inside it, and the first interaction would fail
|
|
149
|
+
* with "Unable to find constructor". The bundle's own manifest is the list the content type
|
|
150
|
+
* was published with, so the two are merged: a dependency is kept only if its folder is
|
|
151
|
+
* actually reachable, and the content's own version wins when both archives have one.
|
|
152
|
+
*/
|
|
153
|
+
mergedManifest(): PackageManifest;
|
|
154
|
+
assertLibrariesPresent(): void;
|
|
155
|
+
/**
|
|
156
|
+
* Entries that cannot be served cold without a background inflate, in archive order.
|
|
157
|
+
*
|
|
158
|
+
* Everything else is reachable on demand: a stored entry is already flat in the archive and
|
|
159
|
+
* gets sliced, and a small one is cheap to inflate when asked for. Only a large deflated entry
|
|
160
|
+
* makes the runtime wait on a job, so only these are worth starting early.
|
|
161
|
+
*
|
|
162
|
+
* Archive order, not largest first: the point is to have an entry before the learner reaches
|
|
163
|
+
* it, and the order the packager wrote them in tracks the order the content uses them far
|
|
164
|
+
* better than size does. The biggest video is not usually the first one on screen.
|
|
165
|
+
*/
|
|
166
|
+
prefetchable(): PrefetchEntry[];
|
|
167
|
+
/**
|
|
168
|
+
* Inflates an entry into a stream. The stream is handed straight to `cache.put()` or to a
|
|
169
|
+
* `Response`, so the entry never sits in memory as a whole.
|
|
170
|
+
*
|
|
171
|
+
* An entry from the central directory goes through zip.js. A forward entry has no zip.js object
|
|
172
|
+
* behind it and is inflated directly: its compressed span is known from its local header, and
|
|
173
|
+
* `DecompressionStream` does the rest — the same codec zip.js would have used.
|
|
174
|
+
*/
|
|
175
|
+
inflate(entry: IndexedEntry): ReadableStream<Uint8Array>;
|
|
176
|
+
/**
|
|
177
|
+
* Byte range of an entry's compressed data inside the archive. The central directory records
|
|
178
|
+
* where the *local* header starts, and the local header's own name and extra fields are what
|
|
179
|
+
* stand between it and the bytes, so the header has to be read to find them — unless a forward
|
|
180
|
+
* entry already knows, having come from that very header.
|
|
181
|
+
*
|
|
182
|
+
* Read once per entry and kept as a promise: a media element opens with a burst of range
|
|
183
|
+
* requests, and every one of them used to pay a round trip for the same thirty bytes — over
|
|
184
|
+
* HTTP, a second request on top of the one for the data — and every seek after that paid it
|
|
185
|
+
* again. Now the burst shares one read and a seek pays none.
|
|
186
|
+
*/
|
|
187
|
+
dataRange(entry: IndexedEntry): Promise<ByteRange>;
|
|
188
|
+
private inflateDirect;
|
|
189
|
+
private readDataRange;
|
|
190
|
+
/** Streams a range of an entry's compressed bytes straight out of the source. No storage. */
|
|
191
|
+
sliceStream(entry: IndexedEntry, range: ByteRange): Promise<ReadableStream<Uint8Array>>;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Serving strategy for one entry. Size decides first, then compression method: only a large
|
|
195
|
+
* deflated entry is worth the cost of a background extraction.
|
|
196
|
+
*/
|
|
197
|
+
export declare function chooseStrategy(name: string, entry: {
|
|
198
|
+
uncompressedSize: number;
|
|
199
|
+
compressionMethod: number;
|
|
200
|
+
}): Strategy;
|
|
201
|
+
export interface LibraryFolder {
|
|
202
|
+
machineName: string;
|
|
203
|
+
major: number;
|
|
204
|
+
minor: number;
|
|
205
|
+
folder: string;
|
|
206
|
+
}
|
|
207
|
+
/** Splits `H5P.Text-1.1` into its parts, or returns `null` for a folder that is not a library. */
|
|
208
|
+
export declare function parseLibraryFolder(folder: string): LibraryFolder | null;
|
|
209
|
+
/** Every library folder in a set of entry names, grouped by machine name. */
|
|
210
|
+
export declare function indexLibraryFolders(entryNames: Iterable<string>): Map<string, LibraryFolder[]>;
|
|
211
|
+
/**
|
|
212
|
+
* Picks the folder that satisfies a dependency, by H5P's own compatibility rule: the same major
|
|
213
|
+
* version, and a minor at least as high as the one asked for. Libraries are backward compatible
|
|
214
|
+
* within a major, which is why a platform installs one version per major and content authored
|
|
215
|
+
* against an older minor keeps working.
|
|
216
|
+
*
|
|
217
|
+
* This matters whenever libraries come from somewhere other than the package: a hub bundle ships
|
|
218
|
+
* the current `H5P.Text-1.1` while content authored earlier asks for `H5P.Text-1.0`, and without
|
|
219
|
+
* this every such request is a 404.
|
|
220
|
+
*
|
|
221
|
+
* The lowest satisfying version wins, as the closest to what the content was written against.
|
|
222
|
+
*/
|
|
223
|
+
export declare function resolveLibraryFolder(available: Map<string, LibraryFolder[]>, dependency: {
|
|
224
|
+
machineName: string;
|
|
225
|
+
major: number;
|
|
226
|
+
minor: number;
|
|
227
|
+
}): string | undefined;
|
|
228
|
+
/**
|
|
229
|
+
* The dependencies `h5p.json` declares that the archive does not actually carry. Pure, so the
|
|
230
|
+
* rule can be checked without building a zip.
|
|
231
|
+
*/
|
|
232
|
+
export declare function findMissingLibraries(entryNames: ReadonlySet<string>, manifest: PackageManifest): LibraryDependency[];
|
|
233
|
+
/**
|
|
234
|
+
* What a package declares but does not carry, or `null` when it carries everything.
|
|
235
|
+
*
|
|
236
|
+
* Exports from h5p.com and h5p.org routinely leave the libraries out: the platform they came from
|
|
237
|
+
* already has them, so bundling them would be waste. Played anywhere else, such an archive is
|
|
238
|
+
* just a `content/` folder — the runtime asks for `<MainLibrary>/library.json`, gets a 404, and
|
|
239
|
+
* fails with nothing to go on.
|
|
240
|
+
*/
|
|
241
|
+
export declare function describeMissingLibraries(entryNames: ReadonlySet<string>, manifest: PackageManifest): MissingLibraries | null;
|
|
242
|
+
export declare function explainMissingLibraries(missing: MissingLibraries): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL shape of the virtual routes. Our own registration uses the scope `<swDir>h5p/`, so routes
|
|
3
|
+
* sit directly under it; `mountH5P` puts them at `<hostScope>h5p/`. Both spell the same thing —
|
|
4
|
+
* `…/h5p/virtual/…` and `…/h5p/frame/…` — so everything downstream works off one base.
|
|
5
|
+
*/
|
|
6
|
+
export interface Routes {
|
|
7
|
+
/** Absolute URL prefix, always ending in `h5p/`. */
|
|
8
|
+
base: string;
|
|
9
|
+
virtual: string;
|
|
10
|
+
frame: string;
|
|
11
|
+
ping: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function routesFor(scope: string): Routes;
|
|
14
|
+
export type RouteMatch = {
|
|
15
|
+
kind: 'ping';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'frame';
|
|
18
|
+
pkgId: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'entry';
|
|
21
|
+
pkgId: string;
|
|
22
|
+
path: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'none';
|
|
25
|
+
};
|
|
26
|
+
export declare function matchRoute(routes: Routes, url: string): RouteMatch;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ByteRange } from '../shared/range';
|
|
2
|
+
/**
|
|
3
|
+
* Restricts a byte stream to an inclusive range without buffering it. Used to answer a `Range`
|
|
4
|
+
* request from an entry the cache holds in one piece: the body is sliced as it flows rather than
|
|
5
|
+
* read into memory and cut.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sliceStream(source: ReadableStream<Uint8Array>, range: ByteRange): ReadableStream<Uint8Array>;
|
|
8
|
+
/** Counts bytes as they pass, reporting progress without holding on to any of them. */
|
|
9
|
+
export declare function tapProgress(source: ReadableStream<Uint8Array>, onBytes: (total: number) => void): ReadableStream<Uint8Array>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|