@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.
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+
7
+ exports.handleBridgeMessage = handleBridgeMessage;
8
+
9
+ var _client = require("./client");
10
+
11
+ var native = _interopRequireWildcard(require("./native"));
12
+
13
+ var _pdfLinks = require("./pdfLinks");
14
+
15
+ function _interopRequireWildcard(e, t) {
16
+ if ("function" == typeof WeakMap) var r = new WeakMap, n = new WeakMap;
17
+ return (_interopRequireWildcard = function(e, t) {
18
+ if (!t && e && e.__esModule) return e;
19
+ var o, i, f = {
20
+ __proto__: null,
21
+ default: e
22
+ };
23
+ if (null === e || "object" != typeof e && "function" != typeof e) return f;
24
+ if (o = t ? n : r) {
25
+ if (o.has(e)) return o.get(e);
26
+ o.set(e, f);
27
+ }
28
+ 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]);
29
+ return f;
30
+ })(e, t);
31
+ }
32
+
33
+ const V = 1;
34
+
35
+ const ok = (id, payload) => JSON.stringify({
36
+ v: V,
37
+ id: id,
38
+ ok: true,
39
+ payload: payload
40
+ });
41
+
42
+ const fail = (id, code, message) => JSON.stringify({
43
+ v: V,
44
+ id: id,
45
+ ok: false,
46
+ error: {
47
+ code: code,
48
+ message: message
49
+ }
50
+ });
51
+
52
+ async function handleBridgeMessage(raw, reply, ctx) {
53
+ const trace = (why, detail) => {
54
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
55
+ console.warn(`[MedGen] bridge dropped a frame: ${why}`, detail ?? "");
56
+ }
57
+ };
58
+ let msg;
59
+ try {
60
+ msg = JSON.parse(raw);
61
+ } catch {
62
+ return;
63
+ }
64
+ if (msg.v !== V) {
65
+ trace(`protocol version ${String(msg.v)} (this SDK speaks v${V})`);
66
+ return;
67
+ }
68
+ if (!msg.id || !msg.method) {
69
+ trace("missing id or method", msg);
70
+ return;
71
+ }
72
+ const {handlers: handlers, capabilities: capabilities, onClose: onClose, nonce: nonce} = ctx;
73
+ if (typeof msg.nonce !== "string" || msg.nonce.length !== nonce.length || msg.nonce !== nonce) {
74
+ trace(typeof msg.nonce !== "string" ? `no nonce on "${String(msg.method)}" — the surface is not sending ` + "window.__medgenNonce. Every native call will be dropped." : `nonce mismatch on "${String(msg.method)}" — a subframe, or a stale page`);
75
+ return;
76
+ }
77
+ const {id: id, method: method} = msg;
78
+ const payload = msg.payload ?? {};
79
+ try {
80
+ switch (method) {
81
+ case "system.ready":
82
+ reply(ok(id, {
83
+ platform: "react-native",
84
+ version: _client.SDK_VERSION,
85
+ features: {
86
+ share: capabilities.share && (Boolean(handlers.onShare) || native.canShareText()),
87
+ clipboard: capabilities.clipboard && (Boolean(handlers.onCopy) || native.canCopy()),
88
+ speech: Boolean(handlers.onSpeechStart) && capabilities.dictation,
89
+ fileUpload: Boolean(handlers.onFilePick) && capabilities.fileUpload,
90
+ downloadPdf: capabilities.downloadPdf && (Boolean(handlers.onSavePdf) || native.canRenderPdf())
91
+ }
92
+ }));
93
+ return;
94
+
95
+ case "system.dismiss":
96
+ onClose();
97
+ reply(ok(id));
98
+ return;
99
+
100
+ case "share.content":
101
+ {
102
+ if (!capabilities.share) return reply(fail(id, "FORBIDDEN", "share not granted"));
103
+ const text = String(payload.text ?? "");
104
+ const completed = handlers.onShare ? await handlers.onShare(text) : await native.shareText(text);
105
+ reply(ok(id, {
106
+ completed: completed
107
+ }));
108
+ return;
109
+ }
110
+
111
+ case "clipboard.write":
112
+ {
113
+ if (!capabilities.clipboard) return reply(fail(id, "FORBIDDEN", "clipboard not granted"));
114
+ const text = String(payload.text ?? "");
115
+ const html = capabilities.richClipboard ? payload.html : undefined;
116
+ const written = handlers.onCopy ? await handlers.onCopy({
117
+ text: text,
118
+ html: html
119
+ }) : await native.writeClipboard(text, html);
120
+ reply(ok(id, {
121
+ written: written
122
+ }));
123
+ return;
124
+ }
125
+
126
+ case "speech.start":
127
+ {
128
+ if (!capabilities.dictation) return reply(fail(id, "FORBIDDEN", "dictation not granted"));
129
+ if (!handlers.onSpeechStart) return reply(fail(id, "UNAVAILABLE", "No speech handler"));
130
+ await handlers.onSpeechStart();
131
+ reply(ok(id, {
132
+ listening: true
133
+ }));
134
+ return;
135
+ }
136
+
137
+ case "speech.stop":
138
+ {
139
+ if (!capabilities.dictation) return reply(fail(id, "FORBIDDEN", "dictation not granted"));
140
+ if (!handlers.onSpeechStop) return reply(fail(id, "UNAVAILABLE", "No speech handler"));
141
+ const transcript = await handlers.onSpeechStop();
142
+ reply(ok(id, {
143
+ transcript: transcript
144
+ }));
145
+ return;
146
+ }
147
+
148
+ case "pdf.save":
149
+ {
150
+ const html = String(payload.html ?? "");
151
+ const fileName = String(payload.fileName ?? "MedGen.pdf");
152
+ const links = (0, _pdfLinks.sanitizeLinks)(payload.links);
153
+ if (handlers.onSavePdf) {
154
+ const saved = await handlers.onSavePdf({
155
+ html: html,
156
+ fileName: fileName,
157
+ links: links
158
+ });
159
+ return reply(ok(id, {
160
+ saved: saved
161
+ }));
162
+ }
163
+ const path = await native.renderPdf(html, fileName, links);
164
+ if (!path) {
165
+ return reply(fail(id, "UNAVAILABLE", "PDF rendering needs react-native-html-to-pdf, or an onSavePdf handler"));
166
+ }
167
+ if (capabilities.downloadToDevice) {
168
+ const result = await native.saveToDownloads(path, fileName);
169
+ if (result.saved) return reply(ok(id, {
170
+ saved: true,
171
+ uri: result.uri
172
+ }));
173
+ }
174
+ const shared = await native.shareFile(path, "application/pdf", fileName);
175
+ reply(ok(id, {
176
+ saved: shared.saved,
177
+ uri: shared.uri,
178
+ reason: shared.reason
179
+ }));
180
+ return;
181
+ }
182
+
183
+ case "file.pick":
184
+ case "file.capture":
185
+ {
186
+ if (!capabilities.fileUpload) return reply(fail(id, "FORBIDDEN", "file upload not granted"));
187
+ if (!handlers.onFilePick) return reply(ok(id, {
188
+ files: []
189
+ }));
190
+ const files = await handlers.onFilePick();
191
+ reply(ok(id, {
192
+ files: files
193
+ }));
194
+ return;
195
+ }
196
+
197
+ default:
198
+ trace(`unknown method "${String(method)}" — SDK v${_client.SDK_VERSION}`);
199
+ reply(fail(id, "UNSUPPORTED", `Unknown method: ${String(method)}`));
200
+ return;
201
+ }
202
+ } catch (e) {
203
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
204
+ console.warn("[MedGen] native handler failed:", e);
205
+ }
206
+ reply(fail(id, "HANDLER_ERROR", "Handler failed"));
207
+ }
208
+ }
@@ -0,0 +1,445 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+
7
+ exports.apiBase = exports.SDK_VERSION = void 0;
8
+
9
+ exports.buildEmbedUrl = buildEmbedUrl;
10
+
11
+ exports.clearCredentials = clearCredentials;
12
+
13
+ exports.configure = configure;
14
+
15
+ exports.embedBase = void 0;
16
+
17
+ exports.emit = emit;
18
+
19
+ exports.exchange = exchange;
20
+
21
+ exports.parseBranding = parseBranding;
22
+
23
+ exports.parseCapabilities = parseCapabilities;
24
+
25
+ exports.readCredentials = readCredentials;
26
+
27
+ exports.requireConfig = requireConfig;
28
+
29
+ exports.resetConfig = resetConfig;
30
+
31
+ exports.storeCredentials = storeCredentials;
32
+
33
+ var _reactNative = require("react-native");
34
+
35
+ var _native = require("./native");
36
+
37
+ var Keychain = _interopRequireWildcard(require("react-native-keychain"));
38
+
39
+ var _types = require("./types");
40
+
41
+ function _interopRequireWildcard(e, t) {
42
+ if ("function" == typeof WeakMap) var r = new WeakMap, n = new WeakMap;
43
+ return (_interopRequireWildcard = function(e, t) {
44
+ if (!t && e && e.__esModule) return e;
45
+ var o, i, f = {
46
+ __proto__: null,
47
+ default: e
48
+ };
49
+ if (null === e || "object" != typeof e && "function" != typeof e) return f;
50
+ if (o = t ? n : r) {
51
+ if (o.has(e)) return o.get(e);
52
+ o.set(e, f);
53
+ }
54
+ 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]);
55
+ return f;
56
+ })(e, t);
57
+ }
58
+
59
+ const KEYCHAIN_SERVICE = "jp.nihinmedia.medgen";
60
+
61
+ const ASSERTION_TIMEOUT_MS = 1e4;
62
+
63
+ const ATTESTATION_TIMEOUT_MS = 15e3;
64
+
65
+ const SDK_VERSION = exports.SDK_VERSION = "1.0.12";
66
+
67
+ const DEFAULT_API = {
68
+ development: "https://dev.sugoimed.com",
69
+ staging: "https://staging.sugoimed.com",
70
+ production: "https://medgen.nihinmedia.jp"
71
+ };
72
+
73
+ const DEFAULT_EMBED = {
74
+ development: "https://dev.sugoimed.com",
75
+ staging: "https://staging.sugoimed.com",
76
+ production: "https://medgen.nihinmedia.jp"
77
+ };
78
+
79
+ let config = null;
80
+
81
+ function configure(cfg) {
82
+ config = cfg;
83
+ if (cfg.nativeModules) (0, _native.registerNativeModules)(cfg.nativeModules);
84
+ }
85
+
86
+ function resetConfig() {
87
+ config = null;
88
+ }
89
+
90
+ function requireConfig() {
91
+ if (!config) {
92
+ throw new _types.MedGenError("NOT_INITIALIZED", "Call MedGen.initialize() before present()");
93
+ }
94
+ return config;
95
+ }
96
+
97
+ function requireHttps(url, field) {
98
+ const lower = url.toLowerCase();
99
+ const isLocal = lower.startsWith("http://localhost") || lower.startsWith("http://127.0.0.1");
100
+ if (!lower.startsWith("https://") && !isLocal) {
101
+ throw new _types.MedGenError("NOT_INITIALIZED", `${field} must use https:// — refusing to send an assertion over plaintext`);
102
+ }
103
+ return url.replace(/\/+$/, "");
104
+ }
105
+
106
+ const apiBase = () => {
107
+ const cfg = requireConfig();
108
+ const base = cfg.apiBaseUrl ?? DEFAULT_API[cfg.environment] ?? DEFAULT_API.staging;
109
+ return requireHttps(base, "apiBaseUrl");
110
+ };
111
+
112
+ exports.apiBase = apiBase;
113
+
114
+ const embedBase = () => {
115
+ const cfg = requireConfig();
116
+ const base = cfg.embedBaseUrl ?? DEFAULT_EMBED[cfg.environment] ?? DEFAULT_EMBED.staging;
117
+ return requireHttps(base, "embedBaseUrl");
118
+ };
119
+
120
+ exports.embedBase = embedBase;
121
+
122
+ function emit(name, detail) {
123
+ const handler = config?.onEvent;
124
+ if (!handler) return;
125
+ const event = {
126
+ name: name,
127
+ at: Date.now(),
128
+ detail: detail
129
+ };
130
+ try {
131
+ handler(event);
132
+ } catch {}
133
+ }
134
+
135
+ async function storeCredentials(creds) {
136
+ await Keychain.setGenericPassword("medgen", JSON.stringify(creds), {
137
+ service: KEYCHAIN_SERVICE,
138
+ accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY
139
+ });
140
+ }
141
+
142
+ async function readCredentials() {
143
+ try {
144
+ const res = await Keychain.getGenericPassword({
145
+ service: KEYCHAIN_SERVICE
146
+ });
147
+ if (!res) return null;
148
+ return JSON.parse(res.password);
149
+ } catch {
150
+ return null;
151
+ }
152
+ }
153
+
154
+ async function clearCredentials() {
155
+ await Keychain.resetGenericPassword({
156
+ service: KEYCHAIN_SERVICE
157
+ });
158
+ }
159
+
160
+ function withTimeout(p, ms, code, what) {
161
+ let timer;
162
+ const timeout = new Promise((_, reject) => {
163
+ timer = setTimeout(() => reject(new _types.MedGenError(code, `${what} exceeded ${ms}ms`, true)), ms);
164
+ });
165
+ return Promise.race([ p, timeout ]).finally(() => clearTimeout(timer));
166
+ }
167
+
168
+ async function fetchAssertion() {
169
+ const {assertionProvider: assertionProvider, assertion: assertion} = requireConfig();
170
+ if (!assertionProvider) {
171
+ if (assertion) return assertion;
172
+ throw new _types.MedGenError("NOT_INITIALIZED", "Set assertionProvider (or, to try the SDK once, assertion) in MedGen.initialize()");
173
+ }
174
+ try {
175
+ return await withTimeout(assertionProvider(), ASSERTION_TIMEOUT_MS, "ASSERTION_PROVIDER_TIMEOUT", "Assertion provider");
176
+ } catch (e) {
177
+ if (e instanceof _types.MedGenError) throw e;
178
+ throw new _types.MedGenError("ASSERTION_PROVIDER_FAILED", e instanceof Error ? e.message : "Assertion provider failed", true);
179
+ }
180
+ }
181
+
182
+ async function fetchAttestation(assertion) {
183
+ const {attestationProvider: attestationProvider} = requireConfig();
184
+ if (!attestationProvider) return "";
185
+ try {
186
+ return await withTimeout(attestationProvider(assertion), ATTESTATION_TIMEOUT_MS, "ATTESTATION_FAILED", "Attestation provider");
187
+ } catch (e) {
188
+ if (e instanceof _types.MedGenError) throw e;
189
+ throw new _types.MedGenError("ATTESTATION_FAILED", e instanceof Error ? e.message : "Attestation provider failed", true);
190
+ }
191
+ }
192
+
193
+ const CAPABILITY_DEFAULTS = {
194
+ theming: false,
195
+ customLogo: false,
196
+ customMonogram: false,
197
+ hideAttribution: false,
198
+ topbar: true,
199
+ newChat: true,
200
+ sidebar: false,
201
+ networkBanner: true,
202
+ notifications: false,
203
+ referral: false,
204
+ announcements: false,
205
+ appDownloadPrompt: false,
206
+ notes: false,
207
+ questionForum: false,
208
+ workshopVideos: false,
209
+ collections: false,
210
+ ads: false,
211
+ navBar: false,
212
+ settings: false,
213
+ settingsLegal: true,
214
+ settingsTheme: false,
215
+ settingsSupport: false,
216
+ settingsSystemInfo: false,
217
+ settingsLogout: false,
218
+ profileAccount: false,
219
+ profileDeleteAccount: false,
220
+ quickAnswer: true,
221
+ detailedAnswer: true,
222
+ japaneseAnswer: true,
223
+ dictation: false,
224
+ fileUpload: false,
225
+ clipboard: false,
226
+ richClipboard: true,
227
+ share: true,
228
+ downloadToDevice: false,
229
+ closeButton: false,
230
+ downloadPdf: false,
231
+ chatHistory: true,
232
+ bookmarks: false,
233
+ citationsPanel: true,
234
+ referencesShowMore: true,
235
+ directLink: true
236
+ };
237
+
238
+ function parseBranding(raw) {
239
+ const src = raw ?? {};
240
+ const out = {};
241
+ const asset = value => {
242
+ if (!value || typeof value !== "object") return undefined;
243
+ const a = value;
244
+ if (typeof a.url !== "string" || !a.url) return undefined;
245
+ return {
246
+ url: a.url,
247
+ alt: typeof a.alt === "string" ? a.alt : undefined,
248
+ width: typeof a.width === "number" ? a.width : undefined,
249
+ height: typeof a.height === "number" ? a.height : undefined
250
+ };
251
+ };
252
+ const logo = asset(src.logo);
253
+ if (logo) out.logo = logo;
254
+ const monogram = asset(src.monogram);
255
+ if (monogram) out.monogram = monogram;
256
+ const palette = value => {
257
+ if (!value || typeof value !== "object") return undefined;
258
+ const entries = Object.entries(value).filter(([, v]) => typeof v === "string");
259
+ return entries.length ? Object.fromEntries(entries) : undefined;
260
+ };
261
+ const colors = palette(src.colors);
262
+ if (colors) out.colors = colors;
263
+ const colorsDark = palette(src.colorsDark);
264
+ if (colorsDark) out.colorsDark = colorsDark;
265
+ if (src.attribution && typeof src.attribution === "object") {
266
+ out.attribution = {
267
+ hidden: Boolean(src.attribution.hidden)
268
+ };
269
+ }
270
+ return out;
271
+ }
272
+
273
+ function parseCapabilities(raw) {
274
+ const src = raw ?? {};
275
+ const pick = (key, fallback) => typeof src[key] === "boolean" ? src[key] : fallback;
276
+ return {
277
+ theming: pick("theming", CAPABILITY_DEFAULTS.theming),
278
+ customLogo: pick("custom_logo", CAPABILITY_DEFAULTS.customLogo),
279
+ customMonogram: pick("custom_monogram", CAPABILITY_DEFAULTS.customMonogram),
280
+ hideAttribution: pick("hide_attribution", CAPABILITY_DEFAULTS.hideAttribution),
281
+ topbar: pick("topbar", CAPABILITY_DEFAULTS.topbar),
282
+ newChat: pick("new_chat", CAPABILITY_DEFAULTS.newChat),
283
+ sidebar: pick("sidebar", CAPABILITY_DEFAULTS.sidebar),
284
+ networkBanner: pick("network_banner", CAPABILITY_DEFAULTS.networkBanner),
285
+ notifications: pick("notifications", CAPABILITY_DEFAULTS.notifications),
286
+ referral: pick("referral", CAPABILITY_DEFAULTS.referral),
287
+ announcements: pick("announcements", CAPABILITY_DEFAULTS.announcements),
288
+ appDownloadPrompt: pick("app_download_prompt", CAPABILITY_DEFAULTS.appDownloadPrompt),
289
+ notes: pick("notes", CAPABILITY_DEFAULTS.notes),
290
+ questionForum: pick("question_forum", CAPABILITY_DEFAULTS.questionForum),
291
+ workshopVideos: pick("workshop_videos", CAPABILITY_DEFAULTS.workshopVideos),
292
+ collections: pick("collections", CAPABILITY_DEFAULTS.collections),
293
+ ads: pick("ads", CAPABILITY_DEFAULTS.ads),
294
+ navBar: pick("nav_bar", CAPABILITY_DEFAULTS.navBar),
295
+ settings: pick("settings", CAPABILITY_DEFAULTS.settings),
296
+ settingsLegal: pick("settings_legal", CAPABILITY_DEFAULTS.settingsLegal),
297
+ settingsTheme: pick("settings_theme", CAPABILITY_DEFAULTS.settingsTheme),
298
+ settingsSupport: pick("settings_support", CAPABILITY_DEFAULTS.settingsSupport),
299
+ settingsSystemInfo: pick("settings_system_info", CAPABILITY_DEFAULTS.settingsSystemInfo),
300
+ settingsLogout: pick("settings_logout", CAPABILITY_DEFAULTS.settingsLogout),
301
+ profileAccount: pick("profile_account", CAPABILITY_DEFAULTS.profileAccount),
302
+ profileDeleteAccount: pick("profile_delete_account", CAPABILITY_DEFAULTS.profileDeleteAccount),
303
+ quickAnswer: pick("quick_answer", CAPABILITY_DEFAULTS.quickAnswer),
304
+ detailedAnswer: pick("detailed_answer", CAPABILITY_DEFAULTS.detailedAnswer),
305
+ japaneseAnswer: pick("japanese_answer", CAPABILITY_DEFAULTS.japaneseAnswer),
306
+ dictation: pick("dictation", CAPABILITY_DEFAULTS.dictation),
307
+ fileUpload: pick("file_upload", CAPABILITY_DEFAULTS.fileUpload),
308
+ clipboard: pick("clipboard", CAPABILITY_DEFAULTS.clipboard),
309
+ richClipboard: pick("rich_clipboard", CAPABILITY_DEFAULTS.richClipboard),
310
+ share: pick("share", CAPABILITY_DEFAULTS.share),
311
+ downloadToDevice: pick("download_to_device", CAPABILITY_DEFAULTS.downloadToDevice),
312
+ closeButton: pick("close_button", CAPABILITY_DEFAULTS.closeButton),
313
+ downloadPdf: pick("download_pdf", CAPABILITY_DEFAULTS.downloadPdf),
314
+ chatHistory: pick("chat_history", CAPABILITY_DEFAULTS.chatHistory),
315
+ bookmarks: pick("bookmarks", CAPABILITY_DEFAULTS.bookmarks),
316
+ citationsPanel: pick("citations_panel", CAPABILITY_DEFAULTS.citationsPanel),
317
+ referencesShowMore: pick("references_show_more", CAPABILITY_DEFAULTS.referencesShowMore),
318
+ directLink: pick("direct_link", CAPABILITY_DEFAULTS.directLink)
319
+ };
320
+ }
321
+
322
+ const CLIENT_BY_PLATFORM = {
323
+ ios: "sdk-ios",
324
+ android: "sdk-android"
325
+ };
326
+
327
+ async function exchange() {
328
+ emit("exchange.started");
329
+ const assertion = await fetchAssertion();
330
+ const attestation = await fetchAttestation(assertion);
331
+ const client = CLIENT_BY_PLATFORM[_reactNative.Platform.OS] ?? "sdk-ios";
332
+ const headers = {
333
+ "Content-Type": "application/json",
334
+ "X-MedGen-Client": `rn/${SDK_VERSION}`
335
+ };
336
+ if (attestation) headers["X-MedGen-Attestation"] = attestation;
337
+ let res;
338
+ try {
339
+ res = await fetch(`${apiBase()}/api/v1/partner/session/exchange`, {
340
+ method: "POST",
341
+ headers: headers,
342
+ body: JSON.stringify({
343
+ assertion: assertion,
344
+ client: client
345
+ })
346
+ });
347
+ } catch (e) {
348
+ const err = new _types.MedGenError("NETWORK_UNAVAILABLE", e instanceof Error ? e.message : "Network unavailable", true);
349
+ emit("exchange.failed", {
350
+ code: err.code
351
+ });
352
+ throw err;
353
+ }
354
+ const body = await res.json().catch(() => ({}));
355
+ if (!res.ok) {
356
+ const err = new _types.MedGenError(body.error ?? "UNKNOWN", body.message ?? "Exchange failed", Boolean(body.retryable), body.support_reference);
357
+ emit("exchange.failed", {
358
+ code: err.code,
359
+ status: res.status
360
+ });
361
+ throw err;
362
+ }
363
+ const access = body.access;
364
+ const refresh = body.refresh;
365
+ if (typeof access !== "string" || !access || typeof refresh !== "string" || !refresh) {
366
+ const err = new _types.MedGenError("UNKNOWN", "Exchange response is missing session tokens", true);
367
+ emit("exchange.failed", {
368
+ code: err.code
369
+ });
370
+ throw err;
371
+ }
372
+ if (typeof body.handoff_code !== "string" || !body.handoff_code) {
373
+ const err = new _types.MedGenError("UNKNOWN", "Exchange response is missing a handoff code", true);
374
+ emit("exchange.failed", {
375
+ code: err.code
376
+ });
377
+ throw err;
378
+ }
379
+ await storeCredentials({
380
+ access: access,
381
+ refresh: refresh
382
+ });
383
+ const user = body.user ?? {};
384
+ const partner = body.partner ?? {};
385
+ const result = {
386
+ handoffCode: body.handoff_code,
387
+ isNewUser: Boolean(user.is_new),
388
+ emailCollisionPending: body.error === "EMAIL_COLLISION_PENDING",
389
+ brandingVersion: String(partner.branding_version ?? "1"),
390
+ branding: parseBranding(body.branding),
391
+ capabilities: parseCapabilities(partner.capabilities ?? body.capabilities)
392
+ };
393
+ emit("exchange.succeeded", {
394
+ isNewUser: result.isNewUser,
395
+ emailCollisionPending: result.emailCollisionPending
396
+ });
397
+ return result;
398
+ }
399
+
400
+ const B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
401
+
402
+ function toBase64(input) {
403
+ const bytes = [];
404
+ const utf8 = encodeURIComponent(input);
405
+ for (let i = 0; i < utf8.length; i += 1) {
406
+ if (utf8[i] === "%") {
407
+ bytes.push(parseInt(utf8.substr(i + 1, 2), 16));
408
+ i += 2;
409
+ } else {
410
+ bytes.push(utf8.charCodeAt(i));
411
+ }
412
+ }
413
+ let out = "";
414
+ for (let i = 0; i < bytes.length; i += 3) {
415
+ const b0 = bytes[i];
416
+ const b1 = bytes[i + 1];
417
+ const b2 = bytes[i + 2];
418
+ out += B64_ALPHABET[b0 >> 2];
419
+ out += B64_ALPHABET[(b0 & 3) << 4 | (b1 ?? 0) >> 4];
420
+ out += b1 === undefined ? "=" : B64_ALPHABET[(b1 & 15) << 2 | (b2 ?? 0) >> 6];
421
+ out += b2 === undefined ? "=" : B64_ALPHABET[b2 & 63];
422
+ }
423
+ return out;
424
+ }
425
+
426
+ function buildEmbedUrl(handoffCode, entryQuestion, capabilities) {
427
+ const cfg = requireConfig();
428
+ const params = [ [ "hc", handoffCode ], [ "locale", cfg.locale ?? "ja" ] ];
429
+ if (cfg.theme && capabilities?.theming !== false) {
430
+ const branding = {
431
+ colors: {
432
+ primary: cfg.theme.primary,
433
+ secondary: cfg.theme.secondary,
434
+ accent: cfg.theme.accent,
435
+ surface: cfg.theme.surface,
436
+ onSurface: cfg.theme.onSurface
437
+ },
438
+ colorsDark: cfg.theme.darkVariant
439
+ };
440
+ params.push([ "theme", toBase64(JSON.stringify(branding)) ]);
441
+ }
442
+ if (entryQuestion) params.push([ "q", entryQuestion ]);
443
+ const query = params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
444
+ return `${embedBase()}/embed?${query}`;
445
+ }