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 +45 -0
- package/README.md +52 -8
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/agent-skills/react-native-model-viewer-webview/SKILL.md +3 -1
- package/agent-skills/react-native-model-viewer-webview/references/api.md +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/model-viewer-html.d.ts +2 -0
- package/dist/model-viewer-html.js +14 -3
- package/docs/COMPATIBILITY.md +23 -3
- package/docs/HOW_IT_WORKS.md +12 -4
- package/package.json +5 -1
- package/scripts/generate-model-viewer-runtime.js +150 -0
- package/src/index.ts +2 -0
- package/src/model-viewer-html.ts +13 -3
- package/vendor/model-viewer/LICENSE +202 -0
- package/vendor/model-viewer/SOURCE.md +28 -0
- package/vendor/model-viewer/VERSION +1 -0
- package/vendor/model-viewer/runtime.d.ts +2 -0
- package/vendor/model-viewer/runtime.js +7 -0
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
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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=
|
|
206
|
+
modelSource="https://example.com/car.glb"
|
|
172
207
|
htmlOptions={{
|
|
173
|
-
modelViewerScriptUrl:
|
|
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` |
|
|
219
|
-
| `modelViewerScript` |
|
|
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,
|
|
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
|
|
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
|
-
|
|
137
|
-
|
|
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;
|
package/docs/COMPATIBILITY.md
CHANGED
|
@@ -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
|
-
-
|
|
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
|
|
112
|
-
changing the
|
|
131
|
+
- If `<model-viewer>` changes behavior, update README examples, vendor metadata,
|
|
132
|
+
third-party notices, and tests before changing the bundled runtime version.
|
package/docs/HOW_IT_WORKS.md
CHANGED
|
@@ -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
|
|
80
|
+
By default, the HTML inlines the vendored runtime:
|
|
81
81
|
|
|
82
82
|
```text
|
|
83
|
-
|
|
83
|
+
@google/model-viewer 4.2.0
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
package/src/model-viewer-html.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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(
|