@graffiti-garden/wrapper-data-guard 0.2.6 → 0.2.8
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/browser/index.js +1 -1
- package/dist/browser/index.js.map +3 -3
- package/dist/cjs/index.js +64 -8
- package/dist/cjs/index.js.map +2 -2
- package/dist/esm/index.js +67 -9
- package/dist/esm/index.js.map +2 -2
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +75 -9
package/dist/cjs/index.js
CHANGED
|
@@ -25,6 +25,7 @@ var import_api = require("@graffiti-garden/api");
|
|
|
25
25
|
var import_client = require("@graffiti-garden/wrapper-iframe-rpc/client");
|
|
26
26
|
let instance;
|
|
27
27
|
const defaultConnectionTimeout = 1e4;
|
|
28
|
+
const pendingLoginKey = "graffiti-guard-login:";
|
|
28
29
|
class GraffitiGuarded extends import_api.Graffiti {
|
|
29
30
|
sessionEvents;
|
|
30
31
|
hostUrl;
|
|
@@ -33,6 +34,9 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
33
34
|
onMessage;
|
|
34
35
|
connectionTimeout;
|
|
35
36
|
destroyed = false;
|
|
37
|
+
returnFocus = null;
|
|
38
|
+
backgroundInert = /* @__PURE__ */ new Map();
|
|
39
|
+
backgroundObserver;
|
|
36
40
|
constructor(options = {}) {
|
|
37
41
|
super();
|
|
38
42
|
if (instance) return instance;
|
|
@@ -40,6 +44,8 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
40
44
|
options.hostUrl?.toString() ?? "https://guard.graffiti.garden/",
|
|
41
45
|
document.baseURI
|
|
42
46
|
);
|
|
47
|
+
const loginKey = pendingLoginKey + hostUrl.origin;
|
|
48
|
+
const loginNonce = sessionStorage.getItem(loginKey);
|
|
43
49
|
const iframe = document.createElement("iframe");
|
|
44
50
|
iframe.title = "Graffiti Guard";
|
|
45
51
|
iframe.allow = "storage-access";
|
|
@@ -58,7 +64,7 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
58
64
|
background: "transparent",
|
|
59
65
|
border: "0",
|
|
60
66
|
display: "none",
|
|
61
|
-
//
|
|
67
|
+
// Keep the overlay within the visible viewport when browser controls move.
|
|
62
68
|
height: CSS.supports("height", "100svh") ? "100svh" : "100vh",
|
|
63
69
|
inset: "0",
|
|
64
70
|
position: "fixed",
|
|
@@ -71,7 +77,8 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
71
77
|
iframe.contentWindow?.postMessage(
|
|
72
78
|
{
|
|
73
79
|
type: "graffiti-guard:connect",
|
|
74
|
-
pageUrl: window.location.href
|
|
80
|
+
pageUrl: window.location.href,
|
|
81
|
+
...loginNonce ? { loginNonce } : {}
|
|
75
82
|
},
|
|
76
83
|
hostUrl.origin
|
|
77
84
|
);
|
|
@@ -96,6 +103,10 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
96
103
|
channel: options.channel,
|
|
97
104
|
timeout: options.timeout
|
|
98
105
|
});
|
|
106
|
+
this.sessionEvents = rpc.sessionEvents;
|
|
107
|
+
this.sessionEvents.addEventListener("initialized", () => {
|
|
108
|
+
if (sessionStorage.getItem(loginKey) === loginNonce) sessionStorage.removeItem(loginKey);
|
|
109
|
+
}, { once: true });
|
|
99
110
|
const connectionTimeout = window.setTimeout(
|
|
100
111
|
showConnectionError,
|
|
101
112
|
options.timeout ?? defaultConnectionTimeout
|
|
@@ -112,10 +123,8 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
112
123
|
if (event.data.type === "graffiti-guard:connected") {
|
|
113
124
|
return;
|
|
114
125
|
} else if (event.data.type === "graffiti-guard:set-visible" && typeof event.data.visible === "boolean") {
|
|
115
|
-
|
|
116
|
-
iframe.setAttribute("aria-hidden", String(!event.data.visible));
|
|
126
|
+
this.setGuardVisible(event.data.visible);
|
|
117
127
|
if (event.data.visible) {
|
|
118
|
-
iframe.focus();
|
|
119
128
|
remoteWindow.postMessage(
|
|
120
129
|
{ type: "graffiti-guard:shown" },
|
|
121
130
|
hostUrl.origin
|
|
@@ -139,7 +148,6 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
139
148
|
this.hostUrl = hostUrl;
|
|
140
149
|
this.iframe = iframe;
|
|
141
150
|
this.rpc = rpc;
|
|
142
|
-
this.sessionEvents = rpc.sessionEvents;
|
|
143
151
|
this.onMessage = onMessage;
|
|
144
152
|
this.connectionTimeout = connectionTimeout;
|
|
145
153
|
instance = new Proxy(this, {
|
|
@@ -153,13 +161,17 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
153
161
|
});
|
|
154
162
|
return instance;
|
|
155
163
|
}
|
|
156
|
-
login = (
|
|
164
|
+
login = (options) => {
|
|
157
165
|
const loginUrl = new URL(this.hostUrl);
|
|
158
166
|
const params = new URLSearchParams({
|
|
159
167
|
guardLogin: "1",
|
|
160
168
|
redirectUrl: window.location.href
|
|
161
169
|
});
|
|
162
|
-
if (actor) params.set("suggestedActor", actor);
|
|
170
|
+
if (options?.actor) params.set("suggestedActor", options.actor);
|
|
171
|
+
const nonce = crypto.randomUUID();
|
|
172
|
+
sessionStorage.setItem(pendingLoginKey + this.hostUrl.origin, nonce);
|
|
173
|
+
params.set("loginNonce", nonce);
|
|
174
|
+
if (options?.state !== void 0) params.set("state", options.state);
|
|
163
175
|
loginUrl.hash = params.toString();
|
|
164
176
|
window.location.assign(loginUrl.href);
|
|
165
177
|
return Promise.resolve();
|
|
@@ -175,11 +187,55 @@ class GraffitiGuarded extends import_api.Graffiti {
|
|
|
175
187
|
auditUrl.hash = params.toString();
|
|
176
188
|
return auditUrl.href;
|
|
177
189
|
}
|
|
190
|
+
setGuardVisible(visible) {
|
|
191
|
+
if (visible) {
|
|
192
|
+
if (this.iframe.style.display === "block") return;
|
|
193
|
+
const focused = document.activeElement;
|
|
194
|
+
this.returnFocus = focused instanceof HTMLElement && focused !== this.iframe ? focused : null;
|
|
195
|
+
this.inertBackground();
|
|
196
|
+
this.backgroundObserver = new MutationObserver(() => this.inertBackground());
|
|
197
|
+
this.backgroundObserver.observe(document.body, { childList: true });
|
|
198
|
+
this.iframe.style.display = "block";
|
|
199
|
+
this.iframe.setAttribute("aria-hidden", "false");
|
|
200
|
+
this.iframe.focus();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (this.iframe.style.display === "none") return;
|
|
204
|
+
this.iframe.style.display = "none";
|
|
205
|
+
this.iframe.setAttribute("aria-hidden", "true");
|
|
206
|
+
this.backgroundObserver?.disconnect();
|
|
207
|
+
this.backgroundObserver = void 0;
|
|
208
|
+
for (const [element, wasInert] of this.backgroundInert) {
|
|
209
|
+
element.inert = wasInert;
|
|
210
|
+
}
|
|
211
|
+
this.backgroundInert.clear();
|
|
212
|
+
const target = this.returnFocus;
|
|
213
|
+
this.returnFocus = null;
|
|
214
|
+
if (target?.isConnected && !target.inert && target !== document.body) {
|
|
215
|
+
target.focus({ preventScroll: true });
|
|
216
|
+
} else {
|
|
217
|
+
const previousTabindex = document.body.getAttribute("tabindex");
|
|
218
|
+
document.body.tabIndex = -1;
|
|
219
|
+
document.body.focus({ preventScroll: true });
|
|
220
|
+
if (previousTabindex === null) document.body.removeAttribute("tabindex");
|
|
221
|
+
else document.body.setAttribute("tabindex", previousTabindex);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
inertBackground() {
|
|
225
|
+
for (const child of Array.from(document.body.children)) {
|
|
226
|
+
if (child === this.iframe || !(child instanceof HTMLElement)) continue;
|
|
227
|
+
if (!this.backgroundInert.has(child)) {
|
|
228
|
+
this.backgroundInert.set(child, child.inert);
|
|
229
|
+
}
|
|
230
|
+
child.inert = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
178
233
|
destroy() {
|
|
179
234
|
if (this.destroyed) return;
|
|
180
235
|
this.destroyed = true;
|
|
181
236
|
window.clearTimeout(this.connectionTimeout);
|
|
182
237
|
window.removeEventListener("message", this.onMessage);
|
|
238
|
+
if (this.iframe.style.display === "block") this.setGuardVisible(false);
|
|
183
239
|
this.rpc.destroy();
|
|
184
240
|
this.iframe.remove();
|
|
185
241
|
if (instance === this) instance = void 0;
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: EventTarget;\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n // Prompts scroll inside this frame, so size it for visible browser controls.\n height: CSS.supports(\"height\", \"100svh\") ? \"100svh\" : \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100%\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage(\n {\n type: \"graffiti-guard:connect\",\n pageUrl: window.location.href,\n },\n hostUrl.origin,\n );\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n iframe.style.display = event.data.visible ? \"block\" : \"none\";\n iframe.setAttribute(\"aria-hidden\", String(!event.data.visible));\n if (event.data.visible) {\n iframe.focus();\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-storage-setup\" &&\n typeof event.data.url === \"string\"\n ) {\n try {\n const setupUrl = new URL(event.data.url);\n const setupParams = new URLSearchParams(setupUrl.hash.slice(1));\n if (\n setupUrl.origin !== hostUrl.origin ||\n (setupParams.get(\"guardStorageSetup\") !== \"1\" &&\n setupUrl.searchParams.get(\"guardStorageSetup\") !== \"1\")\n ) {\n return;\n }\n window.location.assign(setupUrl);\n } catch {}\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.sessionEvents = rpc.sessionEvents;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (actor) => {\n const loginUrl = new URL(this.hostUrl);\n // The guard reads this state in the browser; keep it out of the HTTP request.\n // This allows for very long redirectUrls\n const params = new URLSearchParams({\n guardLogin: \"1\",\n redirectUrl: window.location.href,\n });\n if (actor) params.set(\"suggestedActor\", actor);\n loginUrl.hash = params.toString();\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n const params = new URLSearchParams({\n redirectUrl: window.location.href,\n source: JSON.stringify(scope?.source ?? []),\n });\n if (scope?.actor) params.set(\"actor\", scope.actor);\n auditUrl.hash = params.toString();\n return auditUrl.href;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n Graffiti,\n type GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\nconst pendingLoginKey = \"graffiti-guard-login:\";\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: Graffiti[\"sessionEvents\"];\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n private returnFocus: HTMLElement | null = null;\n private readonly backgroundInert = new Map<HTMLElement, boolean>();\n private backgroundObserver: MutationObserver | undefined;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const loginKey = pendingLoginKey + hostUrl.origin;\n const loginNonce = sessionStorage.getItem(loginKey);\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n // Keep the overlay within the visible viewport when browser controls move.\n height: CSS.supports(\"height\", \"100svh\") ? \"100svh\" : \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100%\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage(\n {\n type: \"graffiti-guard:connect\",\n pageUrl: window.location.href,\n ...(loginNonce ? { loginNonce } : {}),\n },\n hostUrl.origin,\n );\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n this.sessionEvents = rpc.sessionEvents;\n this.sessionEvents.addEventListener(\"initialized\", () => {\n if (sessionStorage.getItem(loginKey) === loginNonce) sessionStorage.removeItem(loginKey);\n }, { once: true });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n this.setGuardVisible(event.data.visible);\n if (event.data.visible) {\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-storage-setup\" &&\n typeof event.data.url === \"string\"\n ) {\n try {\n const setupUrl = new URL(event.data.url);\n const setupParams = new URLSearchParams(setupUrl.hash.slice(1));\n if (\n setupUrl.origin !== hostUrl.origin ||\n (setupParams.get(\"guardStorageSetup\") !== \"1\" &&\n setupUrl.searchParams.get(\"guardStorageSetup\") !== \"1\")\n ) {\n return;\n }\n window.location.assign(setupUrl);\n } catch {}\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (options) => {\n const loginUrl = new URL(this.hostUrl);\n // The guard reads this state in the browser; keep it out of the HTTP request.\n // This allows for very long redirectUrls\n const params = new URLSearchParams({\n guardLogin: \"1\",\n redirectUrl: window.location.href,\n });\n if (options?.actor) params.set(\"suggestedActor\", options.actor);\n const nonce = crypto.randomUUID();\n sessionStorage.setItem(pendingLoginKey + this.hostUrl.origin, nonce);\n params.set(\"loginNonce\", nonce);\n if (options?.state !== undefined) params.set(\"state\", options.state);\n loginUrl.hash = params.toString();\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n const params = new URLSearchParams({\n redirectUrl: window.location.href,\n source: JSON.stringify(scope?.source ?? []),\n });\n if (scope?.actor) params.set(\"actor\", scope.actor);\n auditUrl.hash = params.toString();\n return auditUrl.href;\n }\n\n private setGuardVisible(visible: boolean) {\n if (visible) {\n if (this.iframe.style.display === \"block\") return;\n const focused = document.activeElement;\n this.returnFocus =\n focused instanceof HTMLElement && focused !== this.iframe\n ? focused\n : null;\n this.inertBackground();\n this.backgroundObserver = new MutationObserver(() => this.inertBackground());\n this.backgroundObserver.observe(document.body, { childList: true });\n this.iframe.style.display = \"block\";\n this.iframe.setAttribute(\"aria-hidden\", \"false\");\n this.iframe.focus();\n return;\n }\n\n if (this.iframe.style.display === \"none\") return;\n\n this.iframe.style.display = \"none\";\n this.iframe.setAttribute(\"aria-hidden\", \"true\");\n this.backgroundObserver?.disconnect();\n this.backgroundObserver = undefined;\n for (const [element, wasInert] of this.backgroundInert) {\n element.inert = wasInert;\n }\n this.backgroundInert.clear();\n const target = this.returnFocus;\n this.returnFocus = null;\n if (target?.isConnected && !target.inert && target !== document.body) {\n target.focus({ preventScroll: true });\n } else {\n const previousTabindex = document.body.getAttribute(\"tabindex\");\n document.body.tabIndex = -1;\n document.body.focus({ preventScroll: true });\n if (previousTabindex === null) document.body.removeAttribute(\"tabindex\");\n else document.body.setAttribute(\"tabindex\", previousTabindex);\n }\n }\n\n private inertBackground() {\n for (const child of Array.from(document.body.children)) {\n if (child === this.iframe || !(child instanceof HTMLElement)) continue;\n if (!this.backgroundInert.has(child)) {\n this.backgroundInert.set(child, child.inert);\n }\n child.inert = true;\n }\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n if (this.iframe.style.display === \"block\") this.setGuardVisible(false);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/esm/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Graffiti
|
|
3
|
+
} from "@graffiti-garden/api";
|
|
2
4
|
import { GraffitiRpcClient } from "@graffiti-garden/wrapper-iframe-rpc/client";
|
|
3
5
|
let instance;
|
|
4
6
|
const defaultConnectionTimeout = 1e4;
|
|
7
|
+
const pendingLoginKey = "graffiti-guard-login:";
|
|
5
8
|
class GraffitiGuarded extends Graffiti {
|
|
6
9
|
sessionEvents;
|
|
7
10
|
hostUrl;
|
|
@@ -10,6 +13,9 @@ class GraffitiGuarded extends Graffiti {
|
|
|
10
13
|
onMessage;
|
|
11
14
|
connectionTimeout;
|
|
12
15
|
destroyed = false;
|
|
16
|
+
returnFocus = null;
|
|
17
|
+
backgroundInert = /* @__PURE__ */ new Map();
|
|
18
|
+
backgroundObserver;
|
|
13
19
|
constructor(options = {}) {
|
|
14
20
|
super();
|
|
15
21
|
if (instance) return instance;
|
|
@@ -17,6 +23,8 @@ class GraffitiGuarded extends Graffiti {
|
|
|
17
23
|
options.hostUrl?.toString() ?? "https://guard.graffiti.garden/",
|
|
18
24
|
document.baseURI
|
|
19
25
|
);
|
|
26
|
+
const loginKey = pendingLoginKey + hostUrl.origin;
|
|
27
|
+
const loginNonce = sessionStorage.getItem(loginKey);
|
|
20
28
|
const iframe = document.createElement("iframe");
|
|
21
29
|
iframe.title = "Graffiti Guard";
|
|
22
30
|
iframe.allow = "storage-access";
|
|
@@ -35,7 +43,7 @@ class GraffitiGuarded extends Graffiti {
|
|
|
35
43
|
background: "transparent",
|
|
36
44
|
border: "0",
|
|
37
45
|
display: "none",
|
|
38
|
-
//
|
|
46
|
+
// Keep the overlay within the visible viewport when browser controls move.
|
|
39
47
|
height: CSS.supports("height", "100svh") ? "100svh" : "100vh",
|
|
40
48
|
inset: "0",
|
|
41
49
|
position: "fixed",
|
|
@@ -48,7 +56,8 @@ class GraffitiGuarded extends Graffiti {
|
|
|
48
56
|
iframe.contentWindow?.postMessage(
|
|
49
57
|
{
|
|
50
58
|
type: "graffiti-guard:connect",
|
|
51
|
-
pageUrl: window.location.href
|
|
59
|
+
pageUrl: window.location.href,
|
|
60
|
+
...loginNonce ? { loginNonce } : {}
|
|
52
61
|
},
|
|
53
62
|
hostUrl.origin
|
|
54
63
|
);
|
|
@@ -73,6 +82,10 @@ class GraffitiGuarded extends Graffiti {
|
|
|
73
82
|
channel: options.channel,
|
|
74
83
|
timeout: options.timeout
|
|
75
84
|
});
|
|
85
|
+
this.sessionEvents = rpc.sessionEvents;
|
|
86
|
+
this.sessionEvents.addEventListener("initialized", () => {
|
|
87
|
+
if (sessionStorage.getItem(loginKey) === loginNonce) sessionStorage.removeItem(loginKey);
|
|
88
|
+
}, { once: true });
|
|
76
89
|
const connectionTimeout = window.setTimeout(
|
|
77
90
|
showConnectionError,
|
|
78
91
|
options.timeout ?? defaultConnectionTimeout
|
|
@@ -89,10 +102,8 @@ class GraffitiGuarded extends Graffiti {
|
|
|
89
102
|
if (event.data.type === "graffiti-guard:connected") {
|
|
90
103
|
return;
|
|
91
104
|
} else if (event.data.type === "graffiti-guard:set-visible" && typeof event.data.visible === "boolean") {
|
|
92
|
-
|
|
93
|
-
iframe.setAttribute("aria-hidden", String(!event.data.visible));
|
|
105
|
+
this.setGuardVisible(event.data.visible);
|
|
94
106
|
if (event.data.visible) {
|
|
95
|
-
iframe.focus();
|
|
96
107
|
remoteWindow.postMessage(
|
|
97
108
|
{ type: "graffiti-guard:shown" },
|
|
98
109
|
hostUrl.origin
|
|
@@ -116,7 +127,6 @@ class GraffitiGuarded extends Graffiti {
|
|
|
116
127
|
this.hostUrl = hostUrl;
|
|
117
128
|
this.iframe = iframe;
|
|
118
129
|
this.rpc = rpc;
|
|
119
|
-
this.sessionEvents = rpc.sessionEvents;
|
|
120
130
|
this.onMessage = onMessage;
|
|
121
131
|
this.connectionTimeout = connectionTimeout;
|
|
122
132
|
instance = new Proxy(this, {
|
|
@@ -130,13 +140,17 @@ class GraffitiGuarded extends Graffiti {
|
|
|
130
140
|
});
|
|
131
141
|
return instance;
|
|
132
142
|
}
|
|
133
|
-
login = (
|
|
143
|
+
login = (options) => {
|
|
134
144
|
const loginUrl = new URL(this.hostUrl);
|
|
135
145
|
const params = new URLSearchParams({
|
|
136
146
|
guardLogin: "1",
|
|
137
147
|
redirectUrl: window.location.href
|
|
138
148
|
});
|
|
139
|
-
if (actor) params.set("suggestedActor", actor);
|
|
149
|
+
if (options?.actor) params.set("suggestedActor", options.actor);
|
|
150
|
+
const nonce = crypto.randomUUID();
|
|
151
|
+
sessionStorage.setItem(pendingLoginKey + this.hostUrl.origin, nonce);
|
|
152
|
+
params.set("loginNonce", nonce);
|
|
153
|
+
if (options?.state !== void 0) params.set("state", options.state);
|
|
140
154
|
loginUrl.hash = params.toString();
|
|
141
155
|
window.location.assign(loginUrl.href);
|
|
142
156
|
return Promise.resolve();
|
|
@@ -152,11 +166,55 @@ class GraffitiGuarded extends Graffiti {
|
|
|
152
166
|
auditUrl.hash = params.toString();
|
|
153
167
|
return auditUrl.href;
|
|
154
168
|
}
|
|
169
|
+
setGuardVisible(visible) {
|
|
170
|
+
if (visible) {
|
|
171
|
+
if (this.iframe.style.display === "block") return;
|
|
172
|
+
const focused = document.activeElement;
|
|
173
|
+
this.returnFocus = focused instanceof HTMLElement && focused !== this.iframe ? focused : null;
|
|
174
|
+
this.inertBackground();
|
|
175
|
+
this.backgroundObserver = new MutationObserver(() => this.inertBackground());
|
|
176
|
+
this.backgroundObserver.observe(document.body, { childList: true });
|
|
177
|
+
this.iframe.style.display = "block";
|
|
178
|
+
this.iframe.setAttribute("aria-hidden", "false");
|
|
179
|
+
this.iframe.focus();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (this.iframe.style.display === "none") return;
|
|
183
|
+
this.iframe.style.display = "none";
|
|
184
|
+
this.iframe.setAttribute("aria-hidden", "true");
|
|
185
|
+
this.backgroundObserver?.disconnect();
|
|
186
|
+
this.backgroundObserver = void 0;
|
|
187
|
+
for (const [element, wasInert] of this.backgroundInert) {
|
|
188
|
+
element.inert = wasInert;
|
|
189
|
+
}
|
|
190
|
+
this.backgroundInert.clear();
|
|
191
|
+
const target = this.returnFocus;
|
|
192
|
+
this.returnFocus = null;
|
|
193
|
+
if (target?.isConnected && !target.inert && target !== document.body) {
|
|
194
|
+
target.focus({ preventScroll: true });
|
|
195
|
+
} else {
|
|
196
|
+
const previousTabindex = document.body.getAttribute("tabindex");
|
|
197
|
+
document.body.tabIndex = -1;
|
|
198
|
+
document.body.focus({ preventScroll: true });
|
|
199
|
+
if (previousTabindex === null) document.body.removeAttribute("tabindex");
|
|
200
|
+
else document.body.setAttribute("tabindex", previousTabindex);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
inertBackground() {
|
|
204
|
+
for (const child of Array.from(document.body.children)) {
|
|
205
|
+
if (child === this.iframe || !(child instanceof HTMLElement)) continue;
|
|
206
|
+
if (!this.backgroundInert.has(child)) {
|
|
207
|
+
this.backgroundInert.set(child, child.inert);
|
|
208
|
+
}
|
|
209
|
+
child.inert = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
155
212
|
destroy() {
|
|
156
213
|
if (this.destroyed) return;
|
|
157
214
|
this.destroyed = true;
|
|
158
215
|
window.clearTimeout(this.connectionTimeout);
|
|
159
216
|
window.removeEventListener("message", this.onMessage);
|
|
217
|
+
if (this.iframe.style.display === "block") this.setGuardVisible(false);
|
|
160
218
|
this.rpc.destroy();
|
|
161
219
|
this.iframe.remove();
|
|
162
220
|
if (instance === this) instance = void 0;
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: EventTarget;\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n // Prompts scroll inside this frame, so size it for visible browser controls.\n height: CSS.supports(\"height\", \"100svh\") ? \"100svh\" : \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100%\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage(\n {\n type: \"graffiti-guard:connect\",\n pageUrl: window.location.href,\n },\n hostUrl.origin,\n );\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n iframe.style.display = event.data.visible ? \"block\" : \"none\";\n iframe.setAttribute(\"aria-hidden\", String(!event.data.visible));\n if (event.data.visible) {\n iframe.focus();\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-storage-setup\" &&\n typeof event.data.url === \"string\"\n ) {\n try {\n const setupUrl = new URL(event.data.url);\n const setupParams = new URLSearchParams(setupUrl.hash.slice(1));\n if (\n setupUrl.origin !== hostUrl.origin ||\n (setupParams.get(\"guardStorageSetup\") !== \"1\" &&\n setupUrl.searchParams.get(\"guardStorageSetup\") !== \"1\")\n ) {\n return;\n }\n window.location.assign(setupUrl);\n } catch {}\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.sessionEvents = rpc.sessionEvents;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (actor) => {\n const loginUrl = new URL(this.hostUrl);\n // The guard reads this state in the browser; keep it out of the HTTP request.\n // This allows for very long redirectUrls\n const params = new URLSearchParams({\n guardLogin: \"1\",\n redirectUrl: window.location.href,\n });\n if (actor) params.set(\"suggestedActor\", actor);\n loginUrl.hash = params.toString();\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n const params = new URLSearchParams({\n redirectUrl: window.location.href,\n source: JSON.stringify(scope?.source ?? []),\n });\n if (scope?.actor) params.set(\"actor\", scope.actor);\n auditUrl.hash = params.toString();\n return auditUrl.href;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n Graffiti,\n type GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\nconst pendingLoginKey = \"graffiti-guard-login:\";\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: Graffiti[\"sessionEvents\"];\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n private returnFocus: HTMLElement | null = null;\n private readonly backgroundInert = new Map<HTMLElement, boolean>();\n private backgroundObserver: MutationObserver | undefined;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const loginKey = pendingLoginKey + hostUrl.origin;\n const loginNonce = sessionStorage.getItem(loginKey);\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n // Keep the overlay within the visible viewport when browser controls move.\n height: CSS.supports(\"height\", \"100svh\") ? \"100svh\" : \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100%\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage(\n {\n type: \"graffiti-guard:connect\",\n pageUrl: window.location.href,\n ...(loginNonce ? { loginNonce } : {}),\n },\n hostUrl.origin,\n );\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n this.sessionEvents = rpc.sessionEvents;\n this.sessionEvents.addEventListener(\"initialized\", () => {\n if (sessionStorage.getItem(loginKey) === loginNonce) sessionStorage.removeItem(loginKey);\n }, { once: true });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n this.setGuardVisible(event.data.visible);\n if (event.data.visible) {\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-storage-setup\" &&\n typeof event.data.url === \"string\"\n ) {\n try {\n const setupUrl = new URL(event.data.url);\n const setupParams = new URLSearchParams(setupUrl.hash.slice(1));\n if (\n setupUrl.origin !== hostUrl.origin ||\n (setupParams.get(\"guardStorageSetup\") !== \"1\" &&\n setupUrl.searchParams.get(\"guardStorageSetup\") !== \"1\")\n ) {\n return;\n }\n window.location.assign(setupUrl);\n } catch {}\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (options) => {\n const loginUrl = new URL(this.hostUrl);\n // The guard reads this state in the browser; keep it out of the HTTP request.\n // This allows for very long redirectUrls\n const params = new URLSearchParams({\n guardLogin: \"1\",\n redirectUrl: window.location.href,\n });\n if (options?.actor) params.set(\"suggestedActor\", options.actor);\n const nonce = crypto.randomUUID();\n sessionStorage.setItem(pendingLoginKey + this.hostUrl.origin, nonce);\n params.set(\"loginNonce\", nonce);\n if (options?.state !== undefined) params.set(\"state\", options.state);\n loginUrl.hash = params.toString();\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n const params = new URLSearchParams({\n redirectUrl: window.location.href,\n source: JSON.stringify(scope?.source ?? []),\n });\n if (scope?.actor) params.set(\"actor\", scope.actor);\n auditUrl.hash = params.toString();\n return auditUrl.href;\n }\n\n private setGuardVisible(visible: boolean) {\n if (visible) {\n if (this.iframe.style.display === \"block\") return;\n const focused = document.activeElement;\n this.returnFocus =\n focused instanceof HTMLElement && focused !== this.iframe\n ? focused\n : null;\n this.inertBackground();\n this.backgroundObserver = new MutationObserver(() => this.inertBackground());\n this.backgroundObserver.observe(document.body, { childList: true });\n this.iframe.style.display = \"block\";\n this.iframe.setAttribute(\"aria-hidden\", \"false\");\n this.iframe.focus();\n return;\n }\n\n if (this.iframe.style.display === \"none\") return;\n\n this.iframe.style.display = \"none\";\n this.iframe.setAttribute(\"aria-hidden\", \"true\");\n this.backgroundObserver?.disconnect();\n this.backgroundObserver = undefined;\n for (const [element, wasInert] of this.backgroundInert) {\n element.inert = wasInert;\n }\n this.backgroundInert.clear();\n const target = this.returnFocus;\n this.returnFocus = null;\n if (target?.isConnected && !target.inert && target !== document.body) {\n target.focus({ preventScroll: true });\n } else {\n const previousTabindex = document.body.getAttribute(\"tabindex\");\n document.body.tabIndex = -1;\n document.body.focus({ preventScroll: true });\n if (previousTabindex === null) document.body.removeAttribute(\"tabindex\");\n else document.body.setAttribute(\"tabindex\", previousTabindex);\n }\n }\n\n private inertBackground() {\n for (const child of Array.from(document.body.children)) {\n if (child === this.iframe || !(child instanceof HTMLElement)) continue;\n if (!this.backgroundInert.has(child)) {\n this.backgroundInert.set(child, child.inert);\n }\n child.inert = true;\n }\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n if (this.iframe.style.display === \"block\") this.setGuardVisible(false);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,13 +13,16 @@ export type GraffitiGuardSession = GraffitiSession & {
|
|
|
13
13
|
};
|
|
14
14
|
/** A Graffiti implementation backed by a sandboxed guard iframe. */
|
|
15
15
|
export declare class GraffitiGuarded extends Graffiti {
|
|
16
|
-
readonly sessionEvents:
|
|
16
|
+
readonly sessionEvents: Graffiti["sessionEvents"];
|
|
17
17
|
private readonly hostUrl;
|
|
18
18
|
private readonly iframe;
|
|
19
19
|
private readonly rpc;
|
|
20
20
|
private readonly onMessage;
|
|
21
21
|
private readonly connectionTimeout;
|
|
22
22
|
private destroyed;
|
|
23
|
+
private returnFocus;
|
|
24
|
+
private readonly backgroundInert;
|
|
25
|
+
private backgroundObserver;
|
|
23
26
|
constructor(options?: GraffitiGuardedOptions);
|
|
24
27
|
login: Graffiti["login"];
|
|
25
28
|
/** Link to the host-owned audit panel without exposing audit data to this app. */
|
|
@@ -27,6 +30,8 @@ export declare class GraffitiGuarded extends Graffiti {
|
|
|
27
30
|
actor?: string;
|
|
28
31
|
source?: GraffitiGuardSourceSegment[];
|
|
29
32
|
}): string;
|
|
33
|
+
private setGuardVisible;
|
|
34
|
+
private inertBackground;
|
|
30
35
|
destroy(): void;
|
|
31
36
|
}
|
|
32
37
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAS9B,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG;IACnD,MAAM,CAAC,EAAE,0BAA0B,EAAE,CAAC;CACvC,CAAC;AAEF,oEAAoE;AAEpE,qBAAa,eAAgB,SAAQ,QAAQ;IAC3C,QAAQ,CAAC,aAAa,EAAG,QAAQ,CAAC,eAAe,CAAC,CAAC;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAO;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmC;IACnE,OAAO,CAAC,kBAAkB,CAA+B;IAEzD,YAAY,OAAO,GAAE,sBAA2B,EAwJ/C;IAED,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAgBtB;IAEF,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,0BAA0B,EAAE,CAAC;KACvC,UASA;IAED,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,eAAe;IAUvB,OAAO,SASN;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graffiti-garden/wrapper-data-guard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Ask for permission before a site accesses Graffiti",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"url": "https://github.com/graffiti-garden/graffiti/issues"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@graffiti-garden/api": "^1.
|
|
47
|
-
"@graffiti-garden/wrapper-iframe-rpc": "^0.
|
|
46
|
+
"@graffiti-garden/api": "^1.4.0",
|
|
47
|
+
"@graffiti-garden/wrapper-iframe-rpc": "^0.2.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^26.6.2",
|