react-native-model-viewer-webview 0.1.0 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to this package should be documented here.
5
5
  This project uses semantic versioning. Versions below `1.0.0` may still change
6
6
  minor APIs when the release notes call it out clearly.
7
7
 
8
+ ## 0.2.1 - 2026-06-30
9
+
10
+ This is a docs and confidence release. There are no runtime API changes.
11
+
12
+ ### Documentation
13
+
14
+ - Add Android Expo Go smoke-test notes for remote GLB loading and local GLB
15
+ loading.
16
+ - Document the local Expo asset path that worked in testing: resolve the
17
+ bundled `.glb`, read it as base64, and pass a
18
+ `data:model/gltf-binary;base64,...` URI to `ModelViewerWebView`.
19
+ - Call out that direct Android WebView loading from a local `file:` GLB URI was
20
+ not reliable in this smoke test.
21
+ - Add a verified smoke-test table to the README and compatibility docs, with
22
+ iOS WKWebView still marked as not yet tested.
23
+ - Add the Android local-GLB screenshot and compatibility section to the public
24
+ docs site.
25
+
26
+ ### Maintenance
27
+
28
+ - Update docs-site tests so the new local-GLB guidance and smoke-test evidence
29
+ stay present.
30
+
31
+ ## 0.2.0 - 2026-06-02
32
+
33
+ ### Added
34
+
35
+ - Bundle `@google/model-viewer` 4.2.0 inside the package so the default WebView
36
+ runtime no longer needs a CDN request.
37
+ - Add third-party notice and Apache-2.0 license metadata for the vendored
38
+ `@google/model-viewer` runtime.
39
+ - Export `BUNDLED_MODEL_VIEWER_VERSION` so consumers can inspect the embedded
40
+ runtime version.
41
+ - Export `MODEL_VIEWER_CDN_SCRIPT_URL` for apps that explicitly opt back into
42
+ the Google CDN runtime.
43
+
44
+ ### Changed
45
+
46
+ - Make generated model-viewer HTML offline-first by inlining the bundled
47
+ runtime by default.
48
+ - Keep `modelViewerScriptUrl` and `modelViewerScript` as explicit overrides for
49
+ custom CDN, local file, or app-bundled script loading.
50
+ - Keep `DEFAULT_MODEL_VIEWER_SCRIPT_URL` as a compatibility alias for
51
+ `MODEL_VIEWER_CDN_SCRIPT_URL`.
52
+
8
53
  ## 0.1.0 - 2026-05-31
9
54
 
10
55
  Initial public release of `react-native-model-viewer-webview`.
package/README.md CHANGED
@@ -122,6 +122,31 @@ await carModel.downloadAsync();
122
122
  `localUri` is preferred over `uri` when both exist. `modelUri` is still accepted
123
123
  as a string-only compatibility prop.
124
124
 
125
+ For Android Expo Go, direct `file:` loading from WebView can be unreliable. The
126
+ verified local-asset path is to read the Expo asset as base64 and pass a GLB
127
+ data URI:
128
+
129
+ ```tsx
130
+ import { Asset } from "expo-asset";
131
+ import * as FileSystem from "expo-file-system/legacy";
132
+ import { ModelViewerWebView } from "react-native-model-viewer-webview";
133
+
134
+ const model = Asset.fromModule(require("./assets/model.glb"));
135
+ await model.downloadAsync();
136
+
137
+ const localUri = model.localUri ?? model.uri;
138
+ const base64 = await FileSystem.readAsStringAsync(localUri, {
139
+ encoding: FileSystem.EncodingType.Base64,
140
+ });
141
+ const modelSource = `data:model/gltf-binary;base64,${base64}`;
142
+
143
+ <ModelViewerWebView modelSource={modelSource} style={{ height: 320 }} />;
144
+ ```
145
+
146
+ This data-URI path was smoke-tested on Android Expo Go with a local Khronos
147
+ `Box.glb` asset. Use `file:` URIs only after testing them on your target
148
+ WebView/device matrix.
149
+
125
150
  ## Metro Setup For Local GLB Files
126
151
 
127
152
  If your app imports `.glb` files, add `glb` to Metro's asset extensions.
@@ -160,17 +185,27 @@ from Metro's file map.
160
185
 
161
186
  ## Offline Script Loading
162
187
 
