@foir/sdk 0.7.0 → 0.8.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.
- package/CHANGELOG.md +805 -4
- package/README.md +46 -15
- package/dist/auth/auth.d.ts +104 -1
- package/dist/auth/index.d.ts +12 -6
- package/dist/auth/operations.d.ts +85 -64
- package/dist/auth/ops.d.ts +99 -0
- package/dist/auth.js +775 -106
- package/dist/chunk-IGNLWT3H.js +35 -0
- package/dist/chunk-OHYVKTGB.js +45 -0
- package/dist/chunk-TAL5H6VA.js +39 -0
- package/dist/client.js +25 -31
- package/dist/codegen/block-registry.d.ts +13 -19
- package/dist/codegen/block-registry.js +3 -31
- package/dist/codegen/block-scaffold.d.ts +12 -21
- package/dist/codegen/block-scaffold.js +3 -41
- package/dist/codegen/index.d.ts +9 -4
- package/dist/codegen/index.js +7 -11
- package/dist/codegen/item-registry.d.ts +28 -0
- package/dist/codegen/item-registry.js +7 -0
- package/dist/codegen/item-scaffold.d.ts +29 -0
- package/dist/codegen/item-scaffold.js +7 -0
- package/dist/codegen/preset.d.ts +14 -7
- package/dist/codegen/shared.d.ts +20 -9
- package/dist/content/content-renderer.d.ts +90 -24
- package/dist/content/index.d.ts +13 -7
- package/dist/content/richtext.d.ts +66 -18
- package/dist/content.js +227 -80
- package/dist/data/builtins.d.ts +302 -92
- package/dist/data/client.d.ts +97 -25
- package/dist/data/document.d.ts +251 -0
- package/dist/data/index.d.ts +38 -19
- package/dist/data/manifest.d.ts +266 -3
- package/dist/data/schema.d.ts +78 -8
- package/dist/data/selection.d.ts +231 -37
- package/dist/data/subjects.d.ts +221 -0
- package/dist/data/subscription-document.d.ts +75 -0
- package/dist/data/subscriptions.d.ts +181 -0
- package/dist/data/transport.d.ts +255 -37
- package/dist/data/ws-transport.d.ts +197 -0
- package/dist/data.js +1419 -45
- package/dist/graphql/cdn-route.d.ts +12 -28
- package/dist/media/index.d.ts +6 -2
- package/dist/media/media.d.ts +2 -2
- package/dist/media/screen-capture.d.ts +81 -0
- package/dist/media.js +97 -0
- package/dist/next.js +663 -182
- package/dist/push.js +700 -90
- package/dist/react/FoirFeedback.d.ts +62 -0
- package/dist/react/feedback-notes.d.ts +101 -0
- package/dist/react/index.d.ts +7 -1
- package/dist/react.js +1272 -194
- package/dist/realtime.js +1 -1
- package/dist/server/events-client.d.ts +279 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server.js +298 -0
- package/dist/transport/foir-http.d.ts +23 -6
- package/dist/transport/persisted-query.d.ts +68 -0
- package/dist/transport/retry.d.ts +1 -1
- package/package.json +13 -1
- package/dist/chunk-ADO5FJNM.js +0 -30
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
2
2
|
import { type FoirEnv } from '../transport/foir-http';
|
|
3
|
+
/**
|
|
4
|
+
* The shared read lane, for `@foir/sdk/client`.
|
|
5
|
+
*
|
|
6
|
+
* The mechanics — the URL, the hash, the handshake codes — moved to
|
|
7
|
+
* `../transport/persisted-query`, which has no `graphql` import, so
|
|
8
|
+
* `@foir/sdk/data` can build the same requests without dragging the parser into
|
|
9
|
+
* a storefront bundle. They are re-exported unchanged; this module's public
|
|
10
|
+
* surface is exactly what it always was. What stayed here is the part that
|
|
11
|
+
* needs a parsed document: `print`, and the eligibility rule that reads the
|
|
12
|
+
* printed text.
|
|
13
|
+
*/
|
|
14
|
+
export { buildCdnUrl, documentHash, hasFoirErrorCode, NOT_SHAREABLE, PERSISTED_QUERY_NOT_FOUND, persistedQueryExtension, } from '../transport/persisted-query';
|
|
3
15
|
/**
|
|
4
16
|
* Whether this read can be served by the shared endpoint.
|
|
5
17
|
*
|
|
@@ -10,33 +22,5 @@ import { type FoirEnv } from '../transport/foir-http';
|
|
|
10
22
|
* saved.
|
|
11
23
|
*/
|
|
12
24
|
export declare function isCdnEligible(env: FoirEnv, documentText: string, bearerToken?: string): boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Build the shared-endpoint URL for a read.
|
|
15
|
-
*
|
|
16
|
-
* The credential goes in the URL deliberately: a shared cache keys on the URL
|
|
17
|
-
* alone, so a key that rode only in a header would let two callers with
|
|
18
|
-
* different reach collide on one entry and be served each other's body.
|
|
19
|
-
*/
|
|
20
|
-
export declare function buildCdnUrl(endpoint: string, apiKey: string, hash: string, variables?: unknown): string;
|
|
21
|
-
/** The APQ handshake object, in Apollo's shape because every client uses it. */
|
|
22
|
-
export declare function persistedQueryExtension(hash: string): {
|
|
23
|
-
persistedQuery: {
|
|
24
|
-
version: number;
|
|
25
|
-
sha256Hash: string;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* SHA-256 of the document text, hex encoded — the identity a persisted query is
|
|
30
|
-
* stored and retrieved under.
|
|
31
|
-
*
|
|
32
|
-
* Web Crypto rather than a bundled implementation: it is present in every
|
|
33
|
-
* runtime this SDK targets (workers, edge, Node 18+) and adds nothing to the
|
|
34
|
-
* caller's bundle.
|
|
35
|
-
*/
|
|
36
|
-
export declare function documentHash(documentText: string): Promise<string>;
|
|
37
|
-
/** The code the server answers with when it has never seen this hash. */
|
|
38
|
-
export declare const PERSISTED_QUERY_NOT_FOUND = "PERSISTED_QUERY_NOT_FOUND";
|
|
39
|
-
/** The code the shared endpoint refuses with, so a caller knows to retry. */
|
|
40
|
-
export declare const NOT_SHAREABLE = "NOT_SHAREABLE";
|
|
41
25
|
/** Print a document once, so eligibility and the URL agree on the same text. */
|
|
42
26
|
export declare function printDocument<TData, TVariables>(document: TypedDocumentNode<TData, TVariables>): string;
|
package/dist/media/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@foir/sdk/media` — progressive media components
|
|
2
|
+
* `@foir/sdk/media` — progressive media components, Foir media adapters, and
|
|
3
|
+
* screen capture.
|
|
3
4
|
*
|
|
4
5
|
* `react` is a peer dependency; `hls.js` and `blurhash` are optional peers
|
|
5
|
-
* loaded lazily (video/blurhash gracefully no-op when absent).
|
|
6
|
+
* loaded lazily (video/blurhash gracefully no-op when absent). The capture
|
|
7
|
+
* helpers are browser-only and pull in neither.
|
|
6
8
|
*/
|
|
7
9
|
export { ProgressiveMedia } from './ProgressiveMedia';
|
|
8
10
|
export type { ProgressiveMediaProps } from './ProgressiveMedia';
|
|
@@ -12,3 +14,5 @@ export { useProgressiveLoad } from './useProgressiveLoad';
|
|
|
12
14
|
export type { LoadStage, UseProgressiveLoadOptions } from './useProgressiveLoad';
|
|
13
15
|
export { toProgressiveImage, toProgressiveVideo, effectiveImageDimensions, cropAspectRatio, DEFAULT_VARIANT_TIERS, } from './media';
|
|
14
16
|
export type { VariantTier, VariantSet, VariantTierKey, ProgressiveImageSource, ProgressiveVideoSource, FoirImageInput, FoirVideoInput, } from './media';
|
|
17
|
+
export { captureFoirScreen, isFoirScreenCaptureSupported } from './screen-capture';
|
|
18
|
+
export type { FoirScreenCapture, FoirScreenCaptureOptions } from './screen-capture';
|
package/dist/media/media.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export interface ProgressiveVideoSource {
|
|
|
72
72
|
duration?: number;
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
|
-
* Foir GraphQL `
|
|
75
|
+
* Foir GraphQL `Image`-like input — the shape a consumer's codegen
|
|
76
76
|
* produces for an image field (intrinsic dims + per-format variant sets +
|
|
77
77
|
* crop as opaque JSON). Structurally compatible with the generated type.
|
|
78
78
|
*/
|
|
@@ -89,7 +89,7 @@ export interface FoirImageInput {
|
|
|
89
89
|
[key: string]: unknown;
|
|
90
90
|
} | null;
|
|
91
91
|
}
|
|
92
|
-
/** Foir GraphQL `
|
|
92
|
+
/** Foir GraphQL `Video`-like input. */
|
|
93
93
|
export interface FoirVideoInput {
|
|
94
94
|
url?: string | null;
|
|
95
95
|
width?: number | null;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen capture — a picture of what the reader was looking at.
|
|
3
|
+
*
|
|
4
|
+
* ---------------------------------------------------------------------------
|
|
5
|
+
* Why it lives in the SDK
|
|
6
|
+
* ---------------------------------------------------------------------------
|
|
7
|
+
*
|
|
8
|
+
* Two surfaces need the same picture: the console's own bug report, and the
|
|
9
|
+
* feedback widget a customer's visitors use (`@foir/sdk/react`). One of those
|
|
10
|
+
* ships to npm and the other does not, so the shared half has to sit in the
|
|
11
|
+
* published package — a copy in each would be two answers to "how big is a
|
|
12
|
+
* screenshot", free to drift, and the drift would only show up as an upload the
|
|
13
|
+
* storage quota measures differently from the preview.
|
|
14
|
+
*
|
|
15
|
+
* ---------------------------------------------------------------------------
|
|
16
|
+
* The browser's picker IS the consent gate
|
|
17
|
+
* ---------------------------------------------------------------------------
|
|
18
|
+
*
|
|
19
|
+
* `getDisplayMedia` cannot be called without a user gesture and cannot resolve
|
|
20
|
+
* without the person choosing a surface in the browser's own dialog. So there is
|
|
21
|
+
* nothing here that asks for permission separately, and nothing that could
|
|
22
|
+
* capture without the reader having picked what to hand over. Call this from a
|
|
23
|
+
* click handler and from nowhere else.
|
|
24
|
+
*
|
|
25
|
+
* The stream is stopped before this function returns, on every path — a capture
|
|
26
|
+
* that left the tracks live would leave the browser's "sharing your screen"
|
|
27
|
+
* indicator on after a single still frame.
|
|
28
|
+
*/
|
|
29
|
+
/** How the still is encoded. Both have defaults; neither is read from anywhere else. */
|
|
30
|
+
export interface FoirScreenCaptureOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Longest horizontal edge of the still, in pixels. Wider captures are scaled
|
|
33
|
+
* down proportionally; narrower ones are left alone rather than upscaled.
|
|
34
|
+
*
|
|
35
|
+
* Default 1600, which is a readable full-page shot of a 4K display and lands
|
|
36
|
+
* around 200-400kB as JPEG. It is a bound on the bytes a reader uploads over
|
|
37
|
+
* their own connection, not a quality target.
|
|
38
|
+
*/
|
|
39
|
+
maxWidth?: number;
|
|
40
|
+
/** JPEG quality, 0 to 1. Default 0.7. */
|
|
41
|
+
quality?: number;
|
|
42
|
+
}
|
|
43
|
+
/** One captured still. */
|
|
44
|
+
export interface FoirScreenCapture {
|
|
45
|
+
/** The bytes, for upload. `size` on it is what the storage quota measures. */
|
|
46
|
+
blob: Blob;
|
|
47
|
+
/**
|
|
48
|
+
* The same bytes as a `data:` URL, for an `<img src>` preview.
|
|
49
|
+
*
|
|
50
|
+
* Both, rather than one plus a converter, because the two callers need
|
|
51
|
+
* different halves and neither should re-encode: an object URL has a lifetime
|
|
52
|
+
* to manage and a second `toDataURL` would be a second encode of one frame.
|
|
53
|
+
*/
|
|
54
|
+
dataUrl: string;
|
|
55
|
+
/** The still's own dimensions, after any downscale. */
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
mimeType: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Whether this browser can capture at all.
|
|
62
|
+
*
|
|
63
|
+
* Ask before rendering a capture button. `getDisplayMedia` is absent on every
|
|
64
|
+
* iOS browser and on any page served over plain HTTP, and a button that can only
|
|
65
|
+
* produce an error message is worse than no button.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isFoirScreenCaptureSupported(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Capture one still of a surface the reader picks.
|
|
70
|
+
*
|
|
71
|
+
* Returns `null` when they dismissed the picker — that is a decision, not a
|
|
72
|
+
* failure, and a caller that treated it as one would put an error in front of
|
|
73
|
+
* somebody who just changed their mind. Everything else throws.
|
|
74
|
+
*
|
|
75
|
+
* The one ambiguity worth stating: a page embedded in an iframe without
|
|
76
|
+
* `allow="display-capture"` is refused with the SAME `NotAllowedError` the
|
|
77
|
+
* dismissal produces, so it lands here as `null` too. The browser gives no way
|
|
78
|
+
* to tell the two apart, and guessing wrong in the other direction would mean an
|
|
79
|
+
* error toast on every cancel.
|
|
80
|
+
*/
|
|
81
|
+
export declare function captureFoirScreen(options?: FoirScreenCaptureOptions): Promise<FoirScreenCapture | null>;
|
package/dist/media.js
CHANGED
|
@@ -392,12 +392,109 @@ function ProgressiveMedia({
|
|
|
392
392
|
}
|
|
393
393
|
);
|
|
394
394
|
}
|
|
395
|
+
|
|
396
|
+
// src/media/screen-capture.ts
|
|
397
|
+
var DEFAULT_MAX_WIDTH = 1600;
|
|
398
|
+
var DEFAULT_QUALITY = 0.7;
|
|
399
|
+
var CAPTURE_MIME_TYPE = "image/jpeg";
|
|
400
|
+
function isFoirScreenCaptureSupported() {
|
|
401
|
+
return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getDisplayMedia === "function";
|
|
402
|
+
}
|
|
403
|
+
async function captureFoirScreen(options = {}) {
|
|
404
|
+
if (!isFoirScreenCaptureSupported()) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
"[foir] this browser cannot capture the screen: navigator.mediaDevices.getDisplayMedia is unavailable. It needs a secure context (https, or localhost) and is absent on iOS. Call isFoirScreenCaptureSupported() before offering capture."
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
const maxWidth = options.maxWidth ?? DEFAULT_MAX_WIDTH;
|
|
410
|
+
const quality = options.quality ?? DEFAULT_QUALITY;
|
|
411
|
+
let stream;
|
|
412
|
+
try {
|
|
413
|
+
stream = await navigator.mediaDevices.getDisplayMedia({
|
|
414
|
+
video: { displaySurface: "browser" }
|
|
415
|
+
});
|
|
416
|
+
} catch (err) {
|
|
417
|
+
if (isDismissal(err)) return null;
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const video = document.createElement("video");
|
|
422
|
+
video.muted = true;
|
|
423
|
+
video.playsInline = true;
|
|
424
|
+
video.srcObject = stream;
|
|
425
|
+
await waitForMetadata(video);
|
|
426
|
+
await video.play();
|
|
427
|
+
const { videoWidth, videoHeight } = video;
|
|
428
|
+
if (!videoWidth || !videoHeight) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
"[foir] the captured stream reported no dimensions, so there is no frame to draw. This is the browser ending the capture before the first frame arrived \u2014 retry it."
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
const scale = videoWidth > maxWidth ? maxWidth / videoWidth : 1;
|
|
434
|
+
const width = Math.round(videoWidth * scale);
|
|
435
|
+
const height = Math.round(videoHeight * scale);
|
|
436
|
+
const canvas = document.createElement("canvas");
|
|
437
|
+
canvas.width = width;
|
|
438
|
+
canvas.height = height;
|
|
439
|
+
const ctx = canvas.getContext("2d");
|
|
440
|
+
if (!ctx) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
"[foir] this browser gave no 2d canvas context, so the captured frame cannot be encoded. Nothing was uploaded."
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
ctx.drawImage(video, 0, 0, width, height);
|
|
446
|
+
video.srcObject = null;
|
|
447
|
+
const dataUrl = canvas.toDataURL(CAPTURE_MIME_TYPE, quality);
|
|
448
|
+
return {
|
|
449
|
+
blob: dataUrlToBlob(dataUrl),
|
|
450
|
+
dataUrl,
|
|
451
|
+
width,
|
|
452
|
+
height,
|
|
453
|
+
mimeType: CAPTURE_MIME_TYPE
|
|
454
|
+
};
|
|
455
|
+
} finally {
|
|
456
|
+
for (const track of stream.getTracks()) track.stop();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function isDismissal(err) {
|
|
460
|
+
const name = err?.name;
|
|
461
|
+
return name === "NotAllowedError" || name === "AbortError";
|
|
462
|
+
}
|
|
463
|
+
function waitForMetadata(video) {
|
|
464
|
+
if (video.readyState >= 1) return Promise.resolve();
|
|
465
|
+
return new Promise((resolve, reject) => {
|
|
466
|
+
video.addEventListener("loadedmetadata", () => resolve(), { once: true });
|
|
467
|
+
video.addEventListener(
|
|
468
|
+
"error",
|
|
469
|
+
() => reject(
|
|
470
|
+
new Error("[foir] the captured stream failed to load before a frame could be read")
|
|
471
|
+
),
|
|
472
|
+
{ once: true }
|
|
473
|
+
);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
function dataUrlToBlob(dataUrl) {
|
|
477
|
+
const comma = dataUrl.indexOf(",");
|
|
478
|
+
const header = comma === -1 ? "" : dataUrl.slice(0, comma);
|
|
479
|
+
if (comma === -1 || !header.includes(";base64")) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`[foir] the canvas produced a data URL this build cannot decode (${header || "no header"}). Expected a base64 data URL.`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
const binary = atob(dataUrl.slice(comma + 1));
|
|
485
|
+
const bytes = new Uint8Array(binary.length);
|
|
486
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
487
|
+
const mimeType = header.slice("data:".length, header.indexOf(";"));
|
|
488
|
+
return new Blob([bytes], { type: mimeType || CAPTURE_MIME_TYPE });
|
|
489
|
+
}
|
|
395
490
|
export {
|
|
396
491
|
BlurhashCanvas,
|
|
397
492
|
DEFAULT_VARIANT_TIERS,
|
|
398
493
|
ProgressiveMedia,
|
|
494
|
+
captureFoirScreen,
|
|
399
495
|
cropAspectRatio,
|
|
400
496
|
effectiveImageDimensions,
|
|
497
|
+
isFoirScreenCaptureSupported,
|
|
401
498
|
toProgressiveImage,
|
|
402
499
|
toProgressiveVideo,
|
|
403
500
|
useProgressiveLoad
|