@patientos/website-kit 0.2.1 → 0.2.2
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/dist/{checkout-block-CrO7OxI9.d.ts → checkout-block-Cog4H4I4.d.ts} +1 -0
- package/dist/{chunk-BW7YKCKH.js → chunk-BHVDY6SC.js} +146 -96
- package/dist/{chunk-CRYD7EXD.js → chunk-N6IGS47W.js} +1 -1
- package/dist/{chunk-EO64UB5F.js → chunk-UTCKIWE7.js} +914 -846
- package/dist/index.d.ts +28 -3
- package/dist/index.js +13 -4
- package/dist/islands-impl.d.ts +3 -4
- package/dist/islands-impl.js +2 -2
- package/dist/islands-registry.js +3 -3
- package/dist/{portal-account-CaK1lqSZ.d.ts → portal-account-pI1cWgsT.d.ts} +77 -1
- package/dist/{portal-account.client-CqnsCCiC.d.ts → portal-account.client-DPRRmfCJ.d.ts} +2 -29
- package/dist/portal-booking-client.d.ts +2 -2
- package/dist/portal-booking-client.js +1 -1
- package/dist/website-kit.css +3 -0
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ interface FunnelPublicApi {
|
|
|
12
12
|
turnstileSiteKey?: string;
|
|
13
13
|
portalUrl?: string;
|
|
14
14
|
portalOrigin?: string;
|
|
15
|
+
portalApiOrigin?: string;
|
|
15
16
|
}
|
|
16
17
|
/** JSON-safe island configuration. Crosses the build→runtime boundary — see `./islands`. */
|
|
17
18
|
type CertificateFunnelProps = {
|
|
@@ -3,101 +3,176 @@ import * as React4 from "react";
|
|
|
3
3
|
|
|
4
4
|
// src/portal-shared.client.tsx
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var
|
|
6
|
+
|
|
7
|
+
// src/portal-client.ts
|
|
8
|
+
var PORTAL_WHOAMI_PATH = "/portal/api/whoami";
|
|
9
|
+
var PORTAL_MAGIC_LINK_PATH = "/portal/api/magic-link";
|
|
10
|
+
var PORTAL_PREFILL_PATH = "/portal/api/prefill";
|
|
11
|
+
var PORTAL_PUBLIC_CLAIM_PATH = "/portal/api/public-claim";
|
|
12
|
+
var PORTAL_RETURN_PATH = "/portal/return";
|
|
13
|
+
var BRIDGE_TIMEOUT_MS = 15e3;
|
|
14
|
+
var BRIDGE_PROBE_TIMEOUT_MS = 8e3;
|
|
15
|
+
var PORTAL_REQUEST_INIT = {
|
|
16
|
+
credentials: "include",
|
|
17
|
+
cache: "no-store"
|
|
18
|
+
};
|
|
9
19
|
function normalizePortalApiOrigin(raw) {
|
|
10
|
-
let
|
|
20
|
+
let url;
|
|
11
21
|
try {
|
|
12
|
-
|
|
22
|
+
url = new URL(raw.trim());
|
|
13
23
|
} catch {
|
|
14
24
|
return null;
|
|
15
25
|
}
|
|
16
|
-
const loopback =
|
|
17
|
-
if (
|
|
18
|
-
if (
|
|
19
|
-
if (
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
function configurePortalApiOrigin(raw) {
|
|
23
|
-
const wanted = raw?.trim();
|
|
24
|
-
if (!wanted) return;
|
|
25
|
-
const origin = normalizePortalApiOrigin(wanted);
|
|
26
|
-
if (!origin) {
|
|
27
|
-
console.error("[portal] ignoring a malformed portal API origin", wanted);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (apiOriginLocked) {
|
|
31
|
-
if (origin !== apiOrigin) {
|
|
32
|
-
console.error("[portal] refusing to re-point the portal API origin", {
|
|
33
|
-
configured: apiOrigin,
|
|
34
|
-
rejected: origin
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
apiOrigin = origin;
|
|
40
|
-
apiOriginLocked = true;
|
|
26
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
27
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) return null;
|
|
28
|
+
if (url.username || url.password || url.search || url.hash) return null;
|
|
29
|
+
if (url.pathname !== "/" && url.pathname !== "") return null;
|
|
30
|
+
return url.origin;
|
|
41
31
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
32
|
+
function encodePortalReturnTarget(url) {
|
|
33
|
+
const bytes = new TextEncoder().encode(url);
|
|
34
|
+
let binary = "";
|
|
35
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
36
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
44
37
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
function sameOriginAvailable(portalOrigin) {
|
|
39
|
+
if (typeof window === "undefined") return false;
|
|
40
|
+
if (portalOrigin == null) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
const normalizedOrigin = portalOrigin.trim().replace(/\/+$/, "");
|
|
44
|
+
return normalizedOrigin !== "" && window.location.origin === normalizedOrigin;
|
|
50
45
|
}
|
|
51
|
-
async function readPortalResult(
|
|
52
|
-
if (!
|
|
46
|
+
async function readPortalResult(response) {
|
|
47
|
+
if (!response.ok) {
|
|
53
48
|
let error = null;
|
|
54
49
|
let body;
|
|
55
50
|
try {
|
|
56
|
-
const parsed = await
|
|
51
|
+
const parsed = await response.json();
|
|
57
52
|
if (typeof parsed?.error === "string") error = parsed.error;
|
|
58
53
|
body = parsed;
|
|
59
54
|
} catch {
|
|
60
55
|
}
|
|
61
|
-
return { ok: false, status:
|
|
56
|
+
return { ok: false, status: response.status, error, body };
|
|
62
57
|
}
|
|
63
58
|
try {
|
|
64
|
-
return { ok: true, data: await
|
|
59
|
+
return { ok: true, data: await response.json() };
|
|
65
60
|
} catch {
|
|
66
61
|
return { ok: false, status: null, error: null };
|
|
67
62
|
}
|
|
68
63
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
64
|
+
function createPortalClient(config = {}) {
|
|
65
|
+
const rawOrigin = config.apiOrigin?.trim() ?? "";
|
|
66
|
+
const normalizedOrigin = rawOrigin ? normalizePortalApiOrigin(rawOrigin) : "";
|
|
67
|
+
if (rawOrigin && !normalizedOrigin) {
|
|
68
|
+
console.error("[portal] ignoring a malformed portal API origin", rawOrigin);
|
|
69
|
+
}
|
|
70
|
+
const apiOrigin = normalizedOrigin ?? "";
|
|
71
|
+
const portalHref = config.portalUrl?.trim() ?? "";
|
|
72
|
+
const available = apiOrigin !== "" || sameOriginAvailable(config.portalOrigin);
|
|
73
|
+
const fetchImpl = config.fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
|
|
74
|
+
const url = (path) => apiOrigin && path.startsWith("/") ? apiOrigin + path : path;
|
|
75
|
+
async function request(path, init, timeoutMs = BRIDGE_TIMEOUT_MS) {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetchImpl(url(path), {
|
|
80
|
+
...PORTAL_REQUEST_INIT,
|
|
81
|
+
...init,
|
|
82
|
+
signal: controller.signal
|
|
83
|
+
});
|
|
84
|
+
return await readPortalResult(response);
|
|
85
|
+
} catch {
|
|
86
|
+
return { ok: false, status: null, error: null };
|
|
87
|
+
} finally {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
}
|
|
76
90
|
}
|
|
91
|
+
const get = (path) => request(path, { headers: { accept: "application/json" } });
|
|
92
|
+
const send = (path, method, body) => request(path, {
|
|
93
|
+
method,
|
|
94
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
95
|
+
body: JSON.stringify(body ?? {})
|
|
96
|
+
});
|
|
97
|
+
const del = (path) => request(path, { method: "DELETE", headers: { accept: "application/json" } });
|
|
98
|
+
const returnPath = () => {
|
|
99
|
+
if (typeof window === "undefined") return "/";
|
|
100
|
+
const path = `${window.location.pathname || "/"}${window.location.search}${window.location.hash}`;
|
|
101
|
+
if (!apiOrigin) return path;
|
|
102
|
+
return `${PORTAL_RETURN_PATH}?to=${encodePortalReturnTarget(window.location.origin + path)}`;
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
available,
|
|
106
|
+
apiOrigin,
|
|
107
|
+
portalHref,
|
|
108
|
+
url,
|
|
109
|
+
fetch: fetchImpl,
|
|
110
|
+
async whoAmI() {
|
|
111
|
+
if (!available) return { signedIn: false };
|
|
112
|
+
const result = await request(
|
|
113
|
+
PORTAL_WHOAMI_PATH,
|
|
114
|
+
{ headers: { accept: "application/json" } },
|
|
115
|
+
BRIDGE_PROBE_TIMEOUT_MS
|
|
116
|
+
);
|
|
117
|
+
return result.ok && result.data?.signedIn === true ? result.data : { signedIn: false };
|
|
118
|
+
},
|
|
119
|
+
async requestMagicLink(email, redirect) {
|
|
120
|
+
if (!available) return false;
|
|
121
|
+
const result = await send(PORTAL_MAGIC_LINK_PATH, "POST", {
|
|
122
|
+
email,
|
|
123
|
+
redirect: redirect ?? returnPath()
|
|
124
|
+
});
|
|
125
|
+
return result.ok && result.data?.ok === true;
|
|
126
|
+
},
|
|
127
|
+
async prefill() {
|
|
128
|
+
if (!available) return null;
|
|
129
|
+
const result = await get(PORTAL_PREFILL_PATH);
|
|
130
|
+
return result.ok ? result.data : null;
|
|
131
|
+
},
|
|
132
|
+
async mintPublicClaim() {
|
|
133
|
+
if (!available) return null;
|
|
134
|
+
const result = await send(PORTAL_PUBLIC_CLAIM_PATH, "POST", {});
|
|
135
|
+
return result.ok ? result.data : null;
|
|
136
|
+
},
|
|
137
|
+
returnPath,
|
|
138
|
+
get,
|
|
139
|
+
send,
|
|
140
|
+
delete: del
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/portal-shared.client.tsx
|
|
145
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
146
|
+
var defaultPortalClient = createPortalClient();
|
|
147
|
+
var PORTAL_SIGN_OUT_URL = "/portal/api/sign-out";
|
|
148
|
+
function portalFetch(fetchImpl) {
|
|
149
|
+
return fetchImpl ?? ((input, init) => fetch(input, init));
|
|
150
|
+
}
|
|
151
|
+
var BOUND_PORTAL_CLIENT = /* @__PURE__ */ Symbol("boundPortalClient");
|
|
152
|
+
function portalFetchForClient(client2, fetchImpl) {
|
|
153
|
+
const fetcher = portalFetch(fetchImpl);
|
|
154
|
+
const bound = (input, init) => fetcher(client2.url(input), init);
|
|
155
|
+
bound[BOUND_PORTAL_CLIENT] = client2;
|
|
156
|
+
return bound;
|
|
157
|
+
}
|
|
158
|
+
function defaultClientWith(fetchImpl) {
|
|
159
|
+
const boundClient = fetchImpl?.[BOUND_PORTAL_CLIENT];
|
|
160
|
+
if (boundClient) return boundClient;
|
|
161
|
+
if (!fetchImpl) return defaultPortalClient;
|
|
162
|
+
return createPortalClient({
|
|
163
|
+
apiOrigin: defaultPortalClient.apiOrigin,
|
|
164
|
+
portalUrl: defaultPortalClient.portalHref,
|
|
165
|
+
fetchImpl
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
async function portalGet(url, fetchImpl) {
|
|
169
|
+
return defaultClientWith(fetchImpl).get(url);
|
|
77
170
|
}
|
|
78
171
|
async function portalSend(url, method, body, fetchImpl) {
|
|
79
|
-
|
|
80
|
-
const res = await portalFetch(fetchImpl)(portalApiUrl(url), {
|
|
81
|
-
...PORTAL_REQUEST_INIT,
|
|
82
|
-
method,
|
|
83
|
-
headers: { "content-type": "application/json" },
|
|
84
|
-
body: JSON.stringify(body ?? {})
|
|
85
|
-
});
|
|
86
|
-
return await readPortalResult(res);
|
|
87
|
-
} catch {
|
|
88
|
-
return { ok: false, status: null, error: null };
|
|
89
|
-
}
|
|
172
|
+
return defaultClientWith(fetchImpl).send(url, method, body);
|
|
90
173
|
}
|
|
91
174
|
async function portalDelete(url, fetchImpl) {
|
|
92
|
-
|
|
93
|
-
const res = await portalFetch(fetchImpl)(portalApiUrl(url), {
|
|
94
|
-
...PORTAL_REQUEST_INIT,
|
|
95
|
-
method: "DELETE"
|
|
96
|
-
});
|
|
97
|
-
return await readPortalResult(res);
|
|
98
|
-
} catch {
|
|
99
|
-
return { ok: false, status: null, error: null };
|
|
100
|
-
}
|
|
175
|
+
return defaultClientWith(fetchImpl).delete(url);
|
|
101
176
|
}
|
|
102
177
|
function hasPortalIdentity(body) {
|
|
103
178
|
return typeof body?.signedIn === "boolean";
|
|
@@ -119,32 +194,10 @@ function writePortalSignedInHint(signedIn) {
|
|
|
119
194
|
} catch {
|
|
120
195
|
}
|
|
121
196
|
}
|
|
122
|
-
async function requestPortalSignInLink(email, redirect, fetchImpl) {
|
|
123
|
-
const res = await portalSend(
|
|
124
|
-
PORTAL_MAGIC_LINK_URL,
|
|
125
|
-
"POST",
|
|
126
|
-
{ email, redirect },
|
|
127
|
-
fetchImpl
|
|
128
|
-
);
|
|
129
|
-
return { ok: res.ok };
|
|
130
|
-
}
|
|
131
197
|
async function signOutOfPortal(fetchImpl) {
|
|
132
198
|
const res = await portalSend(PORTAL_SIGN_OUT_URL, "POST", {}, fetchImpl);
|
|
133
199
|
return { ok: res.ok };
|
|
134
200
|
}
|
|
135
|
-
var PORTAL_RETURN_PATH = "/portal/return";
|
|
136
|
-
function encodeReturnTarget(url) {
|
|
137
|
-
const bytes = new TextEncoder().encode(url);
|
|
138
|
-
let binary = "";
|
|
139
|
-
for (const b of bytes) binary += String.fromCharCode(b);
|
|
140
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
141
|
-
}
|
|
142
|
-
function currentPagePath() {
|
|
143
|
-
if (typeof window === "undefined") return "/";
|
|
144
|
-
const path = `${window.location.pathname || "/"}${window.location.search}${window.location.hash}`;
|
|
145
|
-
if (!apiOrigin) return path;
|
|
146
|
-
return `${PORTAL_RETURN_PATH}?to=${encodeReturnTarget(window.location.origin + path)}`;
|
|
147
|
-
}
|
|
148
201
|
var LOCALE = "en-AU";
|
|
149
202
|
function parseInstant(iso) {
|
|
150
203
|
if (!iso) return null;
|
|
@@ -1224,6 +1277,7 @@ export {
|
|
|
1224
1277
|
FlowErrorSummary,
|
|
1225
1278
|
focusField,
|
|
1226
1279
|
FlowLoading,
|
|
1280
|
+
createPortalClient,
|
|
1227
1281
|
PatientOSApiError,
|
|
1228
1282
|
patientos,
|
|
1229
1283
|
persistClaimToken,
|
|
@@ -1250,18 +1304,14 @@ export {
|
|
|
1250
1304
|
formatSlotTime,
|
|
1251
1305
|
zoneAbbr,
|
|
1252
1306
|
SlotPicker,
|
|
1253
|
-
|
|
1254
|
-
portalApiUrl,
|
|
1255
|
-
PORTAL_REQUEST_INIT,
|
|
1307
|
+
portalFetchForClient,
|
|
1256
1308
|
portalGet,
|
|
1257
1309
|
portalSend,
|
|
1258
1310
|
portalDelete,
|
|
1259
1311
|
hasPortalIdentity,
|
|
1260
1312
|
readPortalSignedInHint,
|
|
1261
1313
|
writePortalSignedInHint,
|
|
1262
|
-
requestPortalSignInLink,
|
|
1263
1314
|
signOutOfPortal,
|
|
1264
|
-
currentPagePath,
|
|
1265
1315
|
formatPortalDate,
|
|
1266
1316
|
formatPortalDateTime,
|
|
1267
1317
|
formatPortalTime,
|