@opendatalabs/vana-sdk 4.3.2 → 4.4.0-connect-sheet.0-pr.218.295a8da
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/README.md +131 -0
- package/dist/connect/account-origin.cjs +72 -0
- package/dist/connect/account-origin.cjs.map +1 -0
- package/dist/connect/account-origin.d.ts +92 -0
- package/dist/connect/account-origin.js +47 -0
- package/dist/connect/account-origin.js.map +1 -0
- package/dist/connect/account-origin.test.d.ts +1 -0
- package/dist/connect/chrome.cjs +221 -0
- package/dist/connect/chrome.cjs.map +1 -0
- package/dist/connect/chrome.d.ts +40 -0
- package/dist/connect/chrome.js +193 -0
- package/dist/connect/chrome.js.map +1 -0
- package/dist/connect/http-transports.cjs +100 -0
- package/dist/connect/http-transports.cjs.map +1 -0
- package/dist/connect/http-transports.d.ts +77 -0
- package/dist/connect/http-transports.js +75 -0
- package/dist/connect/http-transports.js.map +1 -0
- package/dist/connect/http-transports.test.d.ts +1 -0
- package/dist/connect/launcher.cjs +224 -0
- package/dist/connect/launcher.cjs.map +1 -0
- package/dist/connect/launcher.d.ts +122 -0
- package/dist/connect/launcher.js +210 -0
- package/dist/connect/launcher.js.map +1 -0
- package/dist/connect/launcher.test.d.ts +1 -0
- package/dist/connect/protocol.cjs +244 -0
- package/dist/connect/protocol.cjs.map +1 -0
- package/dist/connect/protocol.d.ts +307 -0
- package/dist/connect/protocol.js +203 -0
- package/dist/connect/protocol.js.map +1 -0
- package/dist/connect/protocol.test.d.ts +1 -0
- package/dist/connect.cjs +55 -0
- package/dist/connect.cjs.map +1 -0
- package/dist/connect.d.ts +35 -0
- package/dist/connect.js +34 -0
- package/dist/connect.js.map +1 -0
- package/dist/direct/connect-flow-sheet.test.d.ts +1 -0
- package/dist/direct/connect-flow.cjs +211 -1
- package/dist/direct/connect-flow.cjs.map +1 -1
- package/dist/direct/connect-flow.d.ts +75 -0
- package/dist/direct/connect-flow.js +213 -1
- package/dist/direct/connect-flow.js.map +1 -1
- package/dist/direct/connect-handler.cjs +493 -0
- package/dist/direct/connect-handler.cjs.map +1 -0
- package/dist/direct/connect-handler.d.ts +241 -0
- package/dist/direct/connect-handler.js +476 -0
- package/dist/direct/connect-handler.js.map +1 -0
- package/dist/direct/connect-handler.test.d.ts +1 -0
- package/dist/direct/controller.cjs +58 -3
- package/dist/direct/controller.cjs.map +1 -1
- package/dist/direct/controller.d.ts +79 -3
- package/dist/direct/controller.js +54 -2
- package/dist/direct/controller.js.map +1 -1
- package/dist/direct/endpoints.cjs +5 -2
- package/dist/direct/endpoints.cjs.map +1 -1
- package/dist/direct/endpoints.js +5 -2
- package/dist/direct/endpoints.js.map +1 -1
- package/dist/direct/request-authorization.cjs +65 -0
- package/dist/direct/request-authorization.cjs.map +1 -0
- package/dist/direct/request-authorization.d.ts +65 -0
- package/dist/direct/request-authorization.js +39 -0
- package/dist/direct/request-authorization.js.map +1 -0
- package/dist/direct/request-authorization.test.d.ts +1 -0
- package/dist/direct/types.cjs.map +1 -1
- package/dist/direct/types.d.ts +6 -0
- package/dist/direct/types.js.map +1 -1
- package/dist/direct/use-direct-vana-connect.cjs +18 -1
- package/dist/direct/use-direct-vana-connect.cjs.map +1 -1
- package/dist/direct/use-direct-vana-connect.js +18 -1
- package/dist/direct/use-direct-vana-connect.js.map +1 -1
- package/dist/direct/use-vana-connect.cjs +84 -0
- package/dist/direct/use-vana-connect.cjs.map +1 -0
- package/dist/direct/use-vana-connect.d.ts +59 -0
- package/dist/direct/use-vana-connect.js +64 -0
- package/dist/direct/use-vana-connect.js.map +1 -0
- package/dist/direct/use-vana-connect.test.d.ts +1 -0
- package/dist/react.cjs +10 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.ts +4 -1
- package/dist/react.js +11 -1
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +44 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.ts +6 -2
- package/dist/server.js +49 -3
- package/dist/server.js.map +1 -1
- package/package.json +11 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sheet chrome for the Vana connect sheet: scrim, bottom sheet and drag
|
|
3
|
+
* handle, in a Shadow DOM root.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Ported unchanged from `@vana-unity/connect-embed`. See the layout notes
|
|
7
|
+
* below.
|
|
8
|
+
*
|
|
9
|
+
* @category Connect
|
|
10
|
+
* @module connect/chrome
|
|
11
|
+
*/
|
|
12
|
+
/** Drag the handle this far down to dismiss. */
|
|
13
|
+
export declare const DRAG_CLOSE_PX = 120;
|
|
14
|
+
/** The sheet never covers more of the viewport than this. */
|
|
15
|
+
export declare const MAX_SHEET_RATIO = 0.92;
|
|
16
|
+
export type CloseReason = "drag" | "scrim";
|
|
17
|
+
export interface SheetChrome {
|
|
18
|
+
host: HTMLElement;
|
|
19
|
+
iframe: HTMLIFrameElement;
|
|
20
|
+
remove(): void;
|
|
21
|
+
setContentHeight(px: number): void;
|
|
22
|
+
show(): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Where the sheet's top edge goes so `content` px of the frame show, capped
|
|
26
|
+
* at {@link MAX_SHEET_RATIO} of the viewport.
|
|
27
|
+
*
|
|
28
|
+
* @param viewport - Viewport height in px.
|
|
29
|
+
* @param content - Content height the sheet reported, in px.
|
|
30
|
+
* @returns The sheet's `top`, in whole px.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sheetTopPx(viewport: number, content: number): number;
|
|
33
|
+
/** Scrim taps this soon after the page returns to view are ignored. */
|
|
34
|
+
export declare const RETURN_TAP_GUARD_MS = 500;
|
|
35
|
+
export declare function createSheetChrome(input: {
|
|
36
|
+
ariaLabel: string;
|
|
37
|
+
container: HTMLElement;
|
|
38
|
+
onClose: (reason: CloseReason) => void;
|
|
39
|
+
src: string;
|
|
40
|
+
}): SheetChrome;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const SHEET_RADIUS_PX = 28;
|
|
2
|
+
const SHEET_MAX_WIDTH_PX = 480;
|
|
3
|
+
const HANDLE_STRIP_PX = 20;
|
|
4
|
+
const INITIAL_CONTENT_PX = 320;
|
|
5
|
+
const TRANSITION_MS = 280;
|
|
6
|
+
const SHEET_BACKGROUND = "#f4f4f4";
|
|
7
|
+
const DRAG_CLOSE_PX = 120;
|
|
8
|
+
const MAX_SHEET_RATIO = 0.92;
|
|
9
|
+
const STYLES = `
|
|
10
|
+
:host { all: initial; }
|
|
11
|
+
.scrim {
|
|
12
|
+
position: fixed; inset: 0; z-index: 2147483646;
|
|
13
|
+
background: rgb(0 0 0 / 0.4);
|
|
14
|
+
opacity: 0; transition: opacity ${TRANSITION_MS}ms ease;
|
|
15
|
+
}
|
|
16
|
+
.sheet {
|
|
17
|
+
position: fixed; left: 0; right: 0; z-index: 2147483647;
|
|
18
|
+
margin: 0 auto; max-width: ${SHEET_MAX_WIDTH_PX}px;
|
|
19
|
+
top: 100vh;
|
|
20
|
+
transition: top ${TRANSITION_MS}ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
21
|
+
}
|
|
22
|
+
.sheet.dragging { transition: none; }
|
|
23
|
+
.handle {
|
|
24
|
+
height: ${HANDLE_STRIP_PX}px;
|
|
25
|
+
display: flex; justify-content: center; align-items: center;
|
|
26
|
+
background: ${SHEET_BACKGROUND};
|
|
27
|
+
border-radius: ${SHEET_RADIUS_PX}px ${SHEET_RADIUS_PX}px 0 0;
|
|
28
|
+
touch-action: none; cursor: grab;
|
|
29
|
+
}
|
|
30
|
+
.handle::before {
|
|
31
|
+
content: ""; width: 36px; height: 5px; border-radius: 3px;
|
|
32
|
+
background: rgb(0 0 0 / 0.18);
|
|
33
|
+
}
|
|
34
|
+
iframe {
|
|
35
|
+
display: block; width: 100%; border: 0;
|
|
36
|
+
background: ${SHEET_BACKGROUND};
|
|
37
|
+
}
|
|
38
|
+
.open .scrim { opacity: 1; }
|
|
39
|
+
@media (prefers-reduced-motion: reduce) {
|
|
40
|
+
.scrim, .sheet { transition: none; }
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
function viewportPx() {
|
|
44
|
+
return window.visualViewport?.height ?? window.innerHeight;
|
|
45
|
+
}
|
|
46
|
+
function capPx() {
|
|
47
|
+
return Math.round(viewportPx() * MAX_SHEET_RATIO);
|
|
48
|
+
}
|
|
49
|
+
function placeSheet(sheet, content) {
|
|
50
|
+
sheet.style.top = `${sheetTopPx(viewportPx(), content)}px`;
|
|
51
|
+
}
|
|
52
|
+
function sheetTopPx(viewport, content) {
|
|
53
|
+
const cap = Math.round(viewport * MAX_SHEET_RATIO);
|
|
54
|
+
const visible = Math.min(content, cap - HANDLE_STRIP_PX);
|
|
55
|
+
return Math.round(viewport - HANDLE_STRIP_PX - visible);
|
|
56
|
+
}
|
|
57
|
+
const RETURN_TAP_GUARD_MS = 500;
|
|
58
|
+
function installReturnGuard(sheet, iframe, state) {
|
|
59
|
+
const fitIframe = () => {
|
|
60
|
+
iframe.style.height = `${capPx() - HANDLE_STRIP_PX}px`;
|
|
61
|
+
if (state.shown) {
|
|
62
|
+
placeSheet(sheet, state.lastHeight ?? INITIAL_CONTENT_PX);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const onReturn = () => {
|
|
66
|
+
if (document.visibilityState !== "visible") {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
state.returnedAt = performance.now();
|
|
70
|
+
fitIframe();
|
|
71
|
+
};
|
|
72
|
+
fitIframe();
|
|
73
|
+
window.addEventListener("resize", fitIframe);
|
|
74
|
+
window.visualViewport?.addEventListener("resize", fitIframe);
|
|
75
|
+
document.addEventListener("visibilitychange", onReturn);
|
|
76
|
+
window.addEventListener("focus", onReturn);
|
|
77
|
+
return () => {
|
|
78
|
+
window.removeEventListener("resize", fitIframe);
|
|
79
|
+
window.visualViewport?.removeEventListener("resize", fitIframe);
|
|
80
|
+
document.removeEventListener("visibilitychange", onReturn);
|
|
81
|
+
window.removeEventListener("focus", onReturn);
|
|
82
|
+
sheet.style.transform = "";
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function installDragToClose(sheet, handle, onClose) {
|
|
86
|
+
let startY = null;
|
|
87
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
88
|
+
startY = event.clientY;
|
|
89
|
+
handle.setPointerCapture?.(event.pointerId);
|
|
90
|
+
sheet.classList.add("dragging");
|
|
91
|
+
});
|
|
92
|
+
handle.addEventListener("pointermove", (event) => {
|
|
93
|
+
if (startY === null) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const dy = Math.max(0, event.clientY - startY);
|
|
97
|
+
sheet.style.transform = `translateY(${dy}px)`;
|
|
98
|
+
});
|
|
99
|
+
const end = (event) => {
|
|
100
|
+
if (startY === null) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const dy = event.clientY - startY;
|
|
104
|
+
startY = null;
|
|
105
|
+
sheet.classList.remove("dragging");
|
|
106
|
+
sheet.style.transform = "";
|
|
107
|
+
if (dy > DRAG_CLOSE_PX) {
|
|
108
|
+
onClose("drag");
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
handle.addEventListener("pointerup", end);
|
|
112
|
+
handle.addEventListener("pointercancel", end);
|
|
113
|
+
}
|
|
114
|
+
function createSheetChrome(input) {
|
|
115
|
+
const returnState = {
|
|
116
|
+
lastHeight: null,
|
|
117
|
+
returnedAt: 0,
|
|
118
|
+
shown: false
|
|
119
|
+
};
|
|
120
|
+
const host = document.createElement("div");
|
|
121
|
+
host.setAttribute("data-vana-connect", "");
|
|
122
|
+
const root = host.attachShadow({ mode: "open" });
|
|
123
|
+
const style = document.createElement("style");
|
|
124
|
+
style.textContent = STYLES;
|
|
125
|
+
const frame = document.createElement("div");
|
|
126
|
+
const scrim = document.createElement("div");
|
|
127
|
+
scrim.className = "scrim";
|
|
128
|
+
scrim.setAttribute("data-slot", "scrim");
|
|
129
|
+
scrim.addEventListener("click", () => {
|
|
130
|
+
if (performance.now() - returnState.returnedAt < RETURN_TAP_GUARD_MS) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
input.onClose("scrim");
|
|
134
|
+
});
|
|
135
|
+
const sheet = document.createElement("div");
|
|
136
|
+
sheet.className = "sheet";
|
|
137
|
+
sheet.setAttribute("role", "dialog");
|
|
138
|
+
sheet.setAttribute("aria-modal", "true");
|
|
139
|
+
sheet.setAttribute("aria-label", input.ariaLabel);
|
|
140
|
+
const iframe = document.createElement("iframe");
|
|
141
|
+
iframe.setAttribute(
|
|
142
|
+
"sandbox",
|
|
143
|
+
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
144
|
+
);
|
|
145
|
+
iframe.setAttribute(
|
|
146
|
+
"allow",
|
|
147
|
+
"otp-credentials; publickey-credentials-get; clipboard-write"
|
|
148
|
+
);
|
|
149
|
+
iframe.setAttribute("title", "Vana");
|
|
150
|
+
iframe.referrerPolicy = "strict-origin-when-cross-origin";
|
|
151
|
+
iframe.src = input.src;
|
|
152
|
+
const handle = document.createElement("div");
|
|
153
|
+
handle.className = "handle";
|
|
154
|
+
handle.setAttribute("aria-hidden", "true");
|
|
155
|
+
sheet.append(handle, iframe);
|
|
156
|
+
frame.append(scrim, sheet);
|
|
157
|
+
root.append(style, frame);
|
|
158
|
+
input.container.append(host);
|
|
159
|
+
const removeReturnGuard = installReturnGuard(sheet, iframe, returnState);
|
|
160
|
+
installDragToClose(sheet, handle, input.onClose);
|
|
161
|
+
return {
|
|
162
|
+
host,
|
|
163
|
+
iframe,
|
|
164
|
+
remove() {
|
|
165
|
+
removeReturnGuard();
|
|
166
|
+
host.remove();
|
|
167
|
+
},
|
|
168
|
+
setContentHeight(px) {
|
|
169
|
+
if (document.visibilityState !== "visible") {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
returnState.lastHeight = px;
|
|
173
|
+
if (returnState.shown) {
|
|
174
|
+
placeSheet(sheet, px);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
show() {
|
|
178
|
+
requestAnimationFrame(() => {
|
|
179
|
+
returnState.shown = true;
|
|
180
|
+
frame.classList.add("open");
|
|
181
|
+
placeSheet(sheet, returnState.lastHeight ?? INITIAL_CONTENT_PX);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
export {
|
|
187
|
+
DRAG_CLOSE_PX,
|
|
188
|
+
MAX_SHEET_RATIO,
|
|
189
|
+
RETURN_TAP_GUARD_MS,
|
|
190
|
+
createSheetChrome,
|
|
191
|
+
sheetTopPx
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=chrome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/connect/chrome.ts"],"sourcesContent":["/**\n * Sheet chrome for the Vana connect sheet: scrim, bottom sheet and drag\n * handle, in a Shadow DOM root.\n *\n * @remarks\n * Ported unchanged from `@vana-unity/connect-embed`. See the layout notes\n * below.\n *\n * @category Connect\n * @module connect/chrome\n */\n\n// Sheet chrome: scrim, bottom sheet, drag handle. Lives in a Shadow DOM root\n// so partner CSS cannot restyle it, and the partner cannot reach the iframe's\n// document (cross-origin) through it.\n//\n// host (partner DOM)\n// └─ #shadow-root\n// ├─ .scrim click closes\n// └─ .sheet top = viewport − (handle + content)\n// ├─ .handle drag down to close (above the frame, never over it)\n// └─ iframe fixed height = cap; the unused part sits below\n// the viewport\n//\n// Nothing overlaps or clips the iframe. IntersectionObserver v2 inside the\n// sheet reports it \"not visible\" when anything covers or clips it, and the\n// consent step waits for real visibility. The part of the frame below the\n// viewport is simply off screen, which does not count as occlusion.\n\nconst SHEET_RADIUS_PX = 28;\nconst SHEET_MAX_WIDTH_PX = 480;\nconst HANDLE_STRIP_PX = 20;\nconst INITIAL_CONTENT_PX = 320;\nconst TRANSITION_MS = 280;\nconst SHEET_BACKGROUND = \"#f4f4f4\";\n\n/** Drag the handle this far down to dismiss. */\nexport const DRAG_CLOSE_PX = 120;\n\n/** The sheet never covers more of the viewport than this. */\nexport const MAX_SHEET_RATIO = 0.92;\n\nconst STYLES = `\n:host { all: initial; }\n.scrim {\n position: fixed; inset: 0; z-index: 2147483646;\n background: rgb(0 0 0 / 0.4);\n opacity: 0; transition: opacity ${TRANSITION_MS}ms ease;\n}\n.sheet {\n position: fixed; left: 0; right: 0; z-index: 2147483647;\n margin: 0 auto; max-width: ${SHEET_MAX_WIDTH_PX}px;\n top: 100vh;\n transition: top ${TRANSITION_MS}ms cubic-bezier(0.32, 0.72, 0, 1);\n}\n.sheet.dragging { transition: none; }\n.handle {\n height: ${HANDLE_STRIP_PX}px;\n display: flex; justify-content: center; align-items: center;\n background: ${SHEET_BACKGROUND};\n border-radius: ${SHEET_RADIUS_PX}px ${SHEET_RADIUS_PX}px 0 0;\n touch-action: none; cursor: grab;\n}\n.handle::before {\n content: \"\"; width: 36px; height: 5px; border-radius: 3px;\n background: rgb(0 0 0 / 0.18);\n}\niframe {\n display: block; width: 100%; border: 0;\n background: ${SHEET_BACKGROUND};\n}\n.open .scrim { opacity: 1; }\n@media (prefers-reduced-motion: reduce) {\n .scrim, .sheet { transition: none; }\n}\n`;\n\nexport type CloseReason = \"drag\" | \"scrim\";\n\nexport interface SheetChrome {\n host: HTMLElement;\n iframe: HTMLIFrameElement;\n remove(): void;\n setContentHeight(px: number): void;\n show(): void;\n}\n\nfunction viewportPx(): number {\n return window.visualViewport?.height ?? window.innerHeight;\n}\n\nfunction capPx(): number {\n return Math.round(viewportPx() * MAX_SHEET_RATIO);\n}\n\n/** Places the sheet so exactly `content` px of the frame show above the fold. */\nfunction placeSheet(sheet: HTMLElement, content: number): void {\n sheet.style.top = `${sheetTopPx(viewportPx(), content)}px`;\n}\n\n/**\n * Where the sheet's top edge goes so `content` px of the frame show, capped\n * at {@link MAX_SHEET_RATIO} of the viewport.\n *\n * @param viewport - Viewport height in px.\n * @param content - Content height the sheet reported, in px.\n * @returns The sheet's `top`, in whole px.\n */\nexport function sheetTopPx(viewport: number, content: number): number {\n const cap = Math.round(viewport * MAX_SHEET_RATIO);\n const visible = Math.min(content, cap - HANDLE_STRIP_PX);\n // Whole pixels: a fractional offset blurs the framed text.\n return Math.round(viewport - HANDLE_STRIP_PX - visible);\n}\n\n/** Scrim taps this soon after the page returns to view are ignored. */\nexport const RETURN_TAP_GUARD_MS = 500;\n\n/**\n * Return-to-view handling, isolated so it can change on its own.\n *\n * Root cause of the spike's \"blank sheet\" (scratchpad blank-render notes): not\n * a paint failure. Returning from the Google popup tab, a stray tap landed on\n * the scrim and silently closed the sheet; a headless-Chrome harness artefact\n * looked similar. So: taps on the scrim right after the page regains\n * visibility or focus are ignored, heights reported while hidden are dropped,\n * and the last good height is re-applied on return. The iframe keeps a fixed\n * viewport (the cap) and only the sheet's position animates, so measuring\n * content inside the frame never resizes the frame itself.\n */\nfunction installReturnGuard(\n sheet: HTMLElement,\n iframe: HTMLIFrameElement,\n state: { lastHeight: number | null; returnedAt: number; shown: boolean },\n): () => void {\n const fitIframe = () => {\n iframe.style.height = `${capPx() - HANDLE_STRIP_PX}px`;\n if (state.shown) {\n placeSheet(sheet, state.lastHeight ?? INITIAL_CONTENT_PX);\n }\n };\n const onReturn = () => {\n if (document.visibilityState !== \"visible\") {\n return;\n }\n state.returnedAt = performance.now();\n fitIframe();\n };\n fitIframe();\n window.addEventListener(\"resize\", fitIframe);\n window.visualViewport?.addEventListener(\"resize\", fitIframe);\n document.addEventListener(\"visibilitychange\", onReturn);\n window.addEventListener(\"focus\", onReturn);\n return () => {\n window.removeEventListener(\"resize\", fitIframe);\n window.visualViewport?.removeEventListener(\"resize\", fitIframe);\n document.removeEventListener(\"visibilitychange\", onReturn);\n window.removeEventListener(\"focus\", onReturn);\n sheet.style.transform = \"\";\n };\n}\n\nfunction installDragToClose(\n sheet: HTMLElement,\n handle: HTMLElement,\n onClose: (reason: CloseReason) => void,\n): void {\n let startY: number | null = null;\n handle.addEventListener(\"pointerdown\", (event) => {\n startY = event.clientY;\n handle.setPointerCapture?.(event.pointerId);\n sheet.classList.add(\"dragging\");\n });\n handle.addEventListener(\"pointermove\", (event) => {\n if (startY === null) {\n return;\n }\n const dy = Math.max(0, event.clientY - startY);\n sheet.style.transform = `translateY(${dy}px)`;\n });\n const end = (event: PointerEvent) => {\n if (startY === null) {\n return;\n }\n const dy = event.clientY - startY;\n startY = null;\n sheet.classList.remove(\"dragging\");\n sheet.style.transform = \"\";\n if (dy > DRAG_CLOSE_PX) {\n onClose(\"drag\");\n }\n };\n handle.addEventListener(\"pointerup\", end);\n handle.addEventListener(\"pointercancel\", end);\n}\n\nexport function createSheetChrome(input: {\n ariaLabel: string;\n container: HTMLElement;\n onClose: (reason: CloseReason) => void;\n src: string;\n}): SheetChrome {\n const returnState = {\n lastHeight: null as number | null,\n returnedAt: 0,\n shown: false,\n };\n const host = document.createElement(\"div\");\n host.setAttribute(\"data-vana-connect\", \"\");\n const root = host.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = STYLES;\n\n const frame = document.createElement(\"div\");\n const scrim = document.createElement(\"div\");\n scrim.className = \"scrim\";\n scrim.setAttribute(\"data-slot\", \"scrim\");\n scrim.addEventListener(\"click\", () => {\n if (performance.now() - returnState.returnedAt < RETURN_TAP_GUARD_MS) {\n return;\n }\n input.onClose(\"scrim\");\n });\n\n const sheet = document.createElement(\"div\");\n sheet.className = \"sheet\";\n sheet.setAttribute(\"role\", \"dialog\");\n sheet.setAttribute(\"aria-modal\", \"true\");\n sheet.setAttribute(\"aria-label\", input.ariaLabel);\n\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\n \"sandbox\",\n \"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox\",\n );\n iframe.setAttribute(\n \"allow\",\n \"otp-credentials; publickey-credentials-get; clipboard-write\",\n );\n iframe.setAttribute(\"title\", \"Vana\");\n iframe.referrerPolicy = \"strict-origin-when-cross-origin\";\n iframe.src = input.src;\n\n const handle = document.createElement(\"div\");\n handle.className = \"handle\";\n handle.setAttribute(\"aria-hidden\", \"true\");\n\n sheet.append(handle, iframe);\n frame.append(scrim, sheet);\n root.append(style, frame);\n input.container.append(host);\n\n const removeReturnGuard = installReturnGuard(sheet, iframe, returnState);\n installDragToClose(sheet, handle, input.onClose);\n\n return {\n host,\n iframe,\n remove() {\n removeReturnGuard();\n host.remove();\n },\n setContentHeight(px) {\n // A hidden page can report a throttled frame's height; keep the last good one.\n if (document.visibilityState !== \"visible\") {\n return;\n }\n returnState.lastHeight = px;\n if (returnState.shown) {\n placeSheet(sheet, px);\n }\n },\n show() {\n // Next frame, so the closed state paints first and the sheet slides in.\n requestAnimationFrame(() => {\n returnState.shown = true;\n frame.classList.add(\"open\");\n placeSheet(sheet, returnState.lastHeight ?? INITIAL_CONTENT_PX);\n });\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var http_transports_exports = {};
|
|
20
|
+
__export(http_transports_exports, {
|
|
21
|
+
VanaConnectHttpError: () => VanaConnectHttpError,
|
|
22
|
+
createVanaConnectTransports: () => createVanaConnectTransports
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(http_transports_exports);
|
|
25
|
+
class VanaConnectHttpError extends Error {
|
|
26
|
+
constructor(message, status, code) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.name = "VanaConnectHttpError";
|
|
31
|
+
}
|
|
32
|
+
status;
|
|
33
|
+
code;
|
|
34
|
+
}
|
|
35
|
+
function createVanaConnectTransports(options = {}) {
|
|
36
|
+
const base = (options.endpoint ?? "/api/vana").replace(/\/+$/, "");
|
|
37
|
+
const now = options.now ?? Date.now;
|
|
38
|
+
const reuseMs = options.reuseFreshAuthorizationMs ?? 6e4;
|
|
39
|
+
let fresh = null;
|
|
40
|
+
let config = null;
|
|
41
|
+
async function call(path, init) {
|
|
42
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
43
|
+
const response = await fetchFn(`${base}/${path}`, {
|
|
44
|
+
credentials: "same-origin",
|
|
45
|
+
...init
|
|
46
|
+
});
|
|
47
|
+
const body = await response.json().catch(() => null);
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const code = typeof body?.error?.code === "string" ? body.error.code : void 0;
|
|
50
|
+
const message = typeof body?.error?.message === "string" ? body.error.message : `Vana connect route ${path} failed: ${response.status}`;
|
|
51
|
+
throw new VanaConnectHttpError(message, response.status, code);
|
|
52
|
+
}
|
|
53
|
+
return body;
|
|
54
|
+
}
|
|
55
|
+
function postRequest(body) {
|
|
56
|
+
return call("request", {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: { "Content-Type": "application/json" },
|
|
59
|
+
body: JSON.stringify(body)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
async createRequest() {
|
|
64
|
+
const { request, embed } = await postRequest({});
|
|
65
|
+
fresh = embed ? { ...embed, at: now() } : null;
|
|
66
|
+
return request;
|
|
67
|
+
},
|
|
68
|
+
getStatus(requestId) {
|
|
69
|
+
return call(`status?requestId=${encodeURIComponent(requestId)}`);
|
|
70
|
+
},
|
|
71
|
+
readResult(requestId) {
|
|
72
|
+
return call(`data?requestId=${encodeURIComponent(requestId)}`);
|
|
73
|
+
},
|
|
74
|
+
async authorizeRequest(requestId) {
|
|
75
|
+
const cached = fresh;
|
|
76
|
+
fresh = null;
|
|
77
|
+
if (cached && cached.id === requestId && now() - cached.at < reuseMs) {
|
|
78
|
+
return cached.authorization;
|
|
79
|
+
}
|
|
80
|
+
const { embed } = await postRequest({ requestId });
|
|
81
|
+
if (!embed) {
|
|
82
|
+
throw new VanaConnectHttpError("The request could not be signed", 500);
|
|
83
|
+
}
|
|
84
|
+
return embed.authorization;
|
|
85
|
+
},
|
|
86
|
+
getConfig() {
|
|
87
|
+
config ??= call("config").catch((error) => {
|
|
88
|
+
config = null;
|
|
89
|
+
throw error;
|
|
90
|
+
});
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
96
|
+
0 && (module.exports = {
|
|
97
|
+
VanaConnectHttpError,
|
|
98
|
+
createVanaConnectTransports
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=http-transports.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/connect/http-transports.ts"],"sourcesContent":["/**\n * Browser transports for the routes `createVanaConnectHandler` serves.\n *\n * @remarks\n * `createVanaConnectTransports({ endpoint: \"/api/vana\" })` returns the\n * `createRequest` / `getStatus` / `readResult` / `authorizeRequest`\n * transports `createDirectConnectFlow` takes, plus `getConfig` for the sheet.\n * Any backend that implements the same wire contract works, in any language.\n *\n * @category Connect\n * @module connect/http-transports\n */\n\nimport type { RequestAuthorization } from \"./protocol\";\nimport type {\n AccessRequest,\n AccessRequestStatus,\n ApprovedDataResult,\n} from \"../direct/types\";\n\n/** Response body of `GET {endpoint}/config`. */\nexport interface VanaConnectConfig {\n /** Protocol version of the sheet the backend's SDK speaks. */\n protocolVersion: number;\n /** `\"production\"` or `\"dev\"`. */\n env: string;\n /** Registered embed client id, or `null` when the sheet is not set up. */\n clientId: string | null;\n /** Label on the sheet chrome. */\n appName: string;\n /** Vana Account origin that serves the sheet. */\n accountOrigin: string;\n}\n\n/** Response body of `POST {endpoint}/request`. */\nexport interface VanaConnectRequestResponse {\n request: AccessRequest;\n /** The sheet's read capability for `request`; `null` if it cannot be signed. */\n embed: { id: string; authorization: RequestAuthorization } | null;\n}\n\n/** Thrown when a connect route answers with a non-2xx status. */\nexport class VanaConnectHttpError extends Error {\n constructor(\n message: string,\n /** HTTP status. */\n public readonly status: number,\n /** `error.code` from the body, when present. */\n public readonly code?: string,\n ) {\n super(message);\n this.name = \"VanaConnectHttpError\";\n }\n}\n\n/** Options for {@link createVanaConnectTransports}. */\nexport interface VanaConnectTransportOptions {\n /** Base path of the handler's routes. Defaults to `\"/api/vana\"`. */\n endpoint?: string;\n /** `fetch`. Defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /**\n * How long the signature returned with a fresh request is reused for the\n * sheet's first ask, in ms. Defaults to 60000. Later asks always re-sign.\n */\n reuseFreshAuthorizationMs?: number;\n /** Clock in ms. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/** The transports {@link createVanaConnectTransports} returns. */\nexport interface VanaConnectTransports<T = unknown> {\n createRequest(): Promise<AccessRequest>;\n getStatus(requestId: string): Promise<AccessRequestStatus>;\n readResult(requestId: string): Promise<ApprovedDataResult<T>>;\n authorizeRequest(requestId: string): Promise<RequestAuthorization>;\n getConfig(): Promise<VanaConnectConfig>;\n}\n\n/**\n * Create browser transports for a `createVanaConnectHandler` backend.\n *\n * @param options - Base path and `fetch`.\n * @returns Transports for `createDirectConnectFlow` / `useVanaConnect`.\n */\nexport function createVanaConnectTransports<T = unknown>(\n options: VanaConnectTransportOptions = {},\n): VanaConnectTransports<T> {\n const base = (options.endpoint ?? \"/api/vana\").replace(/\\/+$/, \"\");\n const now = options.now ?? Date.now;\n const reuseMs = options.reuseFreshAuthorizationMs ?? 60_000;\n let fresh: {\n id: string;\n authorization: RequestAuthorization;\n at: number;\n } | null = null;\n let config: Promise<VanaConnectConfig> | null = null;\n\n async function call<R>(path: string, init?: RequestInit): Promise<R> {\n const fetchFn = options.fetch ?? globalThis.fetch;\n const response = await fetchFn(`${base}/${path}`, {\n credentials: \"same-origin\",\n ...init,\n });\n const body = (await response.json().catch(() => null)) as {\n error?: { code?: unknown; message?: unknown };\n } | null;\n if (!response.ok) {\n const code =\n typeof body?.error?.code === \"string\" ? body.error.code : undefined;\n const message =\n typeof body?.error?.message === \"string\"\n ? body.error.message\n : `Vana connect route ${path} failed: ${response.status}`;\n throw new VanaConnectHttpError(message, response.status, code);\n }\n return body as R;\n }\n\n function postRequest(body: object): Promise<VanaConnectRequestResponse> {\n return call<VanaConnectRequestResponse>(\"request\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n }\n\n return {\n async createRequest() {\n const { request, embed } = await postRequest({});\n fresh = embed ? { ...embed, at: now() } : null;\n return request;\n },\n getStatus(requestId) {\n return call(`status?requestId=${encodeURIComponent(requestId)}`);\n },\n readResult(requestId) {\n return call(`data?requestId=${encodeURIComponent(requestId)}`);\n },\n async authorizeRequest(requestId) {\n const cached = fresh;\n fresh = null;\n if (cached && cached.id === requestId && now() - cached.at < reuseMs) {\n return cached.authorization;\n }\n const { embed } = await postRequest({ requestId });\n if (!embed) {\n throw new VanaConnectHttpError(\"The request could not be signed\", 500);\n }\n return embed.authorization;\n },\n getConfig() {\n config ??= call<VanaConnectConfig>(\"config\").catch((error: unknown) => {\n config = null;\n throw error;\n });\n return config;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0CO,MAAM,6BAA6B,MAAM;AAAA,EAC9C,YACE,SAEgB,QAEA,MAChB;AACA,UAAM,OAAO;AAJG;AAEA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EANkB;AAAA,EAEA;AAKpB;AAgCO,SAAS,4BACd,UAAuC,CAAC,GACd;AAC1B,QAAM,QAAQ,QAAQ,YAAY,aAAa,QAAQ,QAAQ,EAAE;AACjE,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,UAAU,QAAQ,6BAA6B;AACrD,MAAI,QAIO;AACX,MAAI,SAA4C;AAEhD,iBAAe,KAAQ,MAAc,MAAgC;AACnE,UAAM,UAAU,QAAQ,SAAS,WAAW;AAC5C,UAAM,WAAW,MAAM,QAAQ,GAAG,IAAI,IAAI,IAAI,IAAI;AAAA,MAChD,aAAa;AAAA,MACb,GAAG;AAAA,IACL,CAAC;AACD,UAAM,OAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,IAAI;AAGpD,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OACJ,OAAO,MAAM,OAAO,SAAS,WAAW,KAAK,MAAM,OAAO;AAC5D,YAAM,UACJ,OAAO,MAAM,OAAO,YAAY,WAC5B,KAAK,MAAM,UACX,sBAAsB,IAAI,YAAY,SAAS,MAAM;AAC3D,YAAM,IAAI,qBAAqB,SAAS,SAAS,QAAQ,IAAI;AAAA,IAC/D;AACA,WAAO;AAAA,EACT;AAEA,WAAS,YAAY,MAAmD;AACtE,WAAO,KAAiC,WAAW;AAAA,MACjD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM,gBAAgB;AACpB,YAAM,EAAE,SAAS,MAAM,IAAI,MAAM,YAAY,CAAC,CAAC;AAC/C,cAAQ,QAAQ,EAAE,GAAG,OAAO,IAAI,IAAI,EAAE,IAAI;AAC1C,aAAO;AAAA,IACT;AAAA,IACA,UAAU,WAAW;AACnB,aAAO,KAAK,oBAAoB,mBAAmB,SAAS,CAAC,EAAE;AAAA,IACjE;AAAA,IACA,WAAW,WAAW;AACpB,aAAO,KAAK,kBAAkB,mBAAmB,SAAS,CAAC,EAAE;AAAA,IAC/D;AAAA,IACA,MAAM,iBAAiB,WAAW;AAChC,YAAM,SAAS;AACf,cAAQ;AACR,UAAI,UAAU,OAAO,OAAO,aAAa,IAAI,IAAI,OAAO,KAAK,SAAS;AACpE,eAAO,OAAO;AAAA,MAChB;AACA,YAAM,EAAE,MAAM,IAAI,MAAM,YAAY,EAAE,UAAU,CAAC;AACjD,UAAI,CAAC,OAAO;AACV,cAAM,IAAI,qBAAqB,mCAAmC,GAAG;AAAA,MACvE;AACA,aAAO,MAAM;AAAA,IACf;AAAA,IACA,YAAY;AACV,iBAAW,KAAwB,QAAQ,EAAE,MAAM,CAAC,UAAmB;AACrE,iBAAS;AACT,cAAM;AAAA,MACR,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser transports for the routes `createVanaConnectHandler` serves.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* `createVanaConnectTransports({ endpoint: "/api/vana" })` returns the
|
|
6
|
+
* `createRequest` / `getStatus` / `readResult` / `authorizeRequest`
|
|
7
|
+
* transports `createDirectConnectFlow` takes, plus `getConfig` for the sheet.
|
|
8
|
+
* Any backend that implements the same wire contract works, in any language.
|
|
9
|
+
*
|
|
10
|
+
* @category Connect
|
|
11
|
+
* @module connect/http-transports
|
|
12
|
+
*/
|
|
13
|
+
import type { RequestAuthorization } from "./protocol.js";
|
|
14
|
+
import type { AccessRequest, AccessRequestStatus, ApprovedDataResult } from "../direct/types.js";
|
|
15
|
+
/** Response body of `GET {endpoint}/config`. */
|
|
16
|
+
export interface VanaConnectConfig {
|
|
17
|
+
/** Protocol version of the sheet the backend's SDK speaks. */
|
|
18
|
+
protocolVersion: number;
|
|
19
|
+
/** `"production"` or `"dev"`. */
|
|
20
|
+
env: string;
|
|
21
|
+
/** Registered embed client id, or `null` when the sheet is not set up. */
|
|
22
|
+
clientId: string | null;
|
|
23
|
+
/** Label on the sheet chrome. */
|
|
24
|
+
appName: string;
|
|
25
|
+
/** Vana Account origin that serves the sheet. */
|
|
26
|
+
accountOrigin: string;
|
|
27
|
+
}
|
|
28
|
+
/** Response body of `POST {endpoint}/request`. */
|
|
29
|
+
export interface VanaConnectRequestResponse {
|
|
30
|
+
request: AccessRequest;
|
|
31
|
+
/** The sheet's read capability for `request`; `null` if it cannot be signed. */
|
|
32
|
+
embed: {
|
|
33
|
+
id: string;
|
|
34
|
+
authorization: RequestAuthorization;
|
|
35
|
+
} | null;
|
|
36
|
+
}
|
|
37
|
+
/** Thrown when a connect route answers with a non-2xx status. */
|
|
38
|
+
export declare class VanaConnectHttpError extends Error {
|
|
39
|
+
/** HTTP status. */
|
|
40
|
+
readonly status: number;
|
|
41
|
+
/** `error.code` from the body, when present. */
|
|
42
|
+
readonly code?: string | undefined;
|
|
43
|
+
constructor(message: string,
|
|
44
|
+
/** HTTP status. */
|
|
45
|
+
status: number,
|
|
46
|
+
/** `error.code` from the body, when present. */
|
|
47
|
+
code?: string | undefined);
|
|
48
|
+
}
|
|
49
|
+
/** Options for {@link createVanaConnectTransports}. */
|
|
50
|
+
export interface VanaConnectTransportOptions {
|
|
51
|
+
/** Base path of the handler's routes. Defaults to `"/api/vana"`. */
|
|
52
|
+
endpoint?: string;
|
|
53
|
+
/** `fetch`. Defaults to `globalThis.fetch`. */
|
|
54
|
+
fetch?: typeof fetch;
|
|
55
|
+
/**
|
|
56
|
+
* How long the signature returned with a fresh request is reused for the
|
|
57
|
+
* sheet's first ask, in ms. Defaults to 60000. Later asks always re-sign.
|
|
58
|
+
*/
|
|
59
|
+
reuseFreshAuthorizationMs?: number;
|
|
60
|
+
/** Clock in ms. Defaults to `Date.now`. */
|
|
61
|
+
now?: () => number;
|
|
62
|
+
}
|
|
63
|
+
/** The transports {@link createVanaConnectTransports} returns. */
|
|
64
|
+
export interface VanaConnectTransports<T = unknown> {
|
|
65
|
+
createRequest(): Promise<AccessRequest>;
|
|
66
|
+
getStatus(requestId: string): Promise<AccessRequestStatus>;
|
|
67
|
+
readResult(requestId: string): Promise<ApprovedDataResult<T>>;
|
|
68
|
+
authorizeRequest(requestId: string): Promise<RequestAuthorization>;
|
|
69
|
+
getConfig(): Promise<VanaConnectConfig>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create browser transports for a `createVanaConnectHandler` backend.
|
|
73
|
+
*
|
|
74
|
+
* @param options - Base path and `fetch`.
|
|
75
|
+
* @returns Transports for `createDirectConnectFlow` / `useVanaConnect`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createVanaConnectTransports<T = unknown>(options?: VanaConnectTransportOptions): VanaConnectTransports<T>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
class VanaConnectHttpError extends Error {
|
|
2
|
+
constructor(message, status, code) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.status = status;
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = "VanaConnectHttpError";
|
|
7
|
+
}
|
|
8
|
+
status;
|
|
9
|
+
code;
|
|
10
|
+
}
|
|
11
|
+
function createVanaConnectTransports(options = {}) {
|
|
12
|
+
const base = (options.endpoint ?? "/api/vana").replace(/\/+$/, "");
|
|
13
|
+
const now = options.now ?? Date.now;
|
|
14
|
+
const reuseMs = options.reuseFreshAuthorizationMs ?? 6e4;
|
|
15
|
+
let fresh = null;
|
|
16
|
+
let config = null;
|
|
17
|
+
async function call(path, init) {
|
|
18
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
19
|
+
const response = await fetchFn(`${base}/${path}`, {
|
|
20
|
+
credentials: "same-origin",
|
|
21
|
+
...init
|
|
22
|
+
});
|
|
23
|
+
const body = await response.json().catch(() => null);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const code = typeof body?.error?.code === "string" ? body.error.code : void 0;
|
|
26
|
+
const message = typeof body?.error?.message === "string" ? body.error.message : `Vana connect route ${path} failed: ${response.status}`;
|
|
27
|
+
throw new VanaConnectHttpError(message, response.status, code);
|
|
28
|
+
}
|
|
29
|
+
return body;
|
|
30
|
+
}
|
|
31
|
+
function postRequest(body) {
|
|
32
|
+
return call("request", {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: { "Content-Type": "application/json" },
|
|
35
|
+
body: JSON.stringify(body)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
async createRequest() {
|
|
40
|
+
const { request, embed } = await postRequest({});
|
|
41
|
+
fresh = embed ? { ...embed, at: now() } : null;
|
|
42
|
+
return request;
|
|
43
|
+
},
|
|
44
|
+
getStatus(requestId) {
|
|
45
|
+
return call(`status?requestId=${encodeURIComponent(requestId)}`);
|
|
46
|
+
},
|
|
47
|
+
readResult(requestId) {
|
|
48
|
+
return call(`data?requestId=${encodeURIComponent(requestId)}`);
|
|
49
|
+
},
|
|
50
|
+
async authorizeRequest(requestId) {
|
|
51
|
+
const cached = fresh;
|
|
52
|
+
fresh = null;
|
|
53
|
+
if (cached && cached.id === requestId && now() - cached.at < reuseMs) {
|
|
54
|
+
return cached.authorization;
|
|
55
|
+
}
|
|
56
|
+
const { embed } = await postRequest({ requestId });
|
|
57
|
+
if (!embed) {
|
|
58
|
+
throw new VanaConnectHttpError("The request could not be signed", 500);
|
|
59
|
+
}
|
|
60
|
+
return embed.authorization;
|
|
61
|
+
},
|
|
62
|
+
getConfig() {
|
|
63
|
+
config ??= call("config").catch((error) => {
|
|
64
|
+
config = null;
|
|
65
|
+
throw error;
|
|
66
|
+
});
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
VanaConnectHttpError,
|
|
73
|
+
createVanaConnectTransports
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=http-transports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/connect/http-transports.ts"],"sourcesContent":["/**\n * Browser transports for the routes `createVanaConnectHandler` serves.\n *\n * @remarks\n * `createVanaConnectTransports({ endpoint: \"/api/vana\" })` returns the\n * `createRequest` / `getStatus` / `readResult` / `authorizeRequest`\n * transports `createDirectConnectFlow` takes, plus `getConfig` for the sheet.\n * Any backend that implements the same wire contract works, in any language.\n *\n * @category Connect\n * @module connect/http-transports\n */\n\nimport type { RequestAuthorization } from \"./protocol\";\nimport type {\n AccessRequest,\n AccessRequestStatus,\n ApprovedDataResult,\n} from \"../direct/types\";\n\n/** Response body of `GET {endpoint}/config`. */\nexport interface VanaConnectConfig {\n /** Protocol version of the sheet the backend's SDK speaks. */\n protocolVersion: number;\n /** `\"production\"` or `\"dev\"`. */\n env: string;\n /** Registered embed client id, or `null` when the sheet is not set up. */\n clientId: string | null;\n /** Label on the sheet chrome. */\n appName: string;\n /** Vana Account origin that serves the sheet. */\n accountOrigin: string;\n}\n\n/** Response body of `POST {endpoint}/request`. */\nexport interface VanaConnectRequestResponse {\n request: AccessRequest;\n /** The sheet's read capability for `request`; `null` if it cannot be signed. */\n embed: { id: string; authorization: RequestAuthorization } | null;\n}\n\n/** Thrown when a connect route answers with a non-2xx status. */\nexport class VanaConnectHttpError extends Error {\n constructor(\n message: string,\n /** HTTP status. */\n public readonly status: number,\n /** `error.code` from the body, when present. */\n public readonly code?: string,\n ) {\n super(message);\n this.name = \"VanaConnectHttpError\";\n }\n}\n\n/** Options for {@link createVanaConnectTransports}. */\nexport interface VanaConnectTransportOptions {\n /** Base path of the handler's routes. Defaults to `\"/api/vana\"`. */\n endpoint?: string;\n /** `fetch`. Defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /**\n * How long the signature returned with a fresh request is reused for the\n * sheet's first ask, in ms. Defaults to 60000. Later asks always re-sign.\n */\n reuseFreshAuthorizationMs?: number;\n /** Clock in ms. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/** The transports {@link createVanaConnectTransports} returns. */\nexport interface VanaConnectTransports<T = unknown> {\n createRequest(): Promise<AccessRequest>;\n getStatus(requestId: string): Promise<AccessRequestStatus>;\n readResult(requestId: string): Promise<ApprovedDataResult<T>>;\n authorizeRequest(requestId: string): Promise<RequestAuthorization>;\n getConfig(): Promise<VanaConnectConfig>;\n}\n\n/**\n * Create browser transports for a `createVanaConnectHandler` backend.\n *\n * @param options - Base path and `fetch`.\n * @returns Transports for `createDirectConnectFlow` / `useVanaConnect`.\n */\nexport function createVanaConnectTransports<T = unknown>(\n options: VanaConnectTransportOptions = {},\n): VanaConnectTransports<T> {\n const base = (options.endpoint ?? \"/api/vana\").replace(/\\/+$/, \"\");\n const now = options.now ?? Date.now;\n const reuseMs = options.reuseFreshAuthorizationMs ?? 60_000;\n let fresh: {\n id: string;\n authorization: RequestAuthorization;\n at: number;\n } | null = null;\n let config: Promise<VanaConnectConfig> | null = null;\n\n async function call<R>(path: string, init?: RequestInit): Promise<R> {\n const fetchFn = options.fetch ?? globalThis.fetch;\n const response = await fetchFn(`${base}/${path}`, {\n credentials: \"same-origin\",\n ...init,\n });\n const body = (await response.json().catch(() => null)) as {\n error?: { code?: unknown; message?: unknown };\n } | null;\n if (!response.ok) {\n const code =\n typeof body?.error?.code === \"string\" ? body.error.code : undefined;\n const message =\n typeof body?.error?.message === \"string\"\n ? body.error.message\n : `Vana connect route ${path} failed: ${response.status}`;\n throw new VanaConnectHttpError(message, response.status, code);\n }\n return body as R;\n }\n\n function postRequest(body: object): Promise<VanaConnectRequestResponse> {\n return call<VanaConnectRequestResponse>(\"request\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n }\n\n return {\n async createRequest() {\n const { request, embed } = await postRequest({});\n fresh = embed ? { ...embed, at: now() } : null;\n return request;\n },\n getStatus(requestId) {\n return call(`status?requestId=${encodeURIComponent(requestId)}`);\n },\n readResult(requestId) {\n return call(`data?requestId=${encodeURIComponent(requestId)}`);\n },\n async authorizeRequest(requestId) {\n const cached = fresh;\n fresh = null;\n if (cached && cached.id === requestId && now() - cached.at < reuseMs) {\n return cached.authorization;\n }\n const { embed } = await postRequest({ requestId });\n if (!embed) {\n throw new VanaConnectHttpError(\"The request could not be signed\", 500);\n }\n return embed.authorization;\n },\n getConfig() {\n config ??= call<VanaConnectConfig>(\"config\").catch((error: unknown) => {\n config = null;\n throw error;\n });\n return config;\n },\n };\n}\n"],"mappings":"AA0CO,MAAM,6BAA6B,MAAM;AAAA,EAC9C,YACE,SAEgB,QAEA,MAChB;AACA,UAAM,OAAO;AAJG;AAEA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EANkB;AAAA,EAEA;AAKpB;AAgCO,SAAS,4BACd,UAAuC,CAAC,GACd;AAC1B,QAAM,QAAQ,QAAQ,YAAY,aAAa,QAAQ,QAAQ,EAAE;AACjE,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,UAAU,QAAQ,6BAA6B;AACrD,MAAI,QAIO;AACX,MAAI,SAA4C;AAEhD,iBAAe,KAAQ,MAAc,MAAgC;AACnE,UAAM,UAAU,QAAQ,SAAS,WAAW;AAC5C,UAAM,WAAW,MAAM,QAAQ,GAAG,IAAI,IAAI,IAAI,IAAI;AAAA,MAChD,aAAa;AAAA,MACb,GAAG;AAAA,IACL,CAAC;AACD,UAAM,OAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,IAAI;AAGpD,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OACJ,OAAO,MAAM,OAAO,SAAS,WAAW,KAAK,MAAM,OAAO;AAC5D,YAAM,UACJ,OAAO,MAAM,OAAO,YAAY,WAC5B,KAAK,MAAM,UACX,sBAAsB,IAAI,YAAY,SAAS,MAAM;AAC3D,YAAM,IAAI,qBAAqB,SAAS,SAAS,QAAQ,IAAI;AAAA,IAC/D;AACA,WAAO;AAAA,EACT;AAEA,WAAS,YAAY,MAAmD;AACtE,WAAO,KAAiC,WAAW;AAAA,MACjD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM,gBAAgB;AACpB,YAAM,EAAE,SAAS,MAAM,IAAI,MAAM,YAAY,CAAC,CAAC;AAC/C,cAAQ,QAAQ,EAAE,GAAG,OAAO,IAAI,IAAI,EAAE,IAAI;AAC1C,aAAO;AAAA,IACT;AAAA,IACA,UAAU,WAAW;AACnB,aAAO,KAAK,oBAAoB,mBAAmB,SAAS,CAAC,EAAE;AAAA,IACjE;AAAA,IACA,WAAW,WAAW;AACpB,aAAO,KAAK,kBAAkB,mBAAmB,SAAS,CAAC,EAAE;AAAA,IAC/D;AAAA,IACA,MAAM,iBAAiB,WAAW;AAChC,YAAM,SAAS;AACf,cAAQ;AACR,UAAI,UAAU,OAAO,OAAO,aAAa,IAAI,IAAI,OAAO,KAAK,SAAS;AACpE,eAAO,OAAO;AAAA,MAChB;AACA,YAAM,EAAE,MAAM,IAAI,MAAM,YAAY,EAAE,UAAU,CAAC;AACjD,UAAI,CAAC,OAAO;AACV,cAAM,IAAI,qBAAqB,mCAAmC,GAAG;AAAA,MACvE;AACA,aAAO,MAAM;AAAA,IACf;AAAA,IACA,YAAY;AACV,iBAAW,KAAwB,QAAQ,EAAE,MAAM,CAAC,UAAmB;AACrE,iBAAS;AACT,cAAM;AAAA,MACR,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|