163
- By default, the generated WebView HTML loads `<model-viewer>` from Google's CDN.
164
- That keeps the package tiny, but it means the viewer needs network access the
165
- first time the WebView loads.
188
+ By default, the generated WebView HTML inlines a vendored copy of
189
+ `@google/model-viewer` 4.2.0. That means the viewer runtime does not need a CDN
190
+ request, and local `.glb` previews can work offline when the model asset is also
191
+ available locally.
166
192
 
167
- For offline apps, pass your own script URL:
193
+ This makes the npm package larger, but keeps runtime behavior deterministic and
194
+ offline-friendly. The Apache-2.0 license for the bundled runtime is included in
195
+ [`vendor/model-viewer/LICENSE`](./vendor/model-viewer/LICENSE), with a summary in
196
+ [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md). The exact source package,
197
+ file hash, and generated runtime details are recorded in
198
+ [`vendor/model-viewer/SOURCE.md`](./vendor/model-viewer/SOURCE.md).
199
+
200
+ If you prefer a custom or CDN-hosted runtime, pass your own script URL:
168
201
 
169
202
  ```tsx
203
+ import { MODEL_VIEWER_CDN_SCRIPT_URL } from "react-native-model-viewer-webview";
204
+
170
205
  <ModelViewerWebView
171
- modelSource={require("./assets/car.glb")}
206
+ modelSource="https://example.com/car.glb"
172
207
  htmlOptions={{
173
- modelViewerScriptUrl: "file:///path/to/model-viewer.min.js",
208
+ modelViewerScriptUrl: MODEL_VIEWER_CDN_SCRIPT_URL,
174
209
  }}
175
210
  />
176
211
  ```
@@ -186,6 +221,15 @@ Or pass an inline module script if your build already bundles the source:
186
221
  />
187
222
  ```
188
223
 
224
+ ## Verified Smoke Tests
225
+
226
+ | Platform | App runtime | Model source | Result |
227
+ | --- | --- | --- | --- |
228
+ | Android 16 physical device | Expo Go, Expo SDK 56 tester app | Remote GLB URL | Verified |
229
+ | Android 16 physical device | Expo Go, Expo SDK 56 tester app | Local Khronos `Box.glb` as `data:model/gltf-binary;base64,...` | Verified |
230
+ | Android 16 physical device | Expo Go, Expo SDK 56 tester app | Local Khronos `Box.glb` as direct `file:` URI | Not reliable in this smoke test |
231
+ | iOS WKWebView | Not yet tested | Any model source | Not yet verified |
232
+
189
233
  ## Props
190
234
 
191
235
  `ModelViewerWebView` accepts all `react-native-webview` props except `source` and
@@ -215,8 +259,8 @@ Useful `htmlOptions`:
215
259
  | `exposure` / `shadowIntensity` | Basic visual tuning. |
216
260
  | `backgroundColor` / `posterColor` | Controls the WebView and poster background. |
217
261
  | `additionalAttributes` | Adds extra `<model-viewer>` attributes. |
218
- | `modelViewerScriptUrl` | Loads a custom `<model-viewer>` script URL. |
219
- | `modelViewerScript` | Inlines a custom module script. |
262
+ | `modelViewerScriptUrl` | Overrides the bundled runtime with a custom `<model-viewer>` script URL. |
263
+ | `modelViewerScript` | Overrides the bundled runtime with a custom inline module script. |
220
264
 
221
265
  ## Example
222
266
 
@@ -0,0 +1,24 @@
1
+ # Third-Party Notices
2
+
3
+ This package includes a vendored copy of `@google/model-viewer` so the default
4
+ viewer runtime can load offline inside React Native WebView.
5
+
6
+ ## @google/model-viewer
7
+
8
+ - Version: `4.2.0`
9
+ - Source: <https://www.npmjs.com/package/@google/model-viewer/v/4.2.0>
10
+ - Repository: <https://github.com/google/model-viewer>
11
+ - License: Apache-2.0
12
+
13
+ The Apache-2.0 license text is included at:
14
+
15
+ ```text
16
+ vendor/model-viewer/LICENSE
17
+ ```
18
+
19
+ The generated runtime and source verification details are included at:
20
+
21
+ ```text
22
+ vendor/model-viewer/runtime.js
23
+ vendor/model-viewer/SOURCE.md
24
+ ```
@@ -96,7 +96,9 @@ await model.downloadAsync();
96
96
 
97
97
  ## Offline Apps
98
98
 
99
- By default, `<model-viewer>` loads from Google's CDN. For offline apps, use:
99
+ By default, the package inlines its bundled `@google/model-viewer` runtime, so
100
+ local model assets can render without a CDN request. If the user wants to provide
101
+ a custom runtime, use:
100
102
 
101
103
  ```tsx
