react-native-webview-stream-chunks 0.1.0 → 0.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 CHANGED
@@ -22,10 +22,10 @@ pnpm add react-native-webview-stream-chunks
22
22
  ### 1) Generate the preload script (receiver side)
23
23
 
24
24
  ```ts
25
- import { getWebViewStreamChunksPreloadScript } from 'react-native-webview-stream-chunks';
25
+ import { createWebViewStreamChunksPreloadScript } from 'react-native-webview-stream-chunks';
26
26
 
27
- const preloadScript = getWebViewStreamChunksPreloadScript({
28
- receiverReadyCallbackPath: 'myApp.onReady', // Optional
27
+ const preloadScript = createWebViewStreamChunksPreloadScript({
28
+ receiverReadyCallbackPath: 'window.onReady', // Optional
29
29
  });
30
30
  ```
31
31
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { WebViewStreamSender } from './sender';
2
2
  export type { WebViewStreamTransport } from './sender';
3
- export { createWebViewStreamChunksPreloadScript, getWebViewStreamChunksPreloadScript } from './streamChunksPreloadScript';
3
+ export { createWebViewStreamChunksPreloadScript } from './streamChunksPreloadScript';
4
4
  export type { PayloadScriptInjectionOptions, WebViewStreamPreloadScriptOptions } from './streamChunksPreloadScript';
5
5
  export { WebViewStreamEventTypes } from './streamChunksPreloadScript';
6
6
  export type { WebViewStreamEventType, WebViewStreamReceiverEvent } from './streamChunksPreloadScript';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,YAAY,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAGvD,OAAO,EAAE,sCAAsC,EAAE,mCAAmC,EAAE,MAAM,6BAA6B,CAAC;AAC1H,YAAY,EAAE,6BAA6B,EAAE,iCAAiC,EAAE,MAAM,6BAA6B,CAAC;AAGpH,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,YAAY,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,YAAY,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAGvD,OAAO,EAAE,sCAAsC,EAAE,MAAM,6BAA6B,CAAC;AACrF,YAAY,EAAE,6BAA6B,EAAE,iCAAiC,EAAE,MAAM,6BAA6B,CAAC;AAGpH,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,YAAY,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // High-level API (preferred)
2
2
  export { WebViewStreamSender } from './sender';
3
3
  // Preload script generators
4
- export { createWebViewStreamChunksPreloadScript, getWebViewStreamChunksPreloadScript } from './streamChunksPreloadScript';
4
+ export { createWebViewStreamChunksPreloadScript } from './streamChunksPreloadScript';
5
5
  // Low-level event protocol (advanced usage)
6
6
  export { WebViewStreamEventTypes } from './streamChunksPreloadScript';
@@ -25,5 +25,4 @@ export interface PayloadScriptInjectionOptions {
25
25
  anchorSelector?: string;
26
26
  }
27
27
  export declare const createWebViewStreamChunksPreloadScript: (options?: WebViewStreamPreloadScriptOptions) => string;
28
- export declare const getWebViewStreamChunksPreloadScript: (options?: WebViewStreamPreloadScriptOptions) => string;
29
28
  //# sourceMappingURL=streamChunksPreloadScript.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"streamChunksPreloadScript.d.ts","sourceRoot":"","sources":["../src/streamChunksPreloadScript.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;CAM1B,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAE5G,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,WAAW,iCAAiC;IAChD,yGAAyG;IACzG,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,6BAA6B;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAoMD,eAAO,MAAM,sCAAsC,GAAI,UAAS,iCAAsC,WAGrG,CAAC;AAEF,eAAO,MAAM,mCAAmC,GAAI,UAAU,iCAAiC,WAE9F,CAAC"}
1
+ {"version":3,"file":"streamChunksPreloadScript.d.ts","sourceRoot":"","sources":["../src/streamChunksPreloadScript.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;CAM1B,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAE5G,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,WAAW,iCAAiC;IAChD,yGAAyG;IACzG,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,6BAA6B;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA2BD,eAAO,MAAM,sCAAsC,GAAI,UAAS,iCAAsC,WAGrG,CAAC"}
@@ -9,173 +9,18 @@ export const WebViewStreamEventTypes = {
9
9
  FINALIZE_PAYLOAD: 'FINALIZE_PAYLOAD',
10
10
  REEXECUTE_SCRIPTS: 'REEXECUTE_SCRIPTS',
11
11
  };
12
- // ---------------------------------------------------------------------------
13
- // Receiver IIFE — serialised via `.toString()` and injected into the WebView.
14
- // ---------------------------------------------------------------------------
15
- function webViewStreamReceiverIIFE(options, eventTypes) {
16
- let payloadChunks = [];
17
- let contentMutationObserved = false;
18
- const injectedPayloadScriptClasses = new Set();
19
- function resetState() {
20
- payloadChunks = [];
21
- contentMutationObserved = false;
22
- }
23
- const targetWindow = window;
24
- // -- receiver-ready callback resolver -------------------------------------
25
- function runReceiverReadyCallback() {
26
- if (!options.receiverReadyCallbackPath.trim())
27
- return;
28
- const segments = options.receiverReadyCallbackPath.split('.').filter(Boolean);
29
- let current = targetWindow;
30
- for (const segment of segments) {
31
- if (current === null || current === undefined || typeof current !== 'object') {
32
- current = undefined;
33
- break;
34
- }
35
- current = current[segment];
36
- }
37
- if (typeof current === 'function') {
38
- current();
39
- }
40
- }
41
- // -- event dispatcher -----------------------------------------------------
42
- targetWindow.onStreamChunksToWebView = function (event) {
43
- switch (event.type) {
44
- case eventTypes.SET_CONTENT: {
45
- resetState();
46
- replaceBodyContent(event.data ?? '');
47
- break;
48
- }
49
- case eventTypes.APPEND_CHUNK: {
50
- if (event.data)
51
- payloadChunks.push(event.data);
52
- break;
53
- }
54
- case eventTypes.FINALIZE_PAYLOAD: {
55
- const waitAndInject = () => {
56
- if (contentMutationObserved) {
57
- injectPayloadScript(event.data);
58
- }
59
- else {
60
- setTimeout(waitAndInject, 100);
61
- }
62
- };
63
- waitAndInject();
64
- break;
65
- }
66
- case eventTypes.REEXECUTE_SCRIPTS: {
67
- const waitAndExec = () => {
68
- if (contentMutationObserved) {
69
- reexecuteScripts(event.data);
70
- }
71
- else {
72
- setTimeout(waitAndExec, 100);
73
- }
74
- };
75
- waitAndExec();
76
- break;
77
- }
78
- case eventTypes.CHECK_RECEIVER_READY: {
79
- runReceiverReadyCallback();
80
- break;
81
- }
82
- }
83
- };
84
- // -- DOM helpers ----------------------------------------------------------
85
- function replaceBodyContent(newInnerHTML) {
86
- const observer = new MutationObserver((mutationsList, currentObserver) => {
87
- for (const mutation of mutationsList) {
88
- if (mutation.type === 'childList') {
89
- currentObserver.disconnect();
90
- contentMutationObserved = true;
91
- return;
92
- }
93
- }
94
- });
95
- observer.observe(document.body, { childList: true });
96
- document.body.innerHTML = newInnerHTML;
97
- }
98
- const defaultInjectionOptions = {
99
- scriptType: 'application/json',
100
- scriptClassName: 'webview-stream-payload',
101
- scriptTagName: 'default-payload',
102
- anchorSelector: '#styleArea',
103
- };
104
- function resolveInjectionOptions(rawData) {
105
- if (!rawData) {
106
- return defaultInjectionOptions;
107
- }
108
- try {
109
- const parsed = JSON.parse(rawData);
110
- return {
111
- scriptType: parsed.scriptType ?? defaultInjectionOptions.scriptType,
112
- scriptClassName: parsed.scriptClassName ?? defaultInjectionOptions.scriptClassName,
113
- scriptTagName: parsed.scriptTagName ?? defaultInjectionOptions.scriptTagName,
114
- anchorSelector: parsed.anchorSelector ?? defaultInjectionOptions.anchorSelector,
115
- };
116
- }
117
- catch {
118
- return defaultInjectionOptions;
119
- }
120
- }
121
- function injectPayloadScript(rawData) {
122
- try {
123
- const injectionOptions = resolveInjectionOptions(rawData);
124
- const fullPayload = payloadChunks.join('');
125
- const scriptElement = document.createElement('script');
126
- scriptElement.type = injectionOptions.scriptType;
127
- scriptElement.classList.add(injectionOptions.scriptClassName, injectionOptions.scriptTagName);
128
- scriptElement.textContent = fullPayload;
129
- const anchor = document.querySelector(injectionOptions.anchorSelector);
130
- anchor?.insertAdjacentElement('afterend', scriptElement);
131
- injectedPayloadScriptClasses.add(injectionOptions.scriptClassName);
132
- }
133
- catch (error) {
134
- console.error('[webview-stream] injectPayloadScript error', error);
135
- }
136
- payloadChunks = [];
137
- }
138
- function reexecuteScripts(selector) {
139
- try {
140
- const query = selector?.trim() || 'script';
141
- const elements = Array.from(document.querySelectorAll(query));
142
- for (const element of elements) {
143
- if (!(element instanceof HTMLScriptElement))
144
- continue;
145
- let isInjectedPayloadScript = false;
146
- for (const payloadClassName of injectedPayloadScriptClasses) {
147
- if (element.classList.contains(payloadClassName)) {
148
- isInjectedPayloadScript = true;
149
- break;
150
- }
151
- }
152
- if (isInjectedPayloadScript)
153
- continue;
154
- const replacement = document.createElement('script');
155
- for (const { name, value } of Array.from(element.attributes)) {
156
- replacement.setAttribute(name, value);
157
- }
158
- if (element.src) {
159
- replacement.src = element.src;
160
- }
161
- else {
162
- replacement.textContent = element.textContent;
163
- }
164
- if (element.parentNode !== null) {
165
- try {
166
- element.parentNode.replaceChild(replacement, element);
167
- }
168
- catch (error) {
169
- console.error('[webview-stream] Failed to re-execute script tag', replacement, element, error);
170
- }
171
- }
172
- }
173
- }
174
- catch (error) {
175
- console.error('[webview-stream] reexecuteScripts error', error);
176
- }
177
- }
178
- }
12
+ /**
13
+ IMPORTANT: Hermes compatibility and code injection rationale
14
+
15
+ We do NOT use function.toString() at runtime because Hermes (the JS engine used by React Native)
16
+ serializes functions as [bytecode], which breaks code injection for WebView. For example, you may see:
17
+ ```
18
+ LOG webviewSideReceiver (function webViewStreamReceiverIIFE(a0, a1) { [bytecode] })({"receiverReadyCallbackPath":"service.wikiHookService.setWebviewReceiverReady"}, {"CHECK_RECEIVER_READY":"CHECK_RECEIVER_READY","SET_CONTENT":"SET_CONTENT","APPEND_CHUNK":"APPEND_CHUNK","FINALIZE_PAYLOAD":"FINALIZE_PAYLOAD","REEXECUTE_SCRIPTS":"REEXECUTE_SCRIPTS"});
19
+ ```
20
+ Instead, we compile the receiver function to JS at build time and inject it as a string into the final bundle.
21
+ See scripts/injectReceiver.mjs for details. The placeholder below is replaced in dist/streamChunksPreloadScript.js after tsc build.
22
+ */
23
+ const webViewStreamReceiverIIFECode = '__WEBVIEW_RECEIVER_CODE__';
179
24
  // ---------------------------------------------------------------------------
180
25
  // Preload script generators
181
26
  // ---------------------------------------------------------------------------
@@ -184,8 +29,5 @@ const defaultOptions = {
184
29
  };
185
30
  export const createWebViewStreamChunksPreloadScript = (options = {}) => {
186
31
  const resolved = { ...defaultOptions, ...options };
187
- return `(${webViewStreamReceiverIIFE.toString()})(${JSON.stringify(resolved)}, ${JSON.stringify(WebViewStreamEventTypes)});`;
188
- };
189
- export const getWebViewStreamChunksPreloadScript = (options) => {
190
- return createWebViewStreamChunksPreloadScript(options);
32
+ return `(${webViewStreamReceiverIIFECode})(${JSON.stringify(resolved)}, ${JSON.stringify(WebViewStreamEventTypes)});`;
191
33
  };
@@ -0,0 +1,13 @@
1
+ interface WebViewStreamResolvedOptions {
2
+ receiverReadyCallbackPath: string;
3
+ }
4
+ declare const _EventTypes: {
5
+ readonly CHECK_RECEIVER_READY: "CHECK_RECEIVER_READY";
6
+ readonly SET_CONTENT: "SET_CONTENT";
7
+ readonly APPEND_CHUNK: "APPEND_CHUNK";
8
+ readonly FINALIZE_PAYLOAD: "FINALIZE_PAYLOAD";
9
+ readonly REEXECUTE_SCRIPTS: "REEXECUTE_SCRIPTS";
10
+ };
11
+ export declare function webViewStreamReceiverIIFE(options: WebViewStreamResolvedOptions, eventTypes: typeof _EventTypes): void;
12
+ export {};
13
+ //# sourceMappingURL=webviewReceiver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webviewReceiver.d.ts","sourceRoot":"","sources":["../src/webviewReceiver.ts"],"names":[],"mappings":"AAMA,UAAU,4BAA4B;IACpC,yBAAyB,EAAE,MAAM,CAAC;CACnC;AAUD,QAAA,MAAM,WAAW;;;;;;CAMP,CAAC;AAEX,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,4BAA4B,EAAE,UAAU,EAAE,OAAO,WAAW,QA0K9G"}
@@ -0,0 +1,175 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Receiver IIFE — serialised via `.toString()` and injected into the WebView.
3
+ // ---------------------------------------------------------------------------
4
+ // Keep in sync with streamChunksPreloadScript.ts
5
+ const _EventTypes = {
6
+ CHECK_RECEIVER_READY: 'CHECK_RECEIVER_READY',
7
+ SET_CONTENT: 'SET_CONTENT',
8
+ APPEND_CHUNK: 'APPEND_CHUNK',
9
+ FINALIZE_PAYLOAD: 'FINALIZE_PAYLOAD',
10
+ REEXECUTE_SCRIPTS: 'REEXECUTE_SCRIPTS',
11
+ };
12
+ export function webViewStreamReceiverIIFE(options, eventTypes) {
13
+ let payloadChunks = [];
14
+ let contentMutationObserved = false;
15
+ const injectedPayloadScriptClasses = new Set();
16
+ function resetState() {
17
+ payloadChunks = [];
18
+ contentMutationObserved = false;
19
+ }
20
+ const targetWindow = window;
21
+ // -- receiver-ready callback resolver -------------------------------------
22
+ function runReceiverReadyCallback() {
23
+ if (!options.receiverReadyCallbackPath.trim())
24
+ return;
25
+ const segments = options.receiverReadyCallbackPath.split('.').filter(Boolean);
26
+ let current = targetWindow;
27
+ for (const segment of segments) {
28
+ if (current === null || current === undefined || typeof current !== 'object') {
29
+ current = undefined;
30
+ break;
31
+ }
32
+ current = current[segment];
33
+ }
34
+ if (typeof current === 'function') {
35
+ current();
36
+ }
37
+ }
38
+ // -- event dispatcher -----------------------------------------------------
39
+ targetWindow.onStreamChunksToWebView = function (event) {
40
+ switch (event.type) {
41
+ case eventTypes.SET_CONTENT: {
42
+ resetState();
43
+ replaceBodyContent(event.data ?? '');
44
+ break;
45
+ }
46
+ case eventTypes.APPEND_CHUNK: {
47
+ if (event.data)
48
+ payloadChunks.push(event.data);
49
+ break;
50
+ }
51
+ case eventTypes.FINALIZE_PAYLOAD: {
52
+ const waitAndInject = () => {
53
+ if (contentMutationObserved) {
54
+ injectPayloadScript(event.data);
55
+ }
56
+ else {
57
+ setTimeout(waitAndInject, 100);
58
+ }
59
+ };
60
+ waitAndInject();
61
+ break;
62
+ }
63
+ case eventTypes.REEXECUTE_SCRIPTS: {
64
+ const waitAndExec = () => {
65
+ if (contentMutationObserved) {
66
+ reexecuteScripts(event.data);
67
+ }
68
+ else {
69
+ setTimeout(waitAndExec, 100);
70
+ }
71
+ };
72
+ waitAndExec();
73
+ break;
74
+ }
75
+ case eventTypes.CHECK_RECEIVER_READY: {
76
+ runReceiverReadyCallback();
77
+ break;
78
+ }
79
+ }
80
+ };
81
+ // -- DOM helpers ----------------------------------------------------------
82
+ function replaceBodyContent(newInnerHTML) {
83
+ const observer = new MutationObserver((mutationsList, currentObserver) => {
84
+ for (const mutation of mutationsList) {
85
+ if (mutation.type === 'childList') {
86
+ currentObserver.disconnect();
87
+ contentMutationObserved = true;
88
+ return;
89
+ }
90
+ }
91
+ });
92
+ observer.observe(document.body, { childList: true });
93
+ document.body.innerHTML = newInnerHTML;
94
+ }
95
+ const defaultInjectionOptions = {
96
+ scriptType: 'application/json',
97
+ scriptClassName: 'webview-stream-payload',
98
+ scriptTagName: 'default-payload',
99
+ anchorSelector: '#styleArea',
100
+ };
101
+ function resolveInjectionOptions(rawData) {
102
+ if (!rawData) {
103
+ return defaultInjectionOptions;
104
+ }
105
+ try {
106
+ const parsed = JSON.parse(rawData);
107
+ return {
108
+ scriptType: parsed.scriptType ?? defaultInjectionOptions.scriptType,
109
+ scriptClassName: parsed.scriptClassName ?? defaultInjectionOptions.scriptClassName,
110
+ scriptTagName: parsed.scriptTagName ?? defaultInjectionOptions.scriptTagName,
111
+ anchorSelector: parsed.anchorSelector ?? defaultInjectionOptions.anchorSelector,
112
+ };
113
+ }
114
+ catch {
115
+ return defaultInjectionOptions;
116
+ }
117
+ }
118
+ function injectPayloadScript(rawData) {
119
+ try {
120
+ const injectionOptions = resolveInjectionOptions(rawData);
121
+ const fullPayload = payloadChunks.join('');
122
+ const scriptElement = document.createElement('script');
123
+ scriptElement.type = injectionOptions.scriptType;
124
+ scriptElement.classList.add(injectionOptions.scriptClassName, injectionOptions.scriptTagName);
125
+ scriptElement.textContent = fullPayload;
126
+ const anchor = document.querySelector(injectionOptions.anchorSelector);
127
+ anchor?.insertAdjacentElement('afterend', scriptElement);
128
+ injectedPayloadScriptClasses.add(injectionOptions.scriptClassName);
129
+ }
130
+ catch (error) {
131
+ console.error('[webview-stream] injectPayloadScript error', error);
132
+ }
133
+ payloadChunks = [];
134
+ }
135
+ function reexecuteScripts(selector) {
136
+ try {
137
+ const query = selector?.trim() || 'script';
138
+ const elements = Array.from(document.querySelectorAll(query));
139
+ for (const element of elements) {
140
+ if (!(element instanceof HTMLScriptElement))
141
+ continue;
142
+ let isInjectedPayloadScript = false;
143
+ for (const payloadClassName of injectedPayloadScriptClasses) {
144
+ if (element.classList.contains(payloadClassName)) {
145
+ isInjectedPayloadScript = true;
146
+ break;
147
+ }
148
+ }
149
+ if (isInjectedPayloadScript)
150
+ continue;
151
+ const replacement = document.createElement('script');
152
+ for (const { name, value } of Array.from(element.attributes)) {
153
+ replacement.setAttribute(name, value);
154
+ }
155
+ if (element.src) {
156
+ replacement.src = element.src;
157
+ }
158
+ else {
159
+ replacement.textContent = element.textContent;
160
+ }
161
+ if (element.parentNode !== null) {
162
+ try {
163
+ element.parentNode.replaceChild(replacement, element);
164
+ }
165
+ catch (error) {
166
+ console.error('[webview-stream] Failed to re-execute script tag', replacement, element, error);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ catch (error) {
172
+ console.error('[webview-stream] reexecuteScripts error', error);
173
+ }
174
+ }
175
+ }
@@ -0,0 +1,2 @@
1
+ export declare const webViewStreamReceiverIIFESource = "function webViewStreamReceiverIIFE(options, eventTypes) {\n let payloadChunks = [];\n let contentMutationObserved = false;\n const injectedPayloadScriptClasses = new Set();\n function resetState() {\n payloadChunks = [];\n contentMutationObserved = false;\n }\n const targetWindow = window;\n // -- receiver-ready callback resolver -------------------------------------\n function runReceiverReadyCallback() {\n if (!options.receiverReadyCallbackPath.trim())\n return;\n const segments = options.receiverReadyCallbackPath.split('.').filter(Boolean);\n let current = targetWindow;\n for (const segment of segments) {\n if (current === null || current === undefined || typeof current !== 'object') {\n current = undefined;\n break;\n }\n current = current[segment];\n }\n if (typeof current === 'function') {\n current();\n }\n }\n // -- event dispatcher -----------------------------------------------------\n targetWindow.onStreamChunksToWebView = function (event) {\n var _a;\n switch (event.type) {\n case eventTypes.SET_CONTENT: {\n resetState();\n replaceBodyContent((_a = event.data) !== null && _a !== void 0 ? _a : '');\n break;\n }\n case eventTypes.APPEND_CHUNK: {\n if (event.data)\n payloadChunks.push(event.data);\n break;\n }\n case eventTypes.FINALIZE_PAYLOAD: {\n const waitAndInject = () => {\n if (contentMutationObserved) {\n injectPayloadScript(event.data);\n }\n else {\n setTimeout(waitAndInject, 100);\n }\n };\n waitAndInject();\n break;\n }\n case eventTypes.REEXECUTE_SCRIPTS: {\n const waitAndExec = () => {\n if (contentMutationObserved) {\n reexecuteScripts(event.data);\n }\n else {\n setTimeout(waitAndExec, 100);\n }\n };\n waitAndExec();\n break;\n }\n case eventTypes.CHECK_RECEIVER_READY: {\n runReceiverReadyCallback();\n break;\n }\n }\n };\n // -- DOM helpers ----------------------------------------------------------\n function replaceBodyContent(newInnerHTML) {\n const observer = new MutationObserver((mutationsList, currentObserver) => {\n for (const mutation of mutationsList) {\n if (mutation.type === 'childList') {\n currentObserver.disconnect();\n contentMutationObserved = true;\n return;\n }\n }\n });\n observer.observe(document.body, { childList: true });\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n document.body.innerHTML = newInnerHTML;\n }\n const defaultInjectionOptions = {\n scriptType: 'application/json',\n scriptClassName: 'webview-stream-payload',\n scriptTagName: 'default-payload',\n anchorSelector: '#styleArea',\n };\n function resolveInjectionOptions(rawData) {\n var _a, _b, _c, _d;\n if (!rawData) {\n return defaultInjectionOptions;\n }\n try {\n const parsed = JSON.parse(rawData);\n return {\n scriptType: (_a = parsed.scriptType) !== null && _a !== void 0 ? _a : defaultInjectionOptions.scriptType,\n scriptClassName: (_b = parsed.scriptClassName) !== null && _b !== void 0 ? _b : defaultInjectionOptions.scriptClassName,\n scriptTagName: (_c = parsed.scriptTagName) !== null && _c !== void 0 ? _c : defaultInjectionOptions.scriptTagName,\n anchorSelector: (_d = parsed.anchorSelector) !== null && _d !== void 0 ? _d : defaultInjectionOptions.anchorSelector,\n };\n }\n catch (_e) {\n return defaultInjectionOptions;\n }\n }\n function injectPayloadScript(rawData) {\n try {\n const injectionOptions = resolveInjectionOptions(rawData);\n const fullPayload = payloadChunks.join('');\n const scriptElement = document.createElement('script');\n scriptElement.type = injectionOptions.scriptType;\n scriptElement.classList.add(injectionOptions.scriptClassName, injectionOptions.scriptTagName);\n scriptElement.textContent = fullPayload;\n const anchor = document.querySelector(injectionOptions.anchorSelector);\n anchor === null || anchor === void 0 ? void 0 : anchor.insertAdjacentElement('afterend', scriptElement);\n injectedPayloadScriptClasses.add(injectionOptions.scriptClassName);\n }\n catch (error) {\n console.error('[webview-stream] injectPayloadScript error', error);\n }\n payloadChunks = [];\n }\n function reexecuteScripts(selector) {\n try {\n const query = (selector === null || selector === void 0 ? void 0 : selector.trim()) || 'script';\n const elements = Array.from(document.querySelectorAll(query));\n for (const element of elements) {\n if (!(element instanceof HTMLScriptElement))\n continue;\n let isInjectedPayloadScript = false;\n for (const payloadClassName of injectedPayloadScriptClasses) {\n if (element.classList.contains(payloadClassName)) {\n isInjectedPayloadScript = true;\n break;\n }\n }\n if (isInjectedPayloadScript)\n continue;\n const replacement = document.createElement('script');\n for (const { name, value } of Array.from(element.attributes)) {\n replacement.setAttribute(name, value);\n }\n if (element.src) {\n replacement.src = element.src;\n }\n else {\n replacement.textContent = element.textContent;\n }\n if (element.parentNode !== null) {\n try {\n element.parentNode.replaceChild(replacement, element);\n }\n catch (error) {\n console.error('[webview-stream] Failed to re-execute script tag', replacement, element, error);\n }\n }\n }\n }\n catch (error) {\n console.error('[webview-stream] reexecuteScripts error', error);\n }\n }\n}";
2
+ //# sourceMappingURL=webviewReceiverString.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webviewReceiverString.d.ts","sourceRoot":"","sources":["../src/webviewReceiverString.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,+BAA+B,87NAsK1C,CAAC"}
@@ -0,0 +1,169 @@
1
+ // This file is auto-generated by scripts/buildReceiver.mjs
2
+ // Do not edit this file directly.
3
+ export const webViewStreamReceiverIIFESource = `function webViewStreamReceiverIIFE(options, eventTypes) {
4
+ let payloadChunks = [];
5
+ let contentMutationObserved = false;
6
+ const injectedPayloadScriptClasses = new Set();
7
+ function resetState() {
8
+ payloadChunks = [];
9
+ contentMutationObserved = false;
10
+ }
11
+ const targetWindow = window;
12
+ // -- receiver-ready callback resolver -------------------------------------
13
+ function runReceiverReadyCallback() {
14
+ if (!options.receiverReadyCallbackPath.trim())
15
+ return;
16
+ const segments = options.receiverReadyCallbackPath.split('.').filter(Boolean);
17
+ let current = targetWindow;
18
+ for (const segment of segments) {
19
+ if (current === null || current === undefined || typeof current !== 'object') {
20
+ current = undefined;
21
+ break;
22
+ }
23
+ current = current[segment];
24
+ }
25
+ if (typeof current === 'function') {
26
+ current();
27
+ }
28
+ }
29
+ // -- event dispatcher -----------------------------------------------------
30
+ targetWindow.onStreamChunksToWebView = function (event) {
31
+ var _a;
32
+ switch (event.type) {
33
+ case eventTypes.SET_CONTENT: {
34
+ resetState();
35
+ replaceBodyContent((_a = event.data) !== null && _a !== void 0 ? _a : '');
36
+ break;
37
+ }
38
+ case eventTypes.APPEND_CHUNK: {
39
+ if (event.data)
40
+ payloadChunks.push(event.data);
41
+ break;
42
+ }
43
+ case eventTypes.FINALIZE_PAYLOAD: {
44
+ const waitAndInject = () => {
45
+ if (contentMutationObserved) {
46
+ injectPayloadScript(event.data);
47
+ }
48
+ else {
49
+ setTimeout(waitAndInject, 100);
50
+ }
51
+ };
52
+ waitAndInject();
53
+ break;
54
+ }
55
+ case eventTypes.REEXECUTE_SCRIPTS: {
56
+ const waitAndExec = () => {
57
+ if (contentMutationObserved) {
58
+ reexecuteScripts(event.data);
59
+ }
60
+ else {
61
+ setTimeout(waitAndExec, 100);
62
+ }
63
+ };
64
+ waitAndExec();
65
+ break;
66
+ }
67
+ case eventTypes.CHECK_RECEIVER_READY: {
68
+ runReceiverReadyCallback();
69
+ break;
70
+ }
71
+ }
72
+ };
73
+ // -- DOM helpers ----------------------------------------------------------
74
+ function replaceBodyContent(newInnerHTML) {
75
+ const observer = new MutationObserver((mutationsList, currentObserver) => {
76
+ for (const mutation of mutationsList) {
77
+ if (mutation.type === 'childList') {
78
+ currentObserver.disconnect();
79
+ contentMutationObserved = true;
80
+ return;
81
+ }
82
+ }
83
+ });
84
+ observer.observe(document.body, { childList: true });
85
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
86
+ document.body.innerHTML = newInnerHTML;
87
+ }
88
+ const defaultInjectionOptions = {
89
+ scriptType: 'application/json',
90
+ scriptClassName: 'webview-stream-payload',
91
+ scriptTagName: 'default-payload',
92
+ anchorSelector: '#styleArea',
93
+ };
94
+ function resolveInjectionOptions(rawData) {
95
+ var _a, _b, _c, _d;
96
+ if (!rawData) {
97
+ return defaultInjectionOptions;
98
+ }
99
+ try {
100
+ const parsed = JSON.parse(rawData);
101
+ return {
102
+ scriptType: (_a = parsed.scriptType) !== null && _a !== void 0 ? _a : defaultInjectionOptions.scriptType,
103
+ scriptClassName: (_b = parsed.scriptClassName) !== null && _b !== void 0 ? _b : defaultInjectionOptions.scriptClassName,
104
+ scriptTagName: (_c = parsed.scriptTagName) !== null && _c !== void 0 ? _c : defaultInjectionOptions.scriptTagName,
105
+ anchorSelector: (_d = parsed.anchorSelector) !== null && _d !== void 0 ? _d : defaultInjectionOptions.anchorSelector,
106
+ };
107
+ }
108
+ catch (_e) {
109
+ return defaultInjectionOptions;
110
+ }
111
+ }
112
+ function injectPayloadScript(rawData) {
113
+ try {
114
+ const injectionOptions = resolveInjectionOptions(rawData);
115
+ const fullPayload = payloadChunks.join('');
116
+ const scriptElement = document.createElement('script');
117
+ scriptElement.type = injectionOptions.scriptType;
118
+ scriptElement.classList.add(injectionOptions.scriptClassName, injectionOptions.scriptTagName);
119
+ scriptElement.textContent = fullPayload;
120
+ const anchor = document.querySelector(injectionOptions.anchorSelector);
121
+ anchor === null || anchor === void 0 ? void 0 : anchor.insertAdjacentElement('afterend', scriptElement);
122
+ injectedPayloadScriptClasses.add(injectionOptions.scriptClassName);
123
+ }
124
+ catch (error) {
125
+ console.error('[webview-stream] injectPayloadScript error', error);
126
+ }
127
+ payloadChunks = [];
128
+ }
129
+ function reexecuteScripts(selector) {
130
+ try {
131
+ const query = (selector === null || selector === void 0 ? void 0 : selector.trim()) || 'script';
132
+ const elements = Array.from(document.querySelectorAll(query));
133
+ for (const element of elements) {
134
+ if (!(element instanceof HTMLScriptElement))
135
+ continue;
136
+ let isInjectedPayloadScript = false;
137
+ for (const payloadClassName of injectedPayloadScriptClasses) {
138
+ if (element.classList.contains(payloadClassName)) {
139
+ isInjectedPayloadScript = true;
140
+ break;
141
+ }
142
+ }
143
+ if (isInjectedPayloadScript)
144
+ continue;
145
+ const replacement = document.createElement('script');
146
+ for (const { name, value } of Array.from(element.attributes)) {
147
+ replacement.setAttribute(name, value);
148
+ }
149
+ if (element.src) {
150
+ replacement.src = element.src;
151
+ }
152
+ else {
153
+ replacement.textContent = element.textContent;
154
+ }
155
+ if (element.parentNode !== null) {
156
+ try {
157
+ element.parentNode.replaceChild(replacement, element);
158
+ }
159
+ catch (error) {
160
+ console.error('[webview-stream] Failed to re-execute script tag', replacement, element, error);
161
+ }
162
+ }
163
+ }
164
+ }
165
+ catch (error) {
166
+ console.error('[webview-stream] reexecuteScripts error', error);
167
+ }
168
+ }
169
+ }`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-webview-stream-chunks",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Reusable preload script and event types for streaming large HTML/chunks into WebView",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,8 @@
17
17
  "scripts": {
18
18
  "lint": "eslint ./src --ext ts",
19
19
  "lint:fix": "eslint ./src --ext ts --fix",
20
- "build": "tsc -p tsconfig.json",
20
+ "build:inject": "node scripts/injectReceiver.mjs",
21
+ "build": "tsc -p tsconfig.json && npm run build:inject",
21
22
  "check": "tsc --noEmit -p tsconfig.json",
22
23
  "prepublishOnly": "npm run build"
23
24
  },