@patientos/website-kit 0.2.0 → 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-TMW4R76E.js → chunk-BHVDY6SC.js} +147 -91
- package/dist/{chunk-RUCVZ2C3.js → chunk-N6IGS47W.js} +1 -1
- package/dist/{chunk-CRCCBM4F.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
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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;
|
|
31
|
+
}
|
|
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(/=+$/, "");
|
|
37
|
+
}
|
|
38
|
+
function sameOriginAvailable(portalOrigin) {
|
|
39
|
+
if (typeof window === "undefined") return false;
|
|
40
|
+
if (portalOrigin == null) {
|
|
41
|
+
return true;
|
|
29
42
|
}
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
41
|
-
}
|
|
42
|
-
function portalApiUrl(path) {
|
|
43
|
-
return apiOrigin && path.startsWith("/") ? apiOrigin + path : path;
|
|
43
|
+
const normalizedOrigin = portalOrigin.trim().replace(/\/+$/, "");
|
|
44
|
+
return normalizedOrigin !== "" && window.location.origin === normalizedOrigin;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
var PORTAL_REQUEST_INIT = { credentials: "include", cache: "no-store" };
|
|
48
|
-
function portalFetch(fetchImpl) {
|
|
49
|
-
return fetchImpl ?? ((input, init) => fetch(input, init));
|
|
50
|
-
}
|
|
51
|
-
async function readPortalResult(res) {
|
|
52
|
-
if (!res.ok) {
|
|
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,26 +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 currentPagePath() {
|
|
137
|
-
if (typeof window === "undefined") return "/";
|
|
138
|
-
const path = `${window.location.pathname || "/"}${window.location.search}`;
|
|
139
|
-
if (!apiOrigin) return path;
|
|
140
|
-
return `${PORTAL_RETURN_PATH}?to=${encodeURIComponent(window.location.origin + path)}`;
|
|
141
|
-
}
|
|
142
201
|
var LOCALE = "en-AU";
|
|
143
202
|
function parseInstant(iso) {
|
|
144
203
|
if (!iso) return null;
|
|
@@ -1218,6 +1277,7 @@ export {
|
|
|
1218
1277
|
FlowErrorSummary,
|
|
1219
1278
|
focusField,
|
|
1220
1279
|
FlowLoading,
|
|
1280
|
+
createPortalClient,
|
|
1221
1281
|
PatientOSApiError,
|
|
1222
1282
|
patientos,
|
|
1223
1283
|
persistClaimToken,
|
|
@@ -1244,18 +1304,14 @@ export {
|
|
|
1244
1304
|
formatSlotTime,
|
|
1245
1305
|
zoneAbbr,
|
|
1246
1306
|
SlotPicker,
|
|
1247
|
-
|
|
1248
|
-
portalApiUrl,
|
|
1249
|
-
PORTAL_REQUEST_INIT,
|
|
1307
|
+
portalFetchForClient,
|
|
1250
1308
|
portalGet,
|
|
1251
1309
|
portalSend,
|
|
1252
1310
|
portalDelete,
|
|
1253
1311
|
hasPortalIdentity,
|
|
1254
1312
|
readPortalSignedInHint,
|
|
1255
1313
|
writePortalSignedInHint,
|
|
1256
|
-
requestPortalSignInLink,
|
|
1257
1314
|
signOutOfPortal,
|
|
1258
|
-
currentPagePath,
|
|
1259
1315
|
formatPortalDate,
|
|
1260
1316
|
formatPortalDateTime,
|
|
1261
1317
|
formatPortalTime,
|