@alliance-droid/status-feedback-system 2.0.0 → 2.1.0
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.
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
} from '@alliance-droid/svelte-component-library';
|
|
12
12
|
import DevContextMenu from './dev/DevContextMenu.svelte';
|
|
13
13
|
import DevOnboardingToast from './dev/DevOnboardingToast.svelte';
|
|
14
|
-
import { captureScreenshot, type ScreenshotProvider } from './dev/screenshot.js';
|
|
15
14
|
import {
|
|
16
15
|
detectEnvironment,
|
|
17
16
|
getEnvironmentName,
|
|
@@ -40,13 +39,10 @@
|
|
|
40
39
|
*/
|
|
41
40
|
userEmail: string;
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* screenshot capture to a server endpoint (e.g. Puppeteer / screenshot-service).
|
|
46
|
-
*
|
|
47
|
-
* See `createServerScreenshotProvider()` for a ready-made helper.
|
|
42
|
+
* Endpoint on the consumer app that issues render tokens.
|
|
43
|
+
* Default: '/svc/internal/issue-render-token'
|
|
48
44
|
*/
|
|
49
|
-
|
|
45
|
+
renderTokenEndpoint?: string;
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
let {
|
|
@@ -57,7 +53,7 @@
|
|
|
57
53
|
showOnboarding = true,
|
|
58
54
|
boardUrl,
|
|
59
55
|
userEmail,
|
|
60
|
-
|
|
56
|
+
renderTokenEndpoint = '/svc/internal/issue-render-token',
|
|
61
57
|
}: Props = $props();
|
|
62
58
|
|
|
63
59
|
// Dev-mode warning when userEmail is empty
|
|
@@ -151,9 +147,9 @@
|
|
|
151
147
|
let submitting = $state(false);
|
|
152
148
|
let submitResult = $state<'success' | 'error' | null>(null);
|
|
153
149
|
|
|
154
|
-
let
|
|
155
|
-
let
|
|
156
|
-
let
|
|
150
|
+
let renderContext = $state<{ url: string; scrollX: number; scrollY: number; renderToken: string; issuer: string } | null>(null);
|
|
151
|
+
let fetchingRenderToken = $state(false);
|
|
152
|
+
let renderTokenFailed = $state(false);
|
|
157
153
|
|
|
158
154
|
// Drag state
|
|
159
155
|
let isDragging = $state(false);
|
|
@@ -249,16 +245,36 @@
|
|
|
249
245
|
if (e.key === 'Escape') {
|
|
250
246
|
menuOpen = false;
|
|
251
247
|
formOpen = false;
|
|
252
|
-
|
|
248
|
+
renderContext = null;
|
|
253
249
|
}
|
|
254
250
|
}
|
|
255
251
|
|
|
256
|
-
async function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
252
|
+
async function fetchRenderToken() {
|
|
253
|
+
fetchingRenderToken = true;
|
|
254
|
+
renderTokenFailed = false;
|
|
255
|
+
try {
|
|
256
|
+
const url = window.location.href;
|
|
257
|
+
const scrollX = Math.round(window.scrollX);
|
|
258
|
+
const scrollY = Math.round(window.scrollY);
|
|
259
|
+
const res = await fetch(renderTokenEndpoint, {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
credentials: 'include',
|
|
262
|
+
headers: { 'Content-Type': 'application/json' },
|
|
263
|
+
body: JSON.stringify({ url, scrollX, scrollY }),
|
|
264
|
+
});
|
|
265
|
+
if (!res.ok) {
|
|
266
|
+
renderTokenFailed = true;
|
|
267
|
+
console.warn('[DevFeedback] Render token fetch failed:', res.status);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const { token } = (await res.json()) as { token: string };
|
|
271
|
+
renderContext = { url, scrollX, scrollY, renderToken: token, issuer: new URL(url).origin };
|
|
272
|
+
} catch (err) {
|
|
273
|
+
renderTokenFailed = true;
|
|
274
|
+
console.warn('[DevFeedback] Render token fetch error:', err);
|
|
275
|
+
} finally {
|
|
276
|
+
fetchingRenderToken = false;
|
|
277
|
+
}
|
|
262
278
|
}
|
|
263
279
|
|
|
264
280
|
function openForm(type: 'bug' | 'feature') {
|
|
@@ -273,9 +289,9 @@
|
|
|
273
289
|
modalY = 0;
|
|
274
290
|
|
|
275
291
|
if (type === 'bug') {
|
|
276
|
-
|
|
292
|
+
fetchRenderToken();
|
|
277
293
|
} else {
|
|
278
|
-
|
|
294
|
+
renderContext = null;
|
|
279
295
|
}
|
|
280
296
|
}
|
|
281
297
|
|
|
@@ -302,8 +318,11 @@
|
|
|
302
318
|
metadata.consoleErrors = [...consoleErrors];
|
|
303
319
|
}
|
|
304
320
|
|
|
305
|
-
if (
|
|
306
|
-
metadata.
|
|
321
|
+
if (renderContext) {
|
|
322
|
+
metadata.renderToken = renderContext.renderToken;
|
|
323
|
+
metadata.issuer = renderContext.issuer;
|
|
324
|
+
metadata.scrollX = renderContext.scrollX;
|
|
325
|
+
metadata.scrollY = renderContext.scrollY;
|
|
307
326
|
}
|
|
308
327
|
|
|
309
328
|
const gitCommit = getEnvVar('VERCEL_GIT_COMMIT_SHA');
|
|
@@ -326,7 +345,7 @@
|
|
|
326
345
|
setTimeout(() => {
|
|
327
346
|
formOpen = false;
|
|
328
347
|
submitResult = null;
|
|
329
|
-
|
|
348
|
+
renderContext = null;
|
|
330
349
|
}, 2000);
|
|
331
350
|
} catch {
|
|
332
351
|
submitResult = 'error';
|
|
@@ -451,40 +470,30 @@
|
|
|
451
470
|
{/if}
|
|
452
471
|
</div>
|
|
453
472
|
|
|
454
|
-
<!--
|
|
455
|
-
{#if
|
|
473
|
+
<!-- Render token status -->
|
|
474
|
+
{#if fetchingRenderToken}
|
|
456
475
|
<div class="flex items-center gap-2 text-sm text-foreground-tertiary">
|
|
457
|
-
<Spinner />
|
|
476
|
+
<Spinner /> Preparing screenshot...
|
|
458
477
|
</div>
|
|
459
|
-
{:else if
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
<button
|
|
467
|
-
class="text-xs text-primary hover:underline bg-transparent border-none cursor-pointer p-0"
|
|
468
|
-
onclick={doScreenshot}
|
|
469
|
-
>
|
|
470
|
-
Retry
|
|
471
|
-
</button>
|
|
472
|
-
</div>
|
|
473
|
-
{:else if screenshotData}
|
|
474
|
-
<div class="relative">
|
|
475
|
-
<img
|
|
476
|
-
src={screenshotData}
|
|
477
|
-
alt="Screenshot"
|
|
478
|
-
class="w-full rounded-md border border-border max-h-40 object-cover object-top"
|
|
479
|
-
/>
|
|
478
|
+
{:else if renderTokenFailed}
|
|
479
|
+
<div class="rounded-md border border-border bg-surface p-3">
|
|
480
|
+
<div class="flex items-center gap-2 text-sm text-foreground-secondary mb-1">
|
|
481
|
+
<i class="fa-solid fa-image-slash" aria-hidden="true"></i>
|
|
482
|
+
<span>Screenshot setup failed</span>
|
|
483
|
+
</div>
|
|
484
|
+
<p class="text-xs text-foreground-tertiary mb-2">Your feedback will be submitted without a screenshot.</p>
|
|
480
485
|
<button
|
|
481
|
-
class="
|
|
482
|
-
onclick={
|
|
483
|
-
aria-label="Remove screenshot"
|
|
486
|
+
class="text-xs text-primary hover:underline bg-transparent border-none cursor-pointer p-0"
|
|
487
|
+
onclick={fetchRenderToken}
|
|
484
488
|
>
|
|
485
|
-
|
|
489
|
+
Retry
|
|
486
490
|
</button>
|
|
487
491
|
</div>
|
|
492
|
+
{:else if renderContext}
|
|
493
|
+
<div class="rounded-md border border-border bg-surface-secondary p-2 flex items-center gap-2 text-sm text-foreground-secondary">
|
|
494
|
+
<i class="fa-solid fa-camera" aria-hidden="true"></i>
|
|
495
|
+
<span>Screenshot will be captured server-side on submit</span>
|
|
496
|
+
</div>
|
|
488
497
|
{/if}
|
|
489
498
|
</div>
|
|
490
499
|
{/if}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type ScreenshotProvider } from './dev/screenshot.js';
|
|
2
1
|
interface Props {
|
|
3
2
|
apiUrl: string;
|
|
4
3
|
apiKey: string;
|
|
@@ -19,13 +18,10 @@ interface Props {
|
|
|
19
18
|
*/
|
|
20
19
|
userEmail: string;
|
|
21
20
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* screenshot capture to a server endpoint (e.g. Puppeteer / screenshot-service).
|
|
25
|
-
*
|
|
26
|
-
* See `createServerScreenshotProvider()` for a ready-made helper.
|
|
21
|
+
* Endpoint on the consumer app that issues render tokens.
|
|
22
|
+
* Default: '/svc/internal/issue-render-token'
|
|
27
23
|
*/
|
|
28
|
-
|
|
24
|
+
renderTokenEndpoint?: string;
|
|
29
25
|
}
|
|
30
26
|
declare const DevFeedback: import("svelte").Component<Props, {}, "">;
|
|
31
27
|
type DevFeedback = ReturnType<typeof DevFeedback>;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export type { Project, System, Incident, IncidentUpdate, Feedback, FeedbackResponse, FeedbackVisibility, StatusHistoryEntry, SystemStatus, IncidentStatus, IncidentSeverity, FeedbackStatus, ApiResponse, } from './types.js';
|
|
11
11
|
export { deriveOverallStatus } from './utils.js';
|
|
12
|
-
export { createServerScreenshotProvider } from './components/dev/screenshot.js';
|
|
13
|
-
export type { ScreenshotProvider } from './components/dev/screenshot.js';
|
|
14
12
|
export { createStatusClient } from './api.js';
|
|
15
13
|
export type { StatusClient, StatusClientConfig, StatusData, IncidentData, UptimeData, BoardData, BoardItem, BoardCategory } from './api.js';
|
|
16
14
|
export { default as StatusPage } from './components/StatusPage.svelte';
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
// ── Utilities ──
|
|
11
11
|
export { deriveOverallStatus } from './utils.js';
|
|
12
|
-
export { createServerScreenshotProvider } from './components/dev/screenshot.js';
|
|
13
12
|
// ── API Client ──
|
|
14
13
|
export { createStatusClient } from './api.js';
|
|
15
14
|
// ── Client Components ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alliance-droid/status-feedback-system",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Drop-in system status pages and user feedback forms for SvelteKit apps",
|
|
6
6
|
"svelte": "./dist/index.js",
|
|
@@ -18,30 +18,32 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "svelte-package -o dist && publint",
|
|
21
|
-
"lint": "
|
|
21
|
+
"lint": "eslint src",
|
|
22
22
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
23
23
|
"test": "vitest run",
|
|
24
24
|
"test:watch": "vitest",
|
|
25
25
|
"prepublishOnly": "npm run build"
|
|
26
26
|
},
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"html2canvas": "^1.4.1"
|
|
29
|
-
},
|
|
30
27
|
"peerDependencies": {
|
|
31
28
|
"@alliance-droid/svelte-component-library": ">=1.0.0",
|
|
32
29
|
"svelte": "^5.0.0"
|
|
33
30
|
},
|
|
34
31
|
"devDependencies": {
|
|
35
32
|
"@alliance-droid/svelte-component-library": "^1.0.5",
|
|
33
|
+
"@eslint/js": "^10.0.1",
|
|
36
34
|
"@sveltejs/package": "^2.3.0",
|
|
37
35
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
|
38
36
|
"@testing-library/jest-dom": "^6.9.1",
|
|
39
37
|
"@testing-library/svelte": "^5.3.1",
|
|
38
|
+
"eslint": "^10.4.0",
|
|
39
|
+
"eslint-plugin-svelte": "^3.17.1",
|
|
40
|
+
"globals": "^17.6.0",
|
|
40
41
|
"jsdom": "^29.0.0",
|
|
41
42
|
"publint": "^0.3.0",
|
|
42
43
|
"svelte": "^5.48.0",
|
|
43
44
|
"svelte-check": "^4.3.0",
|
|
44
45
|
"typescript": "^5.9.0",
|
|
46
|
+
"typescript-eslint": "^8.59.4",
|
|
45
47
|
"vite": "^7.3.0",
|
|
46
48
|
"vitest": "^4.0.0"
|
|
47
49
|
},
|
|
@@ -61,4 +63,4 @@
|
|
|
61
63
|
"type": "git",
|
|
62
64
|
"url": "git+https://alliance.ghe.com/alliance/status-feedback-system.git"
|
|
63
65
|
}
|
|
64
|
-
}
|
|
66
|
+
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screenshot capture utility for DevFeedback.
|
|
3
|
-
*
|
|
4
|
-
* Supports two modes:
|
|
5
|
-
* 1. Custom provider — consumer supplies a `ScreenshotProvider` callback
|
|
6
|
-
* (e.g. server-side capture via Puppeteer / screenshot-service).
|
|
7
|
-
* 2. html2canvas fallback — dynamically imports html2canvas if no provider
|
|
8
|
-
* is given (html2canvas is an optional peer dependency).
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* A function that captures a screenshot and returns a data URL (or null on failure).
|
|
12
|
-
* Consumers can implement this however they like — server endpoint, different
|
|
13
|
-
* client library, canvas API, etc.
|
|
14
|
-
*/
|
|
15
|
-
export type ScreenshotProvider = () => Promise<string | null>;
|
|
16
|
-
/**
|
|
17
|
-
* Capture a screenshot using the given provider, or fall back to html2canvas.
|
|
18
|
-
*/
|
|
19
|
-
export declare function captureScreenshot(provider?: ScreenshotProvider): Promise<string | null>;
|
|
20
|
-
/**
|
|
21
|
-
* Create a screenshot provider that POSTs to a server endpoint.
|
|
22
|
-
*
|
|
23
|
-
* The endpoint receives `{ url, viewport }` and should return
|
|
24
|
-
* `{ screenshot: "data:image/..." }` (or any JSON with a `screenshot` string field).
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```ts
|
|
28
|
-
* <DevFeedback
|
|
29
|
-
* screenshotProvider={createServerScreenshotProvider('/api/screenshot')}
|
|
30
|
-
* ...
|
|
31
|
-
* />
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
export declare function createServerScreenshotProvider(endpoint: string, options?: {
|
|
35
|
-
headers?: Record<string, string>;
|
|
36
|
-
}): ScreenshotProvider;
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screenshot capture utility for DevFeedback.
|
|
3
|
-
*
|
|
4
|
-
* Supports two modes:
|
|
5
|
-
* 1. Custom provider — consumer supplies a `ScreenshotProvider` callback
|
|
6
|
-
* (e.g. server-side capture via Puppeteer / screenshot-service).
|
|
7
|
-
* 2. html2canvas fallback — dynamically imports html2canvas if no provider
|
|
8
|
-
* is given (html2canvas is an optional peer dependency).
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Capture a screenshot using the given provider, or fall back to html2canvas.
|
|
12
|
-
*/
|
|
13
|
-
export async function captureScreenshot(provider) {
|
|
14
|
-
if (provider) {
|
|
15
|
-
try {
|
|
16
|
-
return await provider();
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
console.error('[DevFeedback] Screenshot provider failed:', err);
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return captureWithHtml2Canvas();
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Create a screenshot provider that POSTs to a server endpoint.
|
|
27
|
-
*
|
|
28
|
-
* The endpoint receives `{ url, viewport }` and should return
|
|
29
|
-
* `{ screenshot: "data:image/..." }` (or any JSON with a `screenshot` string field).
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```ts
|
|
33
|
-
* <DevFeedback
|
|
34
|
-
* screenshotProvider={createServerScreenshotProvider('/api/screenshot')}
|
|
35
|
-
* ...
|
|
36
|
-
* />
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export function createServerScreenshotProvider(endpoint, options) {
|
|
40
|
-
return async () => {
|
|
41
|
-
const res = await fetch(endpoint, {
|
|
42
|
-
method: 'POST',
|
|
43
|
-
headers: {
|
|
44
|
-
'Content-Type': 'application/json',
|
|
45
|
-
...options?.headers,
|
|
46
|
-
},
|
|
47
|
-
body: JSON.stringify({
|
|
48
|
-
url: window.location.href,
|
|
49
|
-
viewport: {
|
|
50
|
-
width: window.innerWidth,
|
|
51
|
-
height: window.innerHeight,
|
|
52
|
-
},
|
|
53
|
-
}),
|
|
54
|
-
});
|
|
55
|
-
if (!res.ok)
|
|
56
|
-
return null;
|
|
57
|
-
const data = await res.json();
|
|
58
|
-
return data.screenshot ?? null;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/** html2canvas fallback — gracefully degrades if not installed. */
|
|
62
|
-
async function captureWithHtml2Canvas() {
|
|
63
|
-
try {
|
|
64
|
-
const html2canvas = (await import('html2canvas')).default;
|
|
65
|
-
const canvas = await html2canvas(document.body, {
|
|
66
|
-
logging: false,
|
|
67
|
-
useCORS: true,
|
|
68
|
-
scale: 1,
|
|
69
|
-
width: window.innerWidth,
|
|
70
|
-
height: window.innerHeight,
|
|
71
|
-
windowWidth: window.innerWidth,
|
|
72
|
-
windowHeight: window.innerHeight,
|
|
73
|
-
ignoreElements: (element) => {
|
|
74
|
-
// Skip elements that might have unsupported CSS (oklab, oklch, color-mix)
|
|
75
|
-
const style = window.getComputedStyle(element);
|
|
76
|
-
const hasUnsupportedColor = [
|
|
77
|
-
style.color,
|
|
78
|
-
style.backgroundColor,
|
|
79
|
-
style.borderColor,
|
|
80
|
-
].some(color => color && (color.includes('oklab') || color.includes('oklch') || color.includes('color-mix')));
|
|
81
|
-
return hasUnsupportedColor;
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
return canvas.toDataURL('image/jpeg', 0.7);
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
console.error('[DevFeedback] Screenshot capture failed:', err);
|
|
88
|
-
if (err instanceof Error && err.message.includes('oklab')) {
|
|
89
|
-
console.warn('[DevFeedback] Your CSS uses oklab/oklch colors which html2canvas doesn\'t support. Screenshot skipped.');
|
|
90
|
-
}
|
|
91
|
-
// html2canvas failed (unsupported CSS, cross-origin iframe, etc.)
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
}
|