@goflowstate/widget-sdk 0.2.2 → 0.3.1
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 +44 -5
- package/dist/canvas-widget-sdk.js +455 -7
- package/dist/canvas-widget-sdk.js.map +4 -4
- package/dist/feedback.d.ts +79 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.test.d.ts +2 -0
- package/dist/feedback.test.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +456 -10
- package/dist/index.mjs.map +3 -3
- package/dist/session.d.ts +71 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.request.test.d.ts +2 -0
- package/dist/session.request.test.d.ts.map +1 -0
- package/package.json +5 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @goflowstate/widget-sdk v0.
|
|
1
|
+
// @goflowstate/widget-sdk v0.3.1
|
|
2
2
|
// https://github.com/goflowstate/goflowstate-sdk
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -39,6 +39,389 @@ function createServiceClient(opts) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// src/feedback.ts
|
|
43
|
+
var FEEDBACK_HOST_ATTR = "data-fs-feedback-host";
|
|
44
|
+
var SCREENSHOT_MAX_BYTES = 15e4;
|
|
45
|
+
var SCREENSHOT_MAX_SIDE = 1280;
|
|
46
|
+
var CAPTURE_TIMEOUT_MS = 4e3;
|
|
47
|
+
function parseColor(raw) {
|
|
48
|
+
const value = (raw ?? "").trim().toLowerCase();
|
|
49
|
+
if (!value || value === "transparent") return null;
|
|
50
|
+
const hex = /^#([0-9a-f]{3,8})$/.exec(value);
|
|
51
|
+
if (hex) {
|
|
52
|
+
const h = hex[1];
|
|
53
|
+
const full = h.length <= 4 ? h.split("").map((c) => c + c).join("") : h;
|
|
54
|
+
if (full.length !== 6 && full.length !== 8) return null;
|
|
55
|
+
const n = (i) => parseInt(full.slice(i, i + 2), 16);
|
|
56
|
+
return [n(0), n(2), n(4), full.length === 8 ? n(6) / 255 : 1];
|
|
57
|
+
}
|
|
58
|
+
const fn = /^rgba?\(([^)]+)\)$/.exec(value);
|
|
59
|
+
if (!fn) return null;
|
|
60
|
+
const parts = fn[1].split(/[\s,/]+/).filter(Boolean);
|
|
61
|
+
if (parts.length < 3) return null;
|
|
62
|
+
const channel = (p) => p.endsWith("%") ? parseFloat(p) * 255 / 100 : parseFloat(p);
|
|
63
|
+
const alpha = parts[3] === void 0 ? 1 : parts[3].endsWith("%") ? parseFloat(parts[3]) / 100 : parseFloat(parts[3]);
|
|
64
|
+
const out = [channel(parts[0]), channel(parts[1]), channel(parts[2]), alpha];
|
|
65
|
+
return out.some((v) => Number.isNaN(v)) ? null : out;
|
|
66
|
+
}
|
|
67
|
+
var opaque = (c) => c !== null && c[3] >= 0.9;
|
|
68
|
+
var css = (c, alpha = c[3]) => alpha >= 1 ? `rgb(${Math.round(c[0])}, ${Math.round(c[1])}, ${Math.round(c[2])})` : `rgba(${Math.round(c[0])}, ${Math.round(c[1])}, ${Math.round(c[2])}, ${alpha})`;
|
|
69
|
+
var mix = (a, b, t) => [
|
|
70
|
+
a[0] + (b[0] - a[0]) * t,
|
|
71
|
+
a[1] + (b[1] - a[1]) * t,
|
|
72
|
+
a[2] + (b[2] - a[2]) * t,
|
|
73
|
+
1
|
|
74
|
+
];
|
|
75
|
+
var luminance = (c) => {
|
|
76
|
+
const lin = (v) => {
|
|
77
|
+
const s = v / 255;
|
|
78
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
79
|
+
};
|
|
80
|
+
return 0.2126 * lin(c[0]) + 0.7152 * lin(c[1]) + 0.0722 * lin(c[2]);
|
|
81
|
+
};
|
|
82
|
+
var contrast = (a, b) => {
|
|
83
|
+
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
|
|
84
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
85
|
+
};
|
|
86
|
+
var WHITE = [255, 255, 255, 1];
|
|
87
|
+
var BLACK = [17, 17, 20, 1];
|
|
88
|
+
var styleOf = (el) => getComputedStyle(el);
|
|
89
|
+
var rootVar = (doc, names) => {
|
|
90
|
+
for (const el of [doc.documentElement, doc.body]) {
|
|
91
|
+
if (!el) continue;
|
|
92
|
+
const style = styleOf(el);
|
|
93
|
+
for (const name of names) {
|
|
94
|
+
const value = style.getPropertyValue(name).trim();
|
|
95
|
+
if (value) return value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return "";
|
|
99
|
+
};
|
|
100
|
+
var visible = (el) => {
|
|
101
|
+
const rect = el.getBoundingClientRect();
|
|
102
|
+
const style = styleOf(el);
|
|
103
|
+
return style.display !== "none" && style.visibility !== "hidden" && rect.width * rect.height >= 0;
|
|
104
|
+
};
|
|
105
|
+
var inOurs = (el) => el.closest(`[${FEEDBACK_HOST_ATTR}]`) !== null;
|
|
106
|
+
var primaryButton = (doc, ground) => {
|
|
107
|
+
let best = null;
|
|
108
|
+
let bestArea = -1;
|
|
109
|
+
doc.querySelectorAll(
|
|
110
|
+
'button, [role="button"], input[type="submit"], a[class*="btn"], a[class*="cta"]'
|
|
111
|
+
).forEach((el) => {
|
|
112
|
+
if (inOurs(el) || !visible(el)) return;
|
|
113
|
+
const fill = parseColor(styleOf(el).backgroundColor);
|
|
114
|
+
if (!opaque(fill) || contrast(fill, ground) < 1.25) return;
|
|
115
|
+
const rect = el.getBoundingClientRect();
|
|
116
|
+
const area = rect.width * rect.height;
|
|
117
|
+
if (area > bestArea) {
|
|
118
|
+
best = el;
|
|
119
|
+
bestArea = area;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return best;
|
|
123
|
+
};
|
|
124
|
+
function readFlowTheme(doc = document) {
|
|
125
|
+
const body = doc.body;
|
|
126
|
+
const stage = body.querySelector(":scope > :not(script):not(style):not([data-fs-feedback-host])");
|
|
127
|
+
const bodyStyle = styleOf(body);
|
|
128
|
+
const sampledGround = [stage, body, doc.documentElement].map((el) => el ? parseColor(styleOf(el).backgroundColor) : null).find(opaque);
|
|
129
|
+
const sampledInk = parseColor((stage ? styleOf(stage) : bodyStyle).color) ?? parseColor(bodyStyle.color);
|
|
130
|
+
const tokenGround = parseColor(rootVar(doc, ["--sb-ground", "--sb-bg", "--color-ink-050"]));
|
|
131
|
+
const ink0 = parseColor(rootVar(doc, ["--fs-feedback-ink"])) ?? sampledInk;
|
|
132
|
+
let ground = parseColor(rootVar(doc, ["--fs-feedback-ground"])) ?? sampledGround ?? (tokenGround && opaque(tokenGround) ? tokenGround : null) ?? (ink0 && luminance(ink0) > 0.5 ? BLACK : WHITE);
|
|
133
|
+
ground = [ground[0], ground[1], ground[2], 1];
|
|
134
|
+
const dark = luminance(ground) < 0.35;
|
|
135
|
+
let ink = ink0 && contrast([ink0[0], ink0[1], ink0[2], 1], ground) >= 4.5 ? ink0 : dark ? WHITE : BLACK;
|
|
136
|
+
ink = [ink[0], ink[1], ink[2], 1];
|
|
137
|
+
const button = primaryButton(doc, ground);
|
|
138
|
+
const buttonStyle = button ? styleOf(button) : null;
|
|
139
|
+
const pinnedCta = parseColor(rootVar(doc, ["--fs-feedback-cta"]));
|
|
140
|
+
const tokenCta = parseColor(rootVar(doc, ["--sb-cta", "--sb-accent", "--color-cream"]));
|
|
141
|
+
const sampledCta = buttonStyle ? parseColor(buttonStyle.backgroundColor) : null;
|
|
142
|
+
const cta = pinnedCta ?? sampledCta ?? (tokenCta && contrast(tokenCta, ground) >= 1.25 ? tokenCta : null) ?? ink;
|
|
143
|
+
const pinnedCtaInk = parseColor(rootVar(doc, ["--fs-feedback-cta-ink"]));
|
|
144
|
+
const sampledCtaInk = !pinnedCta && sampledCta && buttonStyle ? parseColor(buttonStyle.color) : null;
|
|
145
|
+
const ctaInk = pinnedCtaInk ?? (sampledCtaInk && contrast(sampledCtaInk, cta) >= 3 ? sampledCtaInk : null) ?? (luminance(cta) > 0.4 ? BLACK : WHITE);
|
|
146
|
+
const heading = Array.from(doc.querySelectorAll('h1, h2, [class*="title"]')).find(
|
|
147
|
+
(el) => !inOurs(el) && visible(el)
|
|
148
|
+
);
|
|
149
|
+
const headingStyle = heading ? styleOf(heading) : null;
|
|
150
|
+
const fontBody = rootVar(doc, ["--fs-feedback-font"]) || (stage ? styleOf(stage).fontFamily : "") || bodyStyle.fontFamily || rootVar(doc, ["--sb-font-body", "--font-body"]) || "system-ui, sans-serif";
|
|
151
|
+
const fontDisplay = rootVar(doc, ["--fs-feedback-font-display"]) || headingStyle?.fontFamily || rootVar(doc, ["--sb-font-display", "--sb-font-title", "--font-display"]) || fontBody;
|
|
152
|
+
const field = Array.from(
|
|
153
|
+
doc.querySelectorAll('textarea, input[type="text"], input:not([type])')
|
|
154
|
+
).find((el) => !inOurs(el) && visible(el));
|
|
155
|
+
const px = (value) => {
|
|
156
|
+
const n = parseFloat(value ?? "");
|
|
157
|
+
return Number.isFinite(n) ? n : null;
|
|
158
|
+
};
|
|
159
|
+
const cornerOf = (style) => style ? px(style.borderTopLeftRadius || style.borderRadius) : null;
|
|
160
|
+
const buttonRadius = cornerOf(buttonStyle);
|
|
161
|
+
const fieldRadius = cornerOf(field ? styleOf(field) : null);
|
|
162
|
+
const radius = rootVar(doc, ["--fs-feedback-radius"]) || `${Math.min(fieldRadius ?? Math.min(buttonRadius ?? 16, 22), 28)}px`;
|
|
163
|
+
return {
|
|
164
|
+
ground: css(ground),
|
|
165
|
+
surface: css(dark ? mix(ground, WHITE, 0.07) : mix(ground, WHITE, 0.6)),
|
|
166
|
+
ink: css(ink),
|
|
167
|
+
inkDim: css(mix(ink, ground, 0.32)),
|
|
168
|
+
line: css(ink, 0.16),
|
|
169
|
+
fill: css(ink, 0.05),
|
|
170
|
+
cta: css([cta[0], cta[1], cta[2], 1]),
|
|
171
|
+
ctaInk: css([ctaInk[0], ctaInk[1], ctaInk[2], 1]),
|
|
172
|
+
fontBody,
|
|
173
|
+
fontDisplay,
|
|
174
|
+
displayWeight: headingStyle?.fontWeight || "500",
|
|
175
|
+
radius,
|
|
176
|
+
buttonRadius: buttonRadius === null ? "999px" : `${Math.min(buttonRadius, 999)}px`,
|
|
177
|
+
dark
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
var dataUrlBytes = (url) => Math.floor((url.length - url.indexOf(",") - 1) * 3 / 4);
|
|
181
|
+
function encodeScreenshot(canvas) {
|
|
182
|
+
let source = canvas;
|
|
183
|
+
for (let pass = 0; pass < 3; pass += 1) {
|
|
184
|
+
for (const quality of [0.82, 0.7, 0.58, 0.46, 0.34]) {
|
|
185
|
+
const url = source.toDataURL("image/jpeg", quality);
|
|
186
|
+
if (url.startsWith("data:image/jpeg") && dataUrlBytes(url) <= SCREENSHOT_MAX_BYTES)
|
|
187
|
+
return url;
|
|
188
|
+
}
|
|
189
|
+
const smaller = document.createElement("canvas");
|
|
190
|
+
smaller.width = Math.max(1, Math.round(source.width * 0.75));
|
|
191
|
+
smaller.height = Math.max(1, Math.round(source.height * 0.75));
|
|
192
|
+
const ctx = smaller.getContext("2d");
|
|
193
|
+
if (!ctx) return null;
|
|
194
|
+
ctx.drawImage(source, 0, 0, smaller.width, smaller.height);
|
|
195
|
+
source = smaller;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
async function captureViewport(ground) {
|
|
200
|
+
const { domToCanvas } = await import("modern-screenshot");
|
|
201
|
+
const width = window.innerWidth;
|
|
202
|
+
const height = window.innerHeight;
|
|
203
|
+
const scale = Math.min(
|
|
204
|
+
window.devicePixelRatio || 1,
|
|
205
|
+
SCREENSHOT_MAX_SIDE / Math.max(width, height)
|
|
206
|
+
);
|
|
207
|
+
const canvas = await domToCanvas(document.documentElement, {
|
|
208
|
+
width,
|
|
209
|
+
height,
|
|
210
|
+
scale,
|
|
211
|
+
backgroundColor: ground,
|
|
212
|
+
style: { transform: `translate(${-window.scrollX}px, ${-window.scrollY}px)` },
|
|
213
|
+
filter: (node) => !(node instanceof Element && node.hasAttribute(FEEDBACK_HOST_ATTR))
|
|
214
|
+
});
|
|
215
|
+
return encodeScreenshot(canvas);
|
|
216
|
+
}
|
|
217
|
+
var withTimeout = (work, ms) => new Promise((resolve) => {
|
|
218
|
+
const timer = setTimeout(() => resolve(null), ms);
|
|
219
|
+
work.then(
|
|
220
|
+
(value) => {
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
resolve(value);
|
|
223
|
+
},
|
|
224
|
+
() => {
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
resolve(null);
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
var BUG_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="7" width="8" height="12" rx="4"/><path d="M9.5 7a2.5 2.5 0 0 1 5 0M12 11v8M4 13h4M16 13h4M5 7l3.2 2.4M19 7l-3.2 2.4M5 20l3.2-2.6M19 20l-3.2-2.6"/></svg>';
|
|
231
|
+
var STYLE = `
|
|
232
|
+
:host { all: initial; }
|
|
233
|
+
* { box-sizing: border-box; }
|
|
234
|
+
.bug {
|
|
235
|
+
position: fixed; z-index: 2147483000;
|
|
236
|
+
right: calc(var(--fs-feedback-right, 14px) + env(safe-area-inset-right, 0px));
|
|
237
|
+
bottom: calc(var(--fs-feedback-bottom, 88px) + env(safe-area-inset-bottom, 0px));
|
|
238
|
+
width: 44px; height: 44px; border-radius: 50%; padding: 0;
|
|
239
|
+
display: grid; place-items: center; cursor: pointer;
|
|
240
|
+
background: var(--surface); color: var(--ink); border: 1px solid var(--line);
|
|
241
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
|
|
242
|
+
-webkit-tap-highlight-color: transparent;
|
|
243
|
+
}
|
|
244
|
+
.bug svg { width: 20px; height: 20px; }
|
|
245
|
+
.bug:focus-visible, .send:focus-visible, .link:focus-visible, textarea:focus-visible {
|
|
246
|
+
outline: 2px solid var(--ink); outline-offset: 2px;
|
|
247
|
+
}
|
|
248
|
+
.scrim { position: fixed; inset: 0; z-index: 2147483001; background: rgba(0, 0, 0, 0.42); }
|
|
249
|
+
.sheet {
|
|
250
|
+
position: fixed; left: 0; right: 0; bottom: 0; z-index: 2147483002;
|
|
251
|
+
margin: 0 auto; max-width: 480px;
|
|
252
|
+
background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-bottom: 0;
|
|
253
|
+
border-radius: calc(var(--radius) + 6px) calc(var(--radius) + 6px) 0 0;
|
|
254
|
+
padding: 12px 18px calc(20px + env(safe-area-inset-bottom, 0px));
|
|
255
|
+
display: flex; flex-direction: column; gap: 12px;
|
|
256
|
+
font-family: var(--font-body); font-size: 15px; line-height: 1.4;
|
|
257
|
+
}
|
|
258
|
+
.grab { width: 36px; height: 4px; border-radius: 2px; background: var(--line); align-self: center; }
|
|
259
|
+
h2 { margin: 0; font-family: var(--font-display); font-weight: var(--display-weight); font-size: 19px; line-height: 1.2; }
|
|
260
|
+
.shot { display: flex; gap: 12px; align-items: center; font-size: 13px; color: var(--ink-dim); }
|
|
261
|
+
.shot img { width: 50px; max-height: 108px; object-fit: cover; object-position: top; border-radius: 8px; border: 1px solid var(--line); }
|
|
262
|
+
.link { background: none; border: 0; padding: 0; font: inherit; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
|
|
263
|
+
textarea {
|
|
264
|
+
width: 100%; min-height: 84px; resize: none; font: inherit; font-size: 16px; color: var(--ink);
|
|
265
|
+
background: var(--fill); border: 1px solid var(--line); border-radius: var(--radius); padding: 13px 15px;
|
|
266
|
+
}
|
|
267
|
+
textarea::placeholder { color: var(--ink-dim); }
|
|
268
|
+
.send {
|
|
269
|
+
font: inherit; font-weight: 500; cursor: pointer; border: 0; padding: 14px;
|
|
270
|
+
background: var(--cta); color: var(--cta-ink); border-radius: var(--button-radius);
|
|
271
|
+
}
|
|
272
|
+
.send[disabled] { opacity: 0.5; cursor: default; }
|
|
273
|
+
.status { margin: 0; font-size: 13px; color: var(--ink-dim); min-height: 1.4em; }
|
|
274
|
+
[hidden] { display: none !important; }
|
|
275
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
276
|
+
.sheet { animation: rise 0.22s ease-out; }
|
|
277
|
+
@keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
278
|
+
}
|
|
279
|
+
`;
|
|
280
|
+
var mounted = null;
|
|
281
|
+
var feedbackOptedOut = (doc = document) => doc.documentElement.getAttribute("data-fs-feedback") === "off";
|
|
282
|
+
function mountFlowFeedback(options) {
|
|
283
|
+
if (typeof document === "undefined" || !document.body) return null;
|
|
284
|
+
if (mounted) return mounted;
|
|
285
|
+
if (feedbackOptedOut()) return null;
|
|
286
|
+
const host = document.createElement("div");
|
|
287
|
+
host.setAttribute(FEEDBACK_HOST_ATTR, "");
|
|
288
|
+
const root = host.attachShadow({ mode: "open" });
|
|
289
|
+
root.innerHTML = `<style>${STYLE}</style>
|
|
290
|
+
<button type="button" class="bug" aria-label="Send feedback on this page">${BUG_ICON}</button>
|
|
291
|
+
<div class="scrim" hidden></div>
|
|
292
|
+
<div class="sheet" role="dialog" aria-modal="true" aria-labelledby="fsf-title" hidden>
|
|
293
|
+
<div class="grab" aria-hidden="true"></div>
|
|
294
|
+
<h2 id="fsf-title">Feedback on this page</h2>
|
|
295
|
+
<div class="shot" hidden><img alt="Screenshot of this page" /><span><span>Screenshot attached</span><br /><button type="button" class="link remove">Remove screenshot</button></span></div>
|
|
296
|
+
<textarea aria-labelledby="fsf-title" placeholder="What should change?" maxlength="2000"></textarea>
|
|
297
|
+
<p class="status" role="status" aria-live="polite"></p>
|
|
298
|
+
<button type="button" class="send" disabled>Send feedback</button>
|
|
299
|
+
</div>`;
|
|
300
|
+
const q = (selector) => root.querySelector(selector);
|
|
301
|
+
const bug = q(".bug");
|
|
302
|
+
const scrim = q(".scrim");
|
|
303
|
+
const sheet = q(".sheet");
|
|
304
|
+
const shot = q(".shot");
|
|
305
|
+
const img = q(".shot img");
|
|
306
|
+
const remove = q(".remove");
|
|
307
|
+
const text = q("textarea");
|
|
308
|
+
const status = q(".status");
|
|
309
|
+
const send = q(".send");
|
|
310
|
+
let theme = readFlowTheme();
|
|
311
|
+
let screenshot = null;
|
|
312
|
+
let busy = false;
|
|
313
|
+
let closeTimer = null;
|
|
314
|
+
const paint = () => {
|
|
315
|
+
theme = readFlowTheme();
|
|
316
|
+
const vars = {
|
|
317
|
+
"--surface": theme.surface,
|
|
318
|
+
"--ink": theme.ink,
|
|
319
|
+
"--ink-dim": theme.inkDim,
|
|
320
|
+
"--line": theme.line,
|
|
321
|
+
"--fill": theme.fill,
|
|
322
|
+
"--cta": theme.cta,
|
|
323
|
+
"--cta-ink": theme.ctaInk,
|
|
324
|
+
"--font-body": theme.fontBody,
|
|
325
|
+
"--font-display": theme.fontDisplay,
|
|
326
|
+
"--display-weight": theme.displayWeight,
|
|
327
|
+
"--radius": theme.radius,
|
|
328
|
+
"--button-radius": theme.buttonRadius
|
|
329
|
+
};
|
|
330
|
+
for (const [name, value] of Object.entries(vars)) host.style.setProperty(name, value);
|
|
331
|
+
host.style.colorScheme = theme.dark ? "dark" : "light";
|
|
332
|
+
};
|
|
333
|
+
const sync = () => {
|
|
334
|
+
send.disabled = busy || text.value.trim().length === 0;
|
|
335
|
+
};
|
|
336
|
+
const setShot = (url) => {
|
|
337
|
+
screenshot = url;
|
|
338
|
+
shot.hidden = url === null;
|
|
339
|
+
if (url) img.src = url;
|
|
340
|
+
else img.removeAttribute("src");
|
|
341
|
+
};
|
|
342
|
+
const close = () => {
|
|
343
|
+
if (closeTimer) clearTimeout(closeTimer);
|
|
344
|
+
closeTimer = null;
|
|
345
|
+
sheet.hidden = true;
|
|
346
|
+
scrim.hidden = true;
|
|
347
|
+
bug.hidden = false;
|
|
348
|
+
setShot(null);
|
|
349
|
+
text.value = "";
|
|
350
|
+
status.textContent = "";
|
|
351
|
+
busy = false;
|
|
352
|
+
sync();
|
|
353
|
+
};
|
|
354
|
+
const open = async () => {
|
|
355
|
+
if (!sheet.hidden || busy) return;
|
|
356
|
+
busy = true;
|
|
357
|
+
paint();
|
|
358
|
+
bug.hidden = true;
|
|
359
|
+
const capture = options.capture ?? (() => captureViewport(theme.ground));
|
|
360
|
+
const url = await withTimeout(Promise.resolve().then(capture), CAPTURE_TIMEOUT_MS);
|
|
361
|
+
busy = false;
|
|
362
|
+
setShot(url);
|
|
363
|
+
scrim.hidden = false;
|
|
364
|
+
sheet.hidden = false;
|
|
365
|
+
sync();
|
|
366
|
+
text.focus();
|
|
367
|
+
};
|
|
368
|
+
const submit = async () => {
|
|
369
|
+
const message = text.value.trim();
|
|
370
|
+
if (!message || busy) return;
|
|
371
|
+
busy = true;
|
|
372
|
+
sync();
|
|
373
|
+
status.textContent = "";
|
|
374
|
+
try {
|
|
375
|
+
await options.submit({
|
|
376
|
+
message,
|
|
377
|
+
...screenshot ? { screenshot } : {},
|
|
378
|
+
...document.body.dataset.pageKey ? { pageKey: document.body.dataset.pageKey } : {},
|
|
379
|
+
pagePath: window.location.pathname.slice(-200),
|
|
380
|
+
viewport: { width: window.innerWidth, height: window.innerHeight }
|
|
381
|
+
});
|
|
382
|
+
status.textContent = "Sent.";
|
|
383
|
+
closeTimer = setTimeout(() => {
|
|
384
|
+
close();
|
|
385
|
+
bug.focus();
|
|
386
|
+
}, 1200);
|
|
387
|
+
} catch {
|
|
388
|
+
busy = false;
|
|
389
|
+
status.textContent = "That did not send. Try again.";
|
|
390
|
+
sync();
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
bug.addEventListener("click", () => void open());
|
|
394
|
+
scrim.addEventListener("click", close);
|
|
395
|
+
remove.addEventListener("click", () => setShot(null));
|
|
396
|
+
text.addEventListener("input", sync);
|
|
397
|
+
send.addEventListener("click", () => void submit());
|
|
398
|
+
sheet.addEventListener("keydown", (event) => {
|
|
399
|
+
if (event.key === "Escape") {
|
|
400
|
+
close();
|
|
401
|
+
bug.focus();
|
|
402
|
+
}
|
|
403
|
+
event.stopPropagation();
|
|
404
|
+
});
|
|
405
|
+
document.body.appendChild(host);
|
|
406
|
+
paint();
|
|
407
|
+
options.onReady?.();
|
|
408
|
+
mounted = {
|
|
409
|
+
host,
|
|
410
|
+
refresh: () => {
|
|
411
|
+
if (sheet.hidden) paint();
|
|
412
|
+
},
|
|
413
|
+
open,
|
|
414
|
+
close,
|
|
415
|
+
unmount: () => {
|
|
416
|
+
if (closeTimer) clearTimeout(closeTimer);
|
|
417
|
+
host.remove();
|
|
418
|
+
mounted = null;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
return mounted;
|
|
422
|
+
}
|
|
423
|
+
var unmountFlowFeedback = () => mounted?.unmount();
|
|
424
|
+
|
|
42
425
|
// src/session.ts
|
|
43
426
|
function readSessionLaunchParams() {
|
|
44
427
|
if (typeof window === "undefined") return null;
|
|
@@ -105,7 +488,8 @@ function toStateView(s) {
|
|
|
105
488
|
myContribution: s.myWishlist ?? null,
|
|
106
489
|
doc: s.plan ?? null,
|
|
107
490
|
artifactId: s.artifactId,
|
|
108
|
-
extension: s.extension ?? null
|
|
491
|
+
extension: s.extension ?? null,
|
|
492
|
+
viewer: s.viewer ? { userId: s.viewer.userId, guest: s.viewer.guest, claims: s.viewer.claims ?? null } : null
|
|
109
493
|
};
|
|
110
494
|
}
|
|
111
495
|
var parentOpener = (hostOrigin) => {
|
|
@@ -122,13 +506,16 @@ var HttpSessionClient = class {
|
|
|
122
506
|
__publicField(this, "slug");
|
|
123
507
|
__publicField(this, "webBase");
|
|
124
508
|
__publicField(this, "mobileBase");
|
|
509
|
+
__publicField(this, "latestClaims", null);
|
|
125
510
|
__publicField(this, "apiBase");
|
|
126
511
|
__publicField(this, "tokenPromise");
|
|
127
512
|
__publicField(this, "opener");
|
|
513
|
+
__publicField(this, "hostOrigin");
|
|
128
514
|
this.slug = params.slug;
|
|
129
515
|
this.webBase = params.webBase;
|
|
130
516
|
this.mobileBase = params.mobileBase ?? null;
|
|
131
517
|
this.apiBase = params.apiBase;
|
|
518
|
+
this.hostOrigin = hostOriginOf(params);
|
|
132
519
|
this.opener = opener ?? parentOpener(hostOriginOf(params));
|
|
133
520
|
this.tokenPromise = this.acquireTicket(params).then((t) => this.redeem(t));
|
|
134
521
|
}
|
|
@@ -162,7 +549,31 @@ var HttpSessionClient = class {
|
|
|
162
549
|
}
|
|
163
550
|
const json = await res.json();
|
|
164
551
|
const data = json.data;
|
|
165
|
-
|
|
552
|
+
const wire = data.state ?? data;
|
|
553
|
+
const view = toStateView(wire);
|
|
554
|
+
this.latestClaims = view.viewer?.claims ?? null;
|
|
555
|
+
this.syncFeedback(wire.feedbackEnabled === true);
|
|
556
|
+
return view;
|
|
557
|
+
}
|
|
558
|
+
get claims() {
|
|
559
|
+
return this.latestClaims;
|
|
560
|
+
}
|
|
561
|
+
/** Flow feedback: every state read says whether the bug icon is on for this ring. On, the
|
|
562
|
+
* icon mounts once and re-reads the page's look on each read; off, it leaves. */
|
|
563
|
+
syncFeedback(enabled) {
|
|
564
|
+
if (typeof document === "undefined") return;
|
|
565
|
+
if (!enabled) {
|
|
566
|
+
unmountFlowFeedback();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const handle = mountFlowFeedback({
|
|
570
|
+
submit: (report) => this.request("feedback", report),
|
|
571
|
+
onReady: () => {
|
|
572
|
+
if (window.parent !== window)
|
|
573
|
+
window.parent.postMessage({ type: "flowstate:feedback-ready" }, this.hostOrigin);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
handle?.refresh();
|
|
166
577
|
}
|
|
167
578
|
join() {
|
|
168
579
|
return this.call("/enter", { method: "POST" });
|
|
@@ -204,15 +615,37 @@ var HttpSessionClient = class {
|
|
|
204
615
|
body: JSON.stringify(body ?? {})
|
|
205
616
|
});
|
|
206
617
|
}
|
|
207
|
-
async request(verb, body) {
|
|
208
|
-
const
|
|
618
|
+
async request(verb, body, options) {
|
|
619
|
+
const signal = options?.signal;
|
|
620
|
+
if (signal?.aborted) {
|
|
621
|
+
void this.tokenPromise.catch(() => {
|
|
622
|
+
});
|
|
623
|
+
signal.throwIfAborted();
|
|
624
|
+
}
|
|
625
|
+
let onAbort;
|
|
626
|
+
let token;
|
|
627
|
+
try {
|
|
628
|
+
token = await (signal ? Promise.race([
|
|
629
|
+
this.tokenPromise,
|
|
630
|
+
new Promise((_resolve, reject) => {
|
|
631
|
+
onAbort = () => reject(signal.reason);
|
|
632
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
633
|
+
})
|
|
634
|
+
]) : this.tokenPromise);
|
|
635
|
+
} finally {
|
|
636
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
637
|
+
}
|
|
209
638
|
const res = await fetch(`${this.apiBase}/api/w/${this.slug}/${verb.replace(/^\//, "")}`, {
|
|
639
|
+
signal,
|
|
210
640
|
method: "POST",
|
|
211
641
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
212
642
|
body: JSON.stringify(body ?? {})
|
|
213
643
|
});
|
|
214
644
|
if (!res.ok) {
|
|
215
|
-
const errBody = await res.json().catch(() =>
|
|
645
|
+
const errBody = await res.json().catch(() => {
|
|
646
|
+
signal?.throwIfAborted();
|
|
647
|
+
return {};
|
|
648
|
+
});
|
|
216
649
|
throw new Error(errBody.message ?? `session service ${res.status}`);
|
|
217
650
|
}
|
|
218
651
|
const json = await res.json();
|
|
@@ -222,13 +655,20 @@ var HttpSessionClient = class {
|
|
|
222
655
|
this.opener(url);
|
|
223
656
|
}
|
|
224
657
|
revealUrl(artifactId) {
|
|
225
|
-
return `${this.webBase}/a/${artifactId}`;
|
|
658
|
+
return this.mobileBase ? `${this.mobileBase}/?a=${artifactId}` : `${this.webBase}/a/${artifactId}`;
|
|
226
659
|
}
|
|
227
660
|
};
|
|
228
661
|
function createMockSession(opts = {}) {
|
|
229
662
|
const palette = ["#8b7ef8", "#f8a34d", "#4dc9a4", "#f26d8f", "#5aa9e6"];
|
|
230
663
|
const members = [
|
|
231
|
-
{
|
|
664
|
+
{
|
|
665
|
+
userId: "mock-me",
|
|
666
|
+
name: opts.me ?? "You",
|
|
667
|
+
color: palette[0],
|
|
668
|
+
sealed: false,
|
|
669
|
+
isMe: true,
|
|
670
|
+
isHost: true
|
|
671
|
+
},
|
|
232
672
|
...(opts.others ?? []).map((name, i) => ({
|
|
233
673
|
userId: `mock-${i + 1}`,
|
|
234
674
|
name,
|
|
@@ -261,12 +701,14 @@ function createMockSession(opts = {}) {
|
|
|
261
701
|
myContribution,
|
|
262
702
|
doc,
|
|
263
703
|
artifactId: doc ? "mock-artifact" : null,
|
|
264
|
-
extension: null
|
|
704
|
+
extension: null,
|
|
705
|
+
viewer: { userId: "mock-me", guest: false, claims: opts.claims ?? null }
|
|
265
706
|
};
|
|
266
707
|
};
|
|
267
708
|
return {
|
|
268
709
|
slug: "mock-session",
|
|
269
710
|
webBase: "http://localhost:14321",
|
|
711
|
+
claims: opts.claims ?? null,
|
|
270
712
|
async join() {
|
|
271
713
|
return view();
|
|
272
714
|
},
|
|
@@ -887,12 +1329,16 @@ export {
|
|
|
887
1329
|
applyWidgetTheme,
|
|
888
1330
|
createMockSession,
|
|
889
1331
|
createServiceClient,
|
|
1332
|
+
feedbackOptedOut,
|
|
890
1333
|
getInitialTheme,
|
|
891
1334
|
hostOriginOf,
|
|
892
1335
|
initSession,
|
|
893
1336
|
initWidgetTheme,
|
|
1337
|
+
mountFlowFeedback,
|
|
894
1338
|
onArtifactIngested,
|
|
895
1339
|
onThemeChange,
|
|
896
|
-
|
|
1340
|
+
readFlowTheme,
|
|
1341
|
+
readSessionLaunchParams,
|
|
1342
|
+
unmountFlowFeedback
|
|
897
1343
|
};
|
|
898
1344
|
//# sourceMappingURL=index.mjs.map
|