102
104
  <ModelViewerWebView
@@ -64,10 +64,14 @@ type ModelViewerHtmlOptions = {
64
64
  ```
65
65
 
66
66
  Consumers pass `htmlOptions` without `modelUri`; the component injects it.
67
+ The package inlines bundled `@google/model-viewer` 4.2.0 by default.
68
+ `modelViewerScriptUrl` and `modelViewerScript` override that bundled runtime.
67
69
 
68
70
  ## Utility Functions
69
71
 
70
72
  ```ts
73
+ BUNDLED_MODEL_VIEWER_VERSION
74
+ MODEL_VIEWER_CDN_SCRIPT_URL
71
75
  buildModelViewerHtml(options)
72
76
  parseModelViewerMessage(data)
73
77
  isModelViewerErrorStatus(status)
package/dist/index.d.ts CHANGED
@@ -3,9 +3,11 @@ export {
3
3
  type ModelViewerWebViewProps,
4
4
  } from "./ModelViewerWebView";
5
5
  export {
6
+ BUNDLED_MODEL_VIEWER_VERSION,
6
7
  buildModelViewerHtml,
7
8
  DEFAULT_MODEL_VIEWER_SCRIPT_URL,
8
9
  isModelViewerErrorStatus,
10
+ MODEL_VIEWER_CDN_SCRIPT_URL,
9
11
  MODEL_VIEWER_DOM_READY_EVENT,
10
12
  MODEL_VIEWER_LOADED_EVENT,
11
13
  MODEL_VIEWER_MODEL_ERROR_EVENT,
@@ -1,4 +1,6 @@
1
+ export declare const MODEL_VIEWER_CDN_SCRIPT_URL = "https://ajax.googleapis.com/ajax/libs/model-viewer/4.2.0/model-viewer.min.js";
1
2
  export declare const DEFAULT_MODEL_VIEWER_SCRIPT_URL = "https://ajax.googleapis.com/ajax/libs/model-viewer/4.2.0/model-viewer.min.js";
3
+ export { BUNDLED_MODEL_VIEWER_VERSION } from "../vendor/model-viewer/runtime";
2
4
  export declare const MODEL_VIEWER_DOM_READY_EVENT = "dom-ready";
3
5
  export declare const MODEL_VIEWER_LOADED_EVENT = "model-loaded";
4
6
  export declare const MODEL_VIEWER_MODEL_ERROR_EVENT = "model-error";
@@ -1,5 +1,11 @@
1
- const DEFAULT_MODEL_VIEWER_SCRIPT_URL =
1
+ const {
2
+ BUNDLED_MODEL_VIEWER_SCRIPT,
3
+ BUNDLED_MODEL_VIEWER_VERSION,
4
+ } = require("../vendor/model-viewer/runtime");
5
+
6
+ const MODEL_VIEWER_CDN_SCRIPT_URL =
2
7
  "https://ajax.googleapis.com/ajax/libs/model-viewer/4.2.0/model-viewer.min.js";
8
+ const DEFAULT_MODEL_VIEWER_SCRIPT_URL = MODEL_VIEWER_CDN_SCRIPT_URL;
3
9
 
4
10
  const MODEL_VIEWER_DOM_READY_EVENT = "dom-ready";
5
11
  const MODEL_VIEWER_LOADED_EVENT = "model-loaded";
@@ -133,8 +139,11 @@ function getModelViewerScriptTag(options) {
133
139
  return `<script type="module">${escapeScriptContent(options.modelViewerScript)}</script>`;
134
140
  }
135
141
 
136
- const scriptUrl = options.modelViewerScriptUrl ?? DEFAULT_MODEL_VIEWER_SCRIPT_URL;
137
- return `<script type="module" src="${escapeHtmlAttribute(scriptUrl)}"></script>`;
142
+ if (options.modelViewerScriptUrl) {
143
+ return `<script type="module" src="${escapeHtmlAttribute(options.modelViewerScriptUrl)}"></script>`;
144
+ }
145
+
146
+ return `<script type="module">/* @google/model-viewer ${BUNDLED_MODEL_VIEWER_VERSION} bundled runtime */\n${escapeScriptContent(BUNDLED_MODEL_VIEWER_SCRIPT)}</script>`;
138
147
  }
139
148
 
140
149
  function htmlAttribute(name, value) {
@@ -178,6 +187,8 @@ function sanitizeCssColor(value) {
178
187
  }
179
188
 
180
189
  exports.DEFAULT_MODEL_VIEWER_SCRIPT_URL = DEFAULT_MODEL_VIEWER_SCRIPT_URL;
190
+ exports.MODEL_VIEWER_CDN_SCRIPT_URL = MODEL_VIEWER_CDN_SCRIPT_URL;
191
+ exports.BUNDLED_MODEL_VIEWER_VERSION = BUNDLED_MODEL_VIEWER_VERSION;
181
192
  exports.MODEL_VIEWER_DOM_READY_EVENT = MODEL_VIEWER_DOM_READY_EVENT;
182
193
  exports.MODEL_VIEWER_LOADED_EVENT = MODEL_VIEWER_LOADED_EVENT;
183
194
  exports.MODEL_VIEWER_MODEL_ERROR_EVENT = MODEL_VIEWER_MODEL_ERROR_EVENT;
@@ -6,7 +6,7 @@ This package is a WebView bridge. Compatibility depends on:
6
6
  - React Native
7
7
  - `react-native-webview`
8
8
  - the Android System WebView or iOS WKWebView
9
- - Google's `<model-viewer>` web component
9
+ - the bundled `@google/model-viewer` runtime
10
10
  - how the consuming app bundles `.glb` and `.gltf` files
11
11
 
12
12
  ## Supported Peer Ranges
@@ -30,6 +30,7 @@ Current local integration target:
30
30
  - React 19.1
31
31
  - React Native 0.81
32
32
  - `react-native-webview` 13.15
33
+ - `@google/model-viewer` 4.2.0
33
34
  - TypeScript 5.9
34
35
 
35
36
  Automated checks validate:
@@ -39,6 +40,22 @@ Automated checks validate:
39
40
  - package tarball contents with `npm pack --dry-run`
40
41
  - integration with this repository's Expo app through TypeScript and Expo lint
41
42
 
43
+ ## Device Smoke Tests
44
+
45
+ These results are manual smoke tests, not a blanket support matrix.
46
+
47
+ | Date | Platform | App runtime | Model source | Result |
48
+ | --- | --- | --- | --- | --- |
49
+ | 2026-06-30 | Android 16 physical device | Expo Go, Expo SDK 56 tester app | Remote GLB URL | Verified |
50
+ | 2026-06-30 | Android 16 physical device | Expo Go, Expo SDK 56 tester app | Local Khronos `Box.glb` as `data:model/gltf-binary;base64,...` | Verified |
51
+ | 2026-06-30 | Android 16 physical device | Expo Go, Expo SDK 56 tester app | Local Khronos `Box.glb` as direct `file:` URI | Not reliable in this smoke test |
52
+ | Not yet tested | iOS WKWebView | Not yet tested | Any model source | Not yet verified |
53
+
54
+ The local Android test used `expo-asset` to resolve a bundled `.glb`, then
55
+ `expo-file-system` to read the local file as base64 before passing a
56
+ `data:model/gltf-binary;base64,...` URI to `ModelViewerWebView`. This avoids the
57
+ Android WebView file-access path that failed in the direct `file:` smoke test.
58
+
42
59
  ## Not Guaranteed
43
60
 
44
61
  The package does not claim blanket support for all React Native versions.
@@ -49,6 +66,9 @@ Explicit limitations:
49
66
  - WebView bugs in specific Android System WebView or iOS versions may affect
50
67
  rendering.
51
68
  - Local `.glb` bundling depends on the consuming app's Metro config.
69
+ - On Android Expo Go, direct WebView loading from a local `file:` GLB URI was
70
+ not reliable in the 2026-06-30 smoke test. Prefer a GLB data URI for local
71
+ Expo assets unless your target app/device matrix proves direct file loading.
52
72
  - Device rendering is not fully proven by unit tests.
53
73
  - AR behavior is not a supported package guarantee.
54
74
  - Long lists of many WebViews may have memory/performance problems.
@@ -108,5 +128,5 @@ For local development, configure the consuming app's Metro setup to:
108
128
  found.
109
129
  - Keep dev dependencies pinned to one known-good React Native stack.
110
130
  - Use CI and device smoke tests before raising support claims.
111
- - If `<model-viewer>` changes behavior, update README examples and tests before
112
- changing the default CDN URL.
131
+ - If `<model-viewer>` changes behavior, update README examples, vendor metadata,
132
+ third-party notices, and tests before changing the bundled runtime version.
@@ -77,15 +77,23 @@ to avoid injecting arbitrary CSS into generated HTML.
77
77
 
78
78
  ## Script Loading
79
79
 
80
- By default, the HTML loads:
80
+ By default, the HTML inlines the vendored runtime:
81
81
 
82
82
  ```text
83
- https://ajax.googleapis.com/ajax/libs/model-viewer/4.2.0/model-viewer.min.js
83
+ @google/model-viewer 4.2.0
84
84
  ```
85
85
 
86
- Apps that need offline behavior can provide either:
86
+ This avoids a CDN request at runtime, so a bundled/local model asset can render
87
+ without network access. It also makes each package version deterministic: the
88
+ same npm package version always embeds the same `<model-viewer>` runtime.
87
89
 
88
- - `modelViewerScriptUrl`, such as a `file:` URL to a bundled script
90
+ The vendored runtime is generated into `vendor/model-viewer/runtime.js` from
91
+ `@google/model-viewer`'s `dist/model-viewer.min.js`. Source hashes and license
92
+ metadata are recorded in `vendor/model-viewer/SOURCE.md`.
93
+
94
+ Apps that want to use a different runtime can provide either:
95
+
96
+ - `modelViewerScriptUrl`, such as a CDN URL or `file:` URL to another script
89
97
  - `modelViewerScript`, an inline module script string
90
98
 
91
99
  Inline script content escapes closing `</script>` sequences to avoid breaking
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-model-viewer-webview",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "A WebView-backed GLB and glTF model viewer for React Native and Expo.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,18 +31,22 @@
31
31
  "dist",
32
32
  "example",
33
33
  "src",
34
+ "vendor",
35
+ "scripts",
34
36
  "CHANGELOG.md",
35
37
  "CONTRIBUTING.md",
36
38
  "LICENSE",
37
39
  "llms.txt",
38
40
  "README.md",
39
41
  "SECURITY.md",
42
+ "THIRD_PARTY_NOTICES.md",
40
43
  "docs",
41
44
  "tsconfig.json"
42
45
  ],
43
46
  "sideEffects": false,
44
47
  "scripts": {
45
48
  "check": "npm test && npm run typecheck && npm run pack:dry-run",
49
+ "generate:model-viewer-runtime": "node scripts/generate-model-viewer-runtime.js",
46
50
  "test": "node --test test/*.test.js",
47
51
  "pack:dry-run": "npm pack --dry-run",
48
52
  "typecheck": "tsc --noEmit"
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+
3
+ const childProcess = require("node:child_process");
4
+ const crypto = require("node:crypto");
5
+ const fs = require("node:fs");
6
+ const os = require("node:os");
7
+ const path = require("node:path");
8
+
9
+ const MODEL_VIEWER_PACKAGE = "@google/model-viewer";
10
+ const MODEL_VIEWER_VERSION = "4.2.0";
11
+ const packageRoot = path.resolve(__dirname, "..");
12
+ const vendorRoot = path.join(packageRoot, "vendor", "model-viewer");
13
+
14
+ function main() {
15
+ const packageDir =
16
+ process.env.MODEL_VIEWER_PACKAGE_DIR || downloadModelViewerPackage();
17
+ const runtimeSourcePath = path.join(packageDir, "dist", "model-viewer.min.js");
18
+ const licensePath = path.join(packageDir, "LICENSE");
19
+
20
+ const runtimeSource = fs.readFileSync(runtimeSourcePath, "utf8");
21
+ const license = fs.readFileSync(licensePath, "utf8");
22
+ const runtimeHash = sha256(runtimeSource);
23
+ const packageHash = findPackageHash(packageDir);
24
+
25
+ fs.mkdirSync(vendorRoot, { recursive: true });
26
+ fs.writeFileSync(
27
+ path.join(vendorRoot, "runtime.js"),
28
+ [
29
+ `"use strict";`,
30
+ "",
31
+ `const BUNDLED_MODEL_VIEWER_VERSION = ${JSON.stringify(MODEL_VIEWER_VERSION)};`,
32
+ `const BUNDLED_MODEL_VIEWER_SCRIPT = ${JSON.stringify(runtimeSource)};`,
33
+ "",
34
+ "exports.BUNDLED_MODEL_VIEWER_VERSION = BUNDLED_MODEL_VIEWER_VERSION;",
35
+ "exports.BUNDLED_MODEL_VIEWER_SCRIPT = BUNDLED_MODEL_VIEWER_SCRIPT;",
36
+ "",
37
+ ].join("\n"),
38
+ );
39
+ fs.writeFileSync(
40
+ path.join(vendorRoot, "runtime.d.ts"),
41
+ [
42
+ `export declare const BUNDLED_MODEL_VIEWER_VERSION = ${JSON.stringify(MODEL_VIEWER_VERSION)};`,
43
+ "export declare const BUNDLED_MODEL_VIEWER_SCRIPT: string;",
44
+ "",
45
+ ].join("\n"),
46
+ );
47
+ fs.writeFileSync(path.join(vendorRoot, "LICENSE"), license);
48
+ fs.writeFileSync(
49
+ path.join(vendorRoot, "VERSION"),
50
+ `${MODEL_VIEWER_PACKAGE} ${MODEL_VIEWER_VERSION}\n`,
51
+ );
52
+ fs.writeFileSync(
53
+ path.join(vendorRoot, "SOURCE.md"),
54
+ buildSourceNotice({ packageHash, runtimeHash, runtimeSource }),
55
+ );
56
+ }
57
+
58
+ function downloadModelViewerPackage() {
59
+ const tempDir = fs.mkdtempSync(
60
+ path.join(os.tmpdir(), "react-native-model-viewer-webview-"),
61
+ );
62
+ const packResult = childProcess.spawnSync(
63
+ "npm",
64
+ ["pack", `${MODEL_VIEWER_PACKAGE}@${MODEL_VIEWER_VERSION}`, "--pack-destination", tempDir],
65
+ { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
66
+ );
67
+
68
+ if (packResult.status !== 0) {
69
+ throw new Error(packResult.stderr || packResult.stdout || "npm pack failed");
70
+ }
71
+
72
+ const tarballPath = fs
73
+ .readdirSync(tempDir)
74
+ .filter((fileName) => fileName.endsWith(".tgz"))
75
+ .map((fileName) => path.join(tempDir, fileName))[0];
76
+
77
+ if (!tarballPath) {
78
+ throw new Error("npm pack did not produce a .tgz file");
79
+ }
80
+
81
+ const tarResult = childProcess.spawnSync(
82
+ "tar",
83
+ ["-xzf", tarballPath, "-C", tempDir],
84
+ { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
85
+ );
86
+
87
+ if (tarResult.status !== 0) {
88
+ throw new Error(tarResult.stderr || tarResult.stdout || "tar extraction failed");
89
+ }
90
+
91
+ return path.join(tempDir, "package");
92
+ }
93
+
94
+ function findPackageHash(packageDir) {
95
+ const parentDir = path.dirname(packageDir);
96
+ const tarballPath = fs
97
+ .readdirSync(parentDir)
98
+ .filter((fileName) => fileName.endsWith(".tgz"))
99
+ .map((fileName) => path.join(parentDir, fileName))[0];
100
+
101
+ if (!tarballPath) {
102
+ return undefined;
103
+ }
104
+
105
+ return sha256(fs.readFileSync(tarballPath));
106
+ }
107
+
108
+ function sha256(value) {
109
+ return crypto.createHash("sha256").update(value).digest("hex");
110
+ }
111
+
112
+ function buildSourceNotice({ packageHash, runtimeHash, runtimeSource }) {
113
+ const packageHashLine = packageHash
114
+ ? `- Package tarball SHA-256: \`${packageHash}\``
115
+ : "- Package tarball SHA-256: not recorded; regenerate from an npm tarball to update this value.";
116
+
117
+ return [
118
+ "# Vendored model-viewer Runtime",
119
+ "",
120
+ `This directory vendors ${MODEL_VIEWER_PACKAGE}@${MODEL_VIEWER_VERSION} so the default`,
121
+ "`ModelViewerWebView` runtime can load inside a React Native WebView without",
122
+ "fetching Google's CDN script at render time.",
123
+ "",
124
+ "## Source",
125
+ "",
126
+ `- Package: \`${MODEL_VIEWER_PACKAGE}@${MODEL_VIEWER_VERSION}\``,
127
+ `- npm: <https://www.npmjs.com/package/${MODEL_VIEWER_PACKAGE}/v/${MODEL_VIEWER_VERSION}>`,
128
+ "- Repository: <https://github.com/google/model-viewer>",
129
+ "- Source file: `dist/model-viewer.min.js`",
130
+ packageHashLine,
131
+ `- Source file SHA-256: \`${runtimeHash}\``,
132
+ `- Source file bytes: \`${Buffer.byteLength(runtimeSource, "utf8")}\``,
133
+ "",
134
+ "## Generated Files",
135
+ "",
136
+ "- `runtime.js` embeds `dist/model-viewer.min.js` as a CommonJS string export.",
137
+ "- `runtime.d.ts` exposes the generated runtime constants to TypeScript.",
138
+ "- `LICENSE` contains the upstream Apache-2.0 license text.",
139
+ "- `VERSION` records the exact upstream package version.",
140
+ "",
141
+ "Regenerate these files with:",
142
+ "",
143
+ "```bash",
144
+ "npm run generate:model-viewer-runtime",
145
+ "```",
146
+ "",
147
+ ].join("\n");
148
+ }
149
+
150
+ main();
package/src/index.ts CHANGED
@@ -3,9 +3,11 @@ export {
3
3
  type ModelViewerWebViewProps,
4
4
  } from "./ModelViewerWebView";
5
5
  export {
6
+ BUNDLED_MODEL_VIEWER_VERSION,
6
7
  buildModelViewerHtml,
7
8
  DEFAULT_MODEL_VIEWER_SCRIPT_URL,
8
9
  isModelViewerErrorStatus,
10
+ MODEL_VIEWER_CDN_SCRIPT_URL,
9
11
  MODEL_VIEWER_DOM_READY_EVENT,
10
12
  MODEL_VIEWER_LOADED_EVENT,
11
13
  MODEL_VIEWER_MODEL_ERROR_EVENT,
@@ -1,5 +1,12 @@
1
- export const DEFAULT_MODEL_VIEWER_SCRIPT_URL =
1
+ import {
2
+ BUNDLED_MODEL_VIEWER_SCRIPT,
3
+ BUNDLED_MODEL_VIEWER_VERSION,
4
+ } from "../vendor/model-viewer/runtime";
5
+
6
+ export const MODEL_VIEWER_CDN_SCRIPT_URL =
2
7
  "https://ajax.googleapis.com/ajax/libs/model-viewer/4.2.0/model-viewer.min.js";
8
+ export const DEFAULT_MODEL_VIEWER_SCRIPT_URL = MODEL_VIEWER_CDN_SCRIPT_URL;
9
+ export { BUNDLED_MODEL_VIEWER_VERSION };
3
10
 
4
11
  export const MODEL_VIEWER_DOM_READY_EVENT = "dom-ready";
5
12
  export const MODEL_VIEWER_LOADED_EVENT = "model-loaded";
@@ -159,8 +166,11 @@ function getModelViewerScriptTag(options: ModelViewerHtmlOptions) {
159
166
  return `<script type="module">${escapeScriptContent(options.modelViewerScript)}</script>`;
160
167
  }
161
168
 
162
- const scriptUrl = options.modelViewerScriptUrl ?? DEFAULT_MODEL_VIEWER_SCRIPT_URL;
163
- return `<script type="module" src="${escapeHtmlAttribute(scriptUrl)}"></script>`;
169
+ if (options.modelViewerScriptUrl) {
170
+ return `<script type="module" src="${escapeHtmlAttribute(options.modelViewerScriptUrl)}"></script>`;
171
+ }
172
+
173
+ return `<script type="module">/* @google/model-viewer ${BUNDLED_MODEL_VIEWER_VERSION} bundled runtime */\n${escapeScriptContent(BUNDLED_MODEL_VIEWER_SCRIPT)}</script>`;
164
174
  }
165
175
 
166
176
  function htmlAttribute(