@nihinmedia/medgen-react-native 1.0.12
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 +62 -0
- package/README.md +221 -0
- package/SECURITY.md +101 -0
- package/lib/commonjs/MedGenWebView.js +201 -0
- package/lib/commonjs/bridge.js +208 -0
- package/lib/commonjs/client.js +445 -0
- package/lib/commonjs/index.js +204 -0
- package/lib/commonjs/native.js +201 -0
- package/lib/commonjs/pdfLinks.js +57 -0
- package/lib/commonjs/types.js +27 -0
- package/lib/module/MedGenWebView.js +166 -0
- package/lib/module/bridge.js +182 -0
- package/lib/module/client.js +391 -0
- package/lib/module/index.js +152 -0
- package/lib/module/native.js +171 -0
- package/lib/module/pdfLinks.js +45 -0
- package/lib/module/types.js +17 -0
- package/lib/typescript/MedGenWebView.d.ts +18 -0
- package/lib/typescript/bridge.d.ts +12 -0
- package/lib/typescript/client.d.ts +30 -0
- package/lib/typescript/index.d.ts +23 -0
- package/lib/typescript/native.d.ts +38 -0
- package/lib/typescript/pdfLinks.d.ts +14 -0
- package/lib/typescript/types.d.ts +182 -0
- package/package.json +116 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _exportNames = {
|
|
8
|
+
MedGen: true,
|
|
9
|
+
MedGenHost: true,
|
|
10
|
+
MedGenSurface: true,
|
|
11
|
+
SDK_VERSION: true
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.MedGenSurface = exports.MedGenHost = exports.MedGen = void 0;
|
|
15
|
+
|
|
16
|
+
Object.defineProperty(exports, "SDK_VERSION", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function() {
|
|
19
|
+
return _client.SDK_VERSION;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
24
|
+
|
|
25
|
+
var _reactNative = require("react-native");
|
|
26
|
+
|
|
27
|
+
var _client = require("./client");
|
|
28
|
+
|
|
29
|
+
var _MedGenWebView = require("./MedGenWebView");
|
|
30
|
+
|
|
31
|
+
var _types = require("./types");
|
|
32
|
+
|
|
33
|
+
Object.keys(_types).forEach(function(key) {
|
|
34
|
+
if (key === "default" || key === "__esModule") return;
|
|
35
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
36
|
+
if (key in exports && exports[key] === _types[key]) return;
|
|
37
|
+
Object.defineProperty(exports, key, {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
get: function() {
|
|
40
|
+
return _types[key];
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function _interopRequireWildcard(e, t) {
|
|
46
|
+
if ("function" == typeof WeakMap) var r = new WeakMap, n = new WeakMap;
|
|
47
|
+
return (_interopRequireWildcard = function(e, t) {
|
|
48
|
+
if (!t && e && e.__esModule) return e;
|
|
49
|
+
var o, i, f = {
|
|
50
|
+
__proto__: null,
|
|
51
|
+
default: e
|
|
52
|
+
};
|
|
53
|
+
if (null === e || "object" != typeof e && "function" != typeof e) return f;
|
|
54
|
+
if (o = t ? n : r) {
|
|
55
|
+
if (o.has(e)) return o.get(e);
|
|
56
|
+
o.set(e, f);
|
|
57
|
+
}
|
|
58
|
+
for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);
|
|
59
|
+
return f;
|
|
60
|
+
})(e, t);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const hostRef = {
|
|
64
|
+
current: null
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const teardowns = new Set;
|
|
68
|
+
|
|
69
|
+
const MedGen = exports.MedGen = {
|
|
70
|
+
initialize(config) {
|
|
71
|
+
if (!config.assertionProvider && !config.assertion) {
|
|
72
|
+
throw new _types.MedGenError("NOT_INITIALIZED", "MedGen.initialize() needs assertionProvider (or, to try the SDK once, assertion)");
|
|
73
|
+
}
|
|
74
|
+
if (!config.assertionProvider && config.assertion) {
|
|
75
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) {
|
|
76
|
+
throw new _types.MedGenError("NOT_INITIALIZED", "A static `assertion` cannot be used in a release build — it is " + "single-use and cannot be refreshed. Set `assertionProvider`.");
|
|
77
|
+
}
|
|
78
|
+
console.warn("[MedGen] Using a static `assertion`. It is single-use and expires " + "after 120s, so only the first present() will succeed. Set " + "`assertionProvider` before shipping.");
|
|
79
|
+
}
|
|
80
|
+
(0, _client.configure)(config);
|
|
81
|
+
},
|
|
82
|
+
async present(options) {
|
|
83
|
+
if (!hostRef.current) {
|
|
84
|
+
throw new _types.MedGenError("NOT_INITIALIZED", "Mount <MedGenHost /> near the root of your app");
|
|
85
|
+
}
|
|
86
|
+
await hostRef.current(options);
|
|
87
|
+
},
|
|
88
|
+
async signOut() {
|
|
89
|
+
teardowns.forEach(fn => {
|
|
90
|
+
try {
|
|
91
|
+
fn();
|
|
92
|
+
} catch {}
|
|
93
|
+
});
|
|
94
|
+
await (0, _client.clearCredentials)();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const MedGenHost = () => {
|
|
99
|
+
const [url, setUrl] = (0, _react.useState)(null);
|
|
100
|
+
const [capabilities, setCapabilities] = (0, _react.useState)(null);
|
|
101
|
+
const present = (0, _react.useCallback)(async options => {
|
|
102
|
+
const result = await (0, _client.exchange)();
|
|
103
|
+
setCapabilities(result.capabilities);
|
|
104
|
+
setUrl((0, _client.buildEmbedUrl)(result.handoffCode, options?.entryQuestion, result.capabilities));
|
|
105
|
+
(0, _client.emit)("surface.opened");
|
|
106
|
+
}, []);
|
|
107
|
+
(0, _react.useEffect)(() => {
|
|
108
|
+
hostRef.current = present;
|
|
109
|
+
return () => {
|
|
110
|
+
if (hostRef.current === present) hostRef.current = null;
|
|
111
|
+
};
|
|
112
|
+
}, [ present ]);
|
|
113
|
+
const close = (0, _react.useCallback)(() => {
|
|
114
|
+
setUrl(null);
|
|
115
|
+
setCapabilities(null);
|
|
116
|
+
(0, _client.emit)("surface.closed");
|
|
117
|
+
}, []);
|
|
118
|
+
(0, _react.useEffect)(() => {
|
|
119
|
+
teardowns.add(close);
|
|
120
|
+
return () => {
|
|
121
|
+
teardowns.delete(close);
|
|
122
|
+
};
|
|
123
|
+
}, [ close ]);
|
|
124
|
+
return _react.default.createElement(_reactNative.Modal, {
|
|
125
|
+
visible: url !== null,
|
|
126
|
+
animationType: "slide",
|
|
127
|
+
presentationStyle: "fullScreen",
|
|
128
|
+
onRequestClose: close
|
|
129
|
+
}, _react.default.createElement(_reactNative.SafeAreaView, {
|
|
130
|
+
style: styles.container
|
|
131
|
+
}, url && capabilities ? _react.default.createElement(_MedGenWebView.MedGenWebView, {
|
|
132
|
+
url: url,
|
|
133
|
+
handlers: (0, _client.requireConfig)().nativeHandlers ?? {},
|
|
134
|
+
capabilities: capabilities,
|
|
135
|
+
onClose: close
|
|
136
|
+
}) : null));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
exports.MedGenHost = MedGenHost;
|
|
140
|
+
|
|
141
|
+
const MedGenSurface = ({visible: visible = true, options: options, onError: onError, onClose: onClose}) => {
|
|
142
|
+
const [url, setUrl] = (0, _react.useState)(null);
|
|
143
|
+
const [capabilities, setCapabilities] = (0, _react.useState)(null);
|
|
144
|
+
const [loading, setLoading] = (0, _react.useState)(false);
|
|
145
|
+
const started = (0, _react.useRef)(false);
|
|
146
|
+
(0, _react.useEffect)(() => {
|
|
147
|
+
if (!visible || started.current) return;
|
|
148
|
+
started.current = true;
|
|
149
|
+
setLoading(true);
|
|
150
|
+
(0, _client.exchange)().then(result => {
|
|
151
|
+
setCapabilities(result.capabilities);
|
|
152
|
+
setUrl((0, _client.buildEmbedUrl)(result.handoffCode, options?.entryQuestion, result.capabilities));
|
|
153
|
+
(0, _client.emit)("surface.opened");
|
|
154
|
+
}).catch(e => {
|
|
155
|
+
started.current = false;
|
|
156
|
+
onError?.(e);
|
|
157
|
+
}).finally(() => setLoading(false));
|
|
158
|
+
}, [ visible, options, onError ]);
|
|
159
|
+
(0, _react.useEffect)(() => {
|
|
160
|
+
const reset = () => {
|
|
161
|
+
setUrl(null);
|
|
162
|
+
setCapabilities(null);
|
|
163
|
+
started.current = false;
|
|
164
|
+
};
|
|
165
|
+
teardowns.add(reset);
|
|
166
|
+
return () => {
|
|
167
|
+
teardowns.delete(reset);
|
|
168
|
+
};
|
|
169
|
+
}, []);
|
|
170
|
+
return _react.default.createElement(_reactNative.View, {
|
|
171
|
+
style: [ styles.container, !visible && styles.hidden ],
|
|
172
|
+
pointerEvents: visible ? "auto" : "none"
|
|
173
|
+
}, url && capabilities ? _react.default.createElement(_MedGenWebView.MedGenWebView, {
|
|
174
|
+
url: url,
|
|
175
|
+
handlers: (0, _client.requireConfig)().nativeHandlers ?? {},
|
|
176
|
+
capabilities: capabilities,
|
|
177
|
+
onClose: () => {
|
|
178
|
+
setUrl(null);
|
|
179
|
+
setCapabilities(null);
|
|
180
|
+
started.current = false;
|
|
181
|
+
onClose?.();
|
|
182
|
+
}
|
|
183
|
+
}) : _react.default.createElement(_reactNative.View, {
|
|
184
|
+
style: styles.loading
|
|
185
|
+
}, loading ? _react.default.createElement(_reactNative.ActivityIndicator, {
|
|
186
|
+
size: "large"
|
|
187
|
+
}) : null));
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
exports.MedGenSurface = MedGenSurface;
|
|
191
|
+
|
|
192
|
+
const styles = _reactNative.StyleSheet.create({
|
|
193
|
+
container: {
|
|
194
|
+
flex: 1
|
|
195
|
+
},
|
|
196
|
+
hidden: {
|
|
197
|
+
display: "none"
|
|
198
|
+
},
|
|
199
|
+
loading: {
|
|
200
|
+
flex: 1,
|
|
201
|
+
alignItems: "center",
|
|
202
|
+
justifyContent: "center"
|
|
203
|
+
}
|
|
204
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
exports.writeClipboard = exports.shareText = exports.shareFile = exports.saveToDownloads = exports.renderPdf = exports.registerNativeModules = exports.ensureStoragePermission = exports.canShareText = exports.canSaveToDownloads = exports.canRenderPdf = exports.canCopy = void 0;
|
|
8
|
+
|
|
9
|
+
var _reactNative = require("react-native");
|
|
10
|
+
|
|
11
|
+
var _pdfLinks = require("./pdfLinks");
|
|
12
|
+
|
|
13
|
+
let registry = {};
|
|
14
|
+
|
|
15
|
+
const registerNativeModules = mods => {
|
|
16
|
+
registry = {
|
|
17
|
+
...registry,
|
|
18
|
+
...mods
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
exports.registerNativeModules = registerNativeModules;
|
|
23
|
+
|
|
24
|
+
const unwrap = mod => mod?.default ?? mod;
|
|
25
|
+
|
|
26
|
+
const fromRegistry = key => {
|
|
27
|
+
const mod = registry[key];
|
|
28
|
+
return mod ? unwrap(mod) : null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const loadClipboard = () => fromRegistry("clipboard");
|
|
32
|
+
|
|
33
|
+
const loadShare = () => fromRegistry("share");
|
|
34
|
+
|
|
35
|
+
const loadHtmlToPdf = () => fromRegistry("htmlToPdf");
|
|
36
|
+
|
|
37
|
+
const loadBlobUtil = () => fromRegistry("blobUtil");
|
|
38
|
+
|
|
39
|
+
const ensureStoragePermission = async () => {
|
|
40
|
+
if (_reactNative.Platform.OS !== "android") return true;
|
|
41
|
+
if (typeof _reactNative.Platform.Version === "number" && _reactNative.Platform.Version >= 29) return true;
|
|
42
|
+
const permission = _reactNative.PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE;
|
|
43
|
+
if (!permission) return false;
|
|
44
|
+
try {
|
|
45
|
+
const granted = await _reactNative.PermissionsAndroid.request(permission, {
|
|
46
|
+
title: "Save to your device",
|
|
47
|
+
message: "MedGen needs permission to save this document.",
|
|
48
|
+
buttonPositive: "Allow"
|
|
49
|
+
});
|
|
50
|
+
return granted === _reactNative.PermissionsAndroid.RESULTS.GRANTED;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
exports.ensureStoragePermission = ensureStoragePermission;
|
|
57
|
+
|
|
58
|
+
const writeClipboard = async (text, html) => {
|
|
59
|
+
const Clipboard = loadClipboard();
|
|
60
|
+
if (!Clipboard) return false;
|
|
61
|
+
try {
|
|
62
|
+
if (html && typeof Clipboard.setStringHTML === "function") {
|
|
63
|
+
Clipboard.setStringHTML(html, text);
|
|
64
|
+
} else {
|
|
65
|
+
Clipboard.setString(text);
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
exports.writeClipboard = writeClipboard;
|
|
74
|
+
|
|
75
|
+
const canShareText = () => true;
|
|
76
|
+
|
|
77
|
+
exports.canShareText = canShareText;
|
|
78
|
+
|
|
79
|
+
const canCopy = () => loadClipboard() !== null;
|
|
80
|
+
|
|
81
|
+
exports.canCopy = canCopy;
|
|
82
|
+
|
|
83
|
+
const canRenderPdf = () => loadHtmlToPdf() !== null;
|
|
84
|
+
|
|
85
|
+
exports.canRenderPdf = canRenderPdf;
|
|
86
|
+
|
|
87
|
+
const canSaveToDownloads = () => _reactNative.Platform.OS === "android" && loadBlobUtil() !== null;
|
|
88
|
+
|
|
89
|
+
exports.canSaveToDownloads = canSaveToDownloads;
|
|
90
|
+
|
|
91
|
+
const shareText = async text => {
|
|
92
|
+
try {
|
|
93
|
+
const result = await _reactNative.Share.share({
|
|
94
|
+
message: text
|
|
95
|
+
});
|
|
96
|
+
return result.action !== _reactNative.Share.dismissedAction;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
exports.shareText = shareText;
|
|
103
|
+
|
|
104
|
+
const shareFile = async (uri, mimeType = "application/pdf", fileName) => {
|
|
105
|
+
const RNShare = loadShare();
|
|
106
|
+
if (!RNShare) return {
|
|
107
|
+
saved: false,
|
|
108
|
+
reason: "no_module"
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
await RNShare.open({
|
|
112
|
+
url: uri.startsWith("file://") ? uri : `file://${uri}`,
|
|
113
|
+
type: mimeType,
|
|
114
|
+
filename: fileName,
|
|
115
|
+
failOnCancel: false
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
saved: true,
|
|
119
|
+
uri: uri
|
|
120
|
+
};
|
|
121
|
+
} catch (e) {
|
|
122
|
+
const msg = e instanceof Error ? e.message : "";
|
|
123
|
+
return {
|
|
124
|
+
saved: false,
|
|
125
|
+
reason: /cancel/i.test(msg) ? "cancelled" : "failed"
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
exports.shareFile = shareFile;
|
|
131
|
+
|
|
132
|
+
const renderPdf = async (html, fileName, links = []) => {
|
|
133
|
+
const HtmlToPdf = loadHtmlToPdf();
|
|
134
|
+
if (!HtmlToPdf) return null;
|
|
135
|
+
const withLinks = _reactNative.Platform.OS === "ios" && links.length > 0;
|
|
136
|
+
try {
|
|
137
|
+
const {filePath: filePath, base64: base64} = await HtmlToPdf.convert({
|
|
138
|
+
html: html,
|
|
139
|
+
fileName: fileName.replace(/\.pdf$/i, ""),
|
|
140
|
+
directory: "Documents",
|
|
141
|
+
base64: withLinks
|
|
142
|
+
});
|
|
143
|
+
if (filePath && withLinks && base64) await writeLinks(filePath, base64, links);
|
|
144
|
+
return filePath ?? null;
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
exports.renderPdf = renderPdf;
|
|
151
|
+
|
|
152
|
+
const writeLinks = async (filePath, base64, links) => {
|
|
153
|
+
const Blob = loadBlobUtil();
|
|
154
|
+
if (!Blob?.fs?.writeFile) return;
|
|
155
|
+
try {
|
|
156
|
+
const linked = await (0, _pdfLinks.addPdfLinks)(base64, links);
|
|
157
|
+
await Blob.fs.writeFile(filePath, linked, "base64");
|
|
158
|
+
} catch {}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const saveToDownloads = async (sourcePath, fileName) => {
|
|
162
|
+
const Blob = loadBlobUtil();
|
|
163
|
+
if (_reactNative.Platform.OS !== "android") return {
|
|
164
|
+
saved: false,
|
|
165
|
+
reason: "no_module"
|
|
166
|
+
};
|
|
167
|
+
if (!Blob) return {
|
|
168
|
+
saved: false,
|
|
169
|
+
reason: "no_module"
|
|
170
|
+
};
|
|
171
|
+
if (!await ensureStoragePermission()) {
|
|
172
|
+
return {
|
|
173
|
+
saved: false,
|
|
174
|
+
reason: "permission_denied"
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const target = `${Blob.fs.dirs.DownloadDir}/${fileName}`;
|
|
179
|
+
await Blob.fs.cp(sourcePath.replace("file://", ""), target);
|
|
180
|
+
if (_reactNative.Platform.OS === "android" && Blob.android?.addCompleteDownload) {
|
|
181
|
+
Blob.android.addCompleteDownload({
|
|
182
|
+
title: fileName,
|
|
183
|
+
description: "MedGen document",
|
|
184
|
+
mime: "application/pdf",
|
|
185
|
+
path: target,
|
|
186
|
+
showNotification: true
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
saved: true,
|
|
191
|
+
uri: target
|
|
192
|
+
};
|
|
193
|
+
} catch {
|
|
194
|
+
return {
|
|
195
|
+
saved: false,
|
|
196
|
+
reason: "failed"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
exports.saveToDownloads = saveToDownloads;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
exports.sanitizeLinks = exports.addPdfLinks = void 0;
|
|
8
|
+
|
|
9
|
+
var _pdfLib = require("pdf-lib");
|
|
10
|
+
|
|
11
|
+
const MAX_LINKS = 2e3;
|
|
12
|
+
|
|
13
|
+
const isFiniteNumber = n => typeof n === "number" && Number.isFinite(n);
|
|
14
|
+
|
|
15
|
+
const sanitizeLinks = raw => {
|
|
16
|
+
if (!Array.isArray(raw)) return [];
|
|
17
|
+
return raw.slice(0, MAX_LINKS).filter(l => {
|
|
18
|
+
const link = l;
|
|
19
|
+
return !!link && Number.isInteger(link.page) && link.page >= 0 && [ link.x, link.y, link.width, link.height ].every(isFiniteNumber) && link.width > 0 && link.height > 0 && typeof link.url === "string" && /^https?:\/\/[^\s]+$/i.test(link.url);
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
exports.sanitizeLinks = sanitizeLinks;
|
|
24
|
+
|
|
25
|
+
const pdfLiteral = url => url.replace(/[^\x20-\x7E]/g, c => encodeURIComponent(c)).replace(/[\\()]/g, c => `\\${c}`);
|
|
26
|
+
|
|
27
|
+
const addPdfLinks = async (base64, links) => {
|
|
28
|
+
const doc = await _pdfLib.PDFDocument.load(base64);
|
|
29
|
+
const pages = doc.getPages();
|
|
30
|
+
for (const link of links) {
|
|
31
|
+
const page = pages[link.page];
|
|
32
|
+
if (!page) continue;
|
|
33
|
+
const {width: pw, height: ph} = page.getSize();
|
|
34
|
+
const left = Math.max(0, link.x);
|
|
35
|
+
const right = Math.min(pw, link.x + link.width);
|
|
36
|
+
const top = Math.min(ph, ph - link.y);
|
|
37
|
+
const bottom = Math.max(0, ph - link.y - link.height);
|
|
38
|
+
if (right <= left || top <= bottom) continue;
|
|
39
|
+
const annot = doc.context.obj({
|
|
40
|
+
Type: "Annot",
|
|
41
|
+
Subtype: "Link",
|
|
42
|
+
Rect: [ left, bottom, right, top ],
|
|
43
|
+
Border: [ 0, 0, 0 ],
|
|
44
|
+
A: {
|
|
45
|
+
Type: "Action",
|
|
46
|
+
S: "URI",
|
|
47
|
+
URI: _pdfLib.PDFString.of(pdfLiteral(link.url))
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
page.node.addAnnot(doc.context.register(annot));
|
|
51
|
+
}
|
|
52
|
+
return doc.saveAsBase64({
|
|
53
|
+
useObjectStreams: false
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
exports.addPdfLinks = addPdfLinks;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
exports.MedGenError = exports.MedGenEnvironment = void 0;
|
|
8
|
+
|
|
9
|
+
let MedGenEnvironment = exports.MedGenEnvironment = function(MedGenEnvironment) {
|
|
10
|
+
MedGenEnvironment["Development"] = "development";
|
|
11
|
+
MedGenEnvironment["Staging"] = "staging";
|
|
12
|
+
MedGenEnvironment["Production"] = "production";
|
|
13
|
+
return MedGenEnvironment;
|
|
14
|
+
}({});
|
|
15
|
+
|
|
16
|
+
class MedGenError extends Error {
|
|
17
|
+
constructor(code, message, retryable = false, supportReference) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "MedGenError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.retryable = retryable;
|
|
22
|
+
this.supportReference = supportReference;
|
|
23
|
+
Object.setPrototypeOf(this, MedGenError.prototype);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
exports.MedGenError = MedGenError;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { ActivityIndicator, Linking, StyleSheet, View } from "react-native";
|
|
4
|
+
|
|
5
|
+
import { WebView as RNWebView } from "react-native-webview";
|
|
6
|
+
|
|
7
|
+
const WebView = RNWebView;
|
|
8
|
+
|
|
9
|
+
import { handleBridgeMessage } from "./bridge";
|
|
10
|
+
|
|
11
|
+
import { embedBase, emit, SDK_VERSION } from "./client";
|
|
12
|
+
|
|
13
|
+
export const originOf = u => {
|
|
14
|
+
const m = /^[a-z][a-z0-9+.-]*:\/\/[^/?#]+/i.exec(u);
|
|
15
|
+
return m ? m[0].toLowerCase() : "";
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const decideNavigation = (url, base, isTopFrame) => {
|
|
19
|
+
if (isSameOrigin(url, base)) return "load";
|
|
20
|
+
if (isTopFrame === false) return "block";
|
|
21
|
+
if (/^(mailto|tel|sms):/i.test(url)) return "external";
|
|
22
|
+
if (!/^https?:\/\//i.test(url)) return "block";
|
|
23
|
+
return "external";
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const openExternally = async url => {
|
|
27
|
+
try {
|
|
28
|
+
if (!await Linking.canOpenURL(url)) {
|
|
29
|
+
emit("surface.externalLinkFailed", {
|
|
30
|
+
url: url,
|
|
31
|
+
reason: "unsupported"
|
|
32
|
+
});
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
await Linking.openURL(url);
|
|
36
|
+
emit("surface.externalLinkOpened", {
|
|
37
|
+
url: url
|
|
38
|
+
});
|
|
39
|
+
return true;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
emit("surface.externalLinkFailed", {
|
|
42
|
+
url: url,
|
|
43
|
+
reason: error instanceof Error ? error.message : "unknown"
|
|
44
|
+
});
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const makeNonce = () => `${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
|
|
50
|
+
|
|
51
|
+
export const isSameOrigin = (target, base) => {
|
|
52
|
+
if (target === "about:blank") return true;
|
|
53
|
+
const b = originOf(base);
|
|
54
|
+
return b !== "" && originOf(target) === b;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const MedGenWebView = ({url: url, handlers: handlers, capabilities: capabilities, onClose: onClose}) => {
|
|
58
|
+
const ref = useRef(null);
|
|
59
|
+
const nonce = useMemo(() => makeNonce(), [ url ]);
|
|
60
|
+
const isMedGenOrigin = useCallback(target => isSameOrigin(target, embedBase()), []);
|
|
61
|
+
const onShouldStartLoadWithRequest = useCallback(req => {
|
|
62
|
+
const decision = decideNavigation(req.url, embedBase(), req.isTopFrame);
|
|
63
|
+
if (decision === "load") return true;
|
|
64
|
+
if (decision === "external") void openExternally(req.url);
|
|
65
|
+
return false;
|
|
66
|
+
}, [ isMedGenOrigin ]);
|
|
67
|
+
const onMessage = useCallback(event => {
|
|
68
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
69
|
+
try {
|
|
70
|
+
const probe = JSON.parse(event.nativeEvent.data);
|
|
71
|
+
if (probe?.__medgenLog) {
|
|
72
|
+
const {level: level = "log", args: args = []} = probe.__medgenLog;
|
|
73
|
+
console[level === "error" ? "error" : "warn"]("[MedGen surface]", ...args);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
if (!isMedGenOrigin(event.nativeEvent.url)) {
|
|
79
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
80
|
+
console.warn("[MedGen] message rejected — origin not allowed:", event.nativeEvent.url);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
void handleBridgeMessage(event.nativeEvent.data, replyJson => {
|
|
85
|
+
const safe = JSON.stringify(replyJson).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
86
|
+
ref.current?.injectJavaScript(`window.__medgen && window.__medgen.receive(${safe}); true;`);
|
|
87
|
+
}, {
|
|
88
|
+
handlers: handlers,
|
|
89
|
+
capabilities: capabilities,
|
|
90
|
+
onClose: onClose,
|
|
91
|
+
nonce: nonce
|
|
92
|
+
});
|
|
93
|
+
}, [ isMedGenOrigin, handlers, capabilities, onClose, nonce ]);
|
|
94
|
+
return React.createElement(WebView, {
|
|
95
|
+
ref: ref,
|
|
96
|
+
source: {
|
|
97
|
+
uri: url
|
|
98
|
+
},
|
|
99
|
+
style: styles.web,
|
|
100
|
+
injectedJavaScriptBeforeContentLoaded: `window.__medgenNonce=${JSON.stringify(nonce)};` + (typeof __DEV__ !== "undefined" && __DEV__ ? `(function(){var p=window.ReactNativeWebView&&window.ReactNativeWebView.postMessage;` + `if(!p)return;['log','warn','error'].forEach(function(l){var o=console[l];` + `console[l]=function(){try{p.call(window.ReactNativeWebView,JSON.stringify(` + `{__medgenLog:{level:l,args:Array.prototype.slice.call(arguments).map(String)}}));}` + `catch(e){}o&&o.apply(console,arguments);};});})();` : "") + `true;`,
|
|
101
|
+
injectedJavaScriptBeforeContentLoadedForMainFrameOnly: true,
|
|
102
|
+
originWhitelist: [ originOf(embedBase()) ],
|
|
103
|
+
onShouldStartLoadWithRequest: onShouldStartLoadWithRequest,
|
|
104
|
+
onMessage: onMessage,
|
|
105
|
+
allowFileAccess: false,
|
|
106
|
+
allowUniversalAccessFromFileURLs: false,
|
|
107
|
+
mixedContentMode: "never",
|
|
108
|
+
javaScriptCanOpenWindowsAutomatically: false,
|
|
109
|
+
setSupportMultipleWindows: false,
|
|
110
|
+
onOpenWindow: event => {
|
|
111
|
+
const url = event?.nativeEvent?.targetUrl;
|
|
112
|
+
if (!url) return;
|
|
113
|
+
const decision = decideNavigation(url, embedBase(), true);
|
|
114
|
+
if (decision === "load") {
|
|
115
|
+
ref.current?.injectJavaScript(`window.location.assign(${JSON.stringify(url)});true;`);
|
|
116
|
+
} else if (decision === "external") {
|
|
117
|
+
void openExternally(url);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
onError: event => {
|
|
121
|
+
emit("surface.loadFailed", {
|
|
122
|
+
description: event?.nativeEvent?.description,
|
|
123
|
+
code: event?.nativeEvent?.code
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
onHttpError: event => {
|
|
127
|
+
emit("surface.loadFailed", {
|
|
128
|
+
statusCode: event?.nativeEvent?.statusCode
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
automaticallyAdjustContentInsets: false,
|
|
132
|
+
contentInsetAdjustmentBehavior: "never",
|
|
133
|
+
keyboardDisplayRequiresUserAction: false,
|
|
134
|
+
androidLayerType: "hardware",
|
|
135
|
+
onContentProcessDidTerminate: () => {
|
|
136
|
+
emit("surface.loadFailed", {
|
|
137
|
+
reason: "content_process_terminated"
|
|
138
|
+
});
|
|
139
|
+
ref.current?.reload();
|
|
140
|
+
},
|
|
141
|
+
allowsInlineMediaPlayback: true,
|
|
142
|
+
allowsLinkPreview: false,
|
|
143
|
+
applicationNameForUserAgent: `MedGenSDK/${SDK_VERSION}`,
|
|
144
|
+
sharedCookiesEnabled: true,
|
|
145
|
+
thirdPartyCookiesEnabled: false,
|
|
146
|
+
startInLoadingState: true,
|
|
147
|
+
renderLoading: () => React.createElement(View, {
|
|
148
|
+
style: styles.loading
|
|
149
|
+
}, React.createElement(ActivityIndicator, {
|
|
150
|
+
size: "large"
|
|
151
|
+
})),
|
|
152
|
+
bounces: false,
|
|
153
|
+
overScrollMode: "never"
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const styles = StyleSheet.create({
|
|
158
|
+
web: {
|
|
159
|
+
flex: 1
|
|
160
|
+
},
|
|
161
|
+
loading: {
|
|
162
|
+
...StyleSheet.absoluteFillObject,
|
|
163
|
+
alignItems: "center",
|
|
164
|
+
justifyContent: "center"
|
|
165
|
+
}
|
|
166
|
+
});
|