@foir/sdk 0.5.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +1126 -0
  2. package/README.md +94 -0
  3. package/dist/auth/auth.d.ts +243 -0
  4. package/dist/auth/hosted.d.ts +40 -0
  5. package/dist/auth/index.d.ts +21 -0
  6. package/dist/auth/operations.d.ts +144 -0
  7. package/dist/auth/ops.d.ts +99 -0
  8. package/dist/auth/session-client.d.ts +76 -0
  9. package/dist/auth-session.js +137 -0
  10. package/dist/auth.js +973 -92
  11. package/dist/chunk-IGNLWT3H.js +35 -0
  12. package/dist/chunk-OHYVKTGB.js +45 -0
  13. package/dist/chunk-TAL5H6VA.js +39 -0
  14. package/dist/client/platform-client.d.ts +29 -0
  15. package/dist/client.js +275 -37
  16. package/dist/codegen/block-registry.d.ts +13 -23
  17. package/dist/codegen/block-registry.js +3 -31
  18. package/dist/codegen/block-scaffold.d.ts +12 -25
  19. package/dist/codegen/block-scaffold.js +3 -41
  20. package/dist/codegen/index.d.ts +12 -61
  21. package/dist/codegen/index.js +7 -11
  22. package/dist/codegen/item-registry.d.ts +28 -0
  23. package/dist/codegen/item-registry.js +7 -0
  24. package/dist/codegen/item-scaffold.d.ts +29 -0
  25. package/dist/codegen/item-scaffold.js +7 -0
  26. package/dist/codegen/preset.d.ts +67 -0
  27. package/dist/codegen/shared.d.ts +36 -0
  28. package/dist/components/EditorProvider.d.ts +36 -0
  29. package/dist/content/content-renderer.d.ts +113 -0
  30. package/dist/content/index.d.ts +18 -0
  31. package/dist/content/richtext.d.ts +118 -0
  32. package/dist/content.js +227 -80
  33. package/dist/data/builtins.d.ts +506 -0
  34. package/dist/data/client.d.ts +166 -0
  35. package/dist/data/document.d.ts +251 -0
  36. package/dist/data/index.d.ts +73 -0
  37. package/dist/data/manifest.d.ts +424 -0
  38. package/dist/data/schema.d.ts +313 -0
  39. package/dist/data/selection.d.ts +524 -0
  40. package/dist/data/subjects.d.ts +221 -0
  41. package/dist/data/subscription-document.d.ts +75 -0
  42. package/dist/data/subscriptions.d.ts +181 -0
  43. package/dist/data/transport.d.ts +325 -0
  44. package/dist/data/ws-transport.d.ts +197 -0
  45. package/dist/data.js +1683 -0
  46. package/dist/graphql/cdn-route.d.ts +26 -0
  47. package/dist/graphql/client.d.ts +30 -0
  48. package/dist/graphql/content-client.d.ts +66 -0
  49. package/dist/graphql/foir-request.d.ts +47 -0
  50. package/dist/hooks/useAutoResize.d.ts +22 -0
  51. package/dist/hooks/useEditor.d.ts +16 -0
  52. package/dist/index.d.ts +10 -394
  53. package/dist/media/BlurhashCanvas.d.ts +10 -0
  54. package/dist/media/ProgressiveMedia.d.ts +56 -0
  55. package/dist/media/index.d.ts +18 -0
  56. package/dist/media/media.d.ts +139 -0
  57. package/dist/media/screen-capture.d.ts +81 -0
  58. package/dist/media/useProgressiveLoad.d.ts +23 -0
  59. package/dist/media.js +102 -0
  60. package/dist/next/cookieAdapter.d.ts +26 -0
  61. package/dist/next/index.d.ts +15 -0
  62. package/dist/next/middleware.d.ts +49 -0
  63. package/dist/next/session.d.ts +53 -0
  64. package/dist/next.js +763 -78
  65. package/dist/protocol/constants.d.ts +6 -0
  66. package/dist/protocol/messages.d.ts +155 -0
  67. package/dist/protocol/validation.d.ts +8 -0
  68. package/dist/push/index.d.ts +14 -0
  69. package/dist/push/operations.d.ts +22 -0
  70. package/dist/push/web-push.d.ts +39 -0
  71. package/dist/push.js +892 -78
  72. package/dist/react/FoirFeedback.d.ts +62 -0
  73. package/dist/react/FoirProvider.d.ts +116 -0
  74. package/dist/react/OAuthButtons.d.ts +20 -0
  75. package/dist/react/SignIn.d.ts +22 -0
  76. package/dist/react/UserButton.d.ts +13 -0
  77. package/dist/react/feedback-notes.d.ts +101 -0
  78. package/dist/react/index.d.ts +25 -0
  79. package/dist/react/localStorageAdapter.d.ts +10 -0
  80. package/dist/react.js +1373 -91
  81. package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
  82. package/dist/realtime.js +1 -1
  83. package/dist/server/callback-client.d.ts +92 -0
  84. package/dist/server/events-client.d.ts +279 -0
  85. package/dist/server/index.d.ts +12 -0
  86. package/dist/server/operation-types.d.ts +134 -0
  87. package/dist/server/require-scoped-token.d.ts +76 -0
  88. package/dist/server/server-client.d.ts +34 -0
  89. package/dist/server/types.d.ts +54 -0
  90. package/dist/server/verify-scoped-token.d.ts +100 -0
  91. package/dist/server/webhook-helpers.d.ts +36 -0
  92. package/dist/server.js +298 -0
  93. package/dist/transport/foir-http.d.ts +234 -0
  94. package/dist/transport/persisted-query.d.ts +68 -0
  95. package/dist/transport/retry.d.ts +61 -0
  96. package/dist/types/index.d.ts +124 -0
  97. package/package.json +35 -13
  98. package/dist/auth.d.ts +0 -382
  99. package/dist/chunk-ADO5FJNM.js +0 -30
  100. package/dist/client.d.ts +0 -135
  101. package/dist/content.d.ts +0 -96
  102. package/dist/media.d.ts +0 -234
  103. package/dist/next.d.ts +0 -178
  104. package/dist/push.d.ts +0 -109
  105. package/dist/react.d.ts +0 -319
  106. package/dist/server.d.ts +0 -534
  107. package/dist/shared-DkWlEd2Z.d.ts +0 -18
@@ -0,0 +1,56 @@
1
+ import type { ProgressiveImageSource, ProgressiveVideoSource, VariantTierKey } from './media';
2
+ export interface ProgressiveMediaProps {
3
+ image?: ProgressiveImageSource;
4
+ video?: ProgressiveVideoSource;
5
+ alt?: string;
6
+ className?: string;
7
+ /** Fill the parent container instead of using intrinsic aspect ratio. */
8
+ fill?: boolean;
9
+ /**
10
+ * Object-fit for fill mode. Defaults to 'cover'. Has no effect when `fill`
11
+ * is false (the image renders at its intrinsic aspect ratio).
12
+ */
13
+ fit?: 'cover' | 'contain';
14
+ /**
15
+ * Solid-colour placeholder mode. When neither image nor video is provided,
16
+ * render a flat coloured div. `aspectRatio` defaults to 16/10.
17
+ */
18
+ placeholder?: {
19
+ color: string;
20
+ aspectRatio?: number;
21
+ };
22
+ /** Video playback — autoplay on mount. Default true. */
23
+ autoplay?: boolean;
24
+ /** Video playback — muted. Default true (required for autoplay). */
25
+ muted?: boolean;
26
+ /** Video playback — loop on end. Default true. */
27
+ loop?: boolean;
28
+ /**
29
+ * When false, skip loading the full media layer (no `<video>`/full `<img>`
30
+ * mount, no HLS attach, no full-image preload). The lower layers — colour,
31
+ * blurhash, thumbnail — still render. Used by responsive dual-render so the
32
+ * CSS-hidden branch doesn't download its full asset. Default true.
33
+ */
34
+ active?: boolean;
35
+ /**
36
+ * `sizes` attribute applied to the full `<img>` — tells the browser how
37
+ * wide the image renders so it can pick the right `srcset` entry. Default
38
+ * `"100vw"`. Has no effect on video sources.
39
+ */
40
+ sizes?: string;
41
+ /**
42
+ * When true, treat this media as the page's LCP candidate: thumbnail and
43
+ * full `<img>` get `fetchpriority="high"` + `loading="eager"`, and any
44
+ * `<video>` mounts immediately. When false (default) below-fold behaviour:
45
+ * `loading="lazy"` on `<img>`, and `<video>` is gated by an
46
+ * IntersectionObserver so its `src`/HLS only attaches near the viewport.
47
+ */
48
+ priority?: boolean;
49
+ /**
50
+ * Tier order, smallest→largest, used to build the `srcset` from a
51
+ * `VariantSet`. Defaults to {@link DEFAULT_VARIANT_TIERS}; override when a
52
+ * project curates a different tier set.
53
+ */
54
+ variantTiers?: readonly VariantTierKey[];
55
+ }
56
+ export declare function ProgressiveMedia({ image, video, alt, className, fill, fit, placeholder, autoplay, muted, loop, active, sizes, priority, variantTiers, }: ProgressiveMediaProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@foir/sdk/media` — progressive media components, Foir media adapters, and
3
+ * screen capture.
4
+ *
5
+ * `react` is a peer dependency; `hls.js` and `blurhash` are optional peers
6
+ * loaded lazily (video/blurhash gracefully no-op when absent). The capture
7
+ * helpers are browser-only and pull in neither.
8
+ */
9
+ export { ProgressiveMedia } from './ProgressiveMedia';
10
+ export type { ProgressiveMediaProps } from './ProgressiveMedia';
11
+ export { BlurhashCanvas } from './BlurhashCanvas';
12
+ export type { BlurhashCanvasProps } from './BlurhashCanvas';
13
+ export { useProgressiveLoad } from './useProgressiveLoad';
14
+ export type { LoadStage, UseProgressiveLoadOptions } from './useProgressiveLoad';
15
+ export { toProgressiveImage, toProgressiveVideo, effectiveImageDimensions, cropAspectRatio, DEFAULT_VARIANT_TIERS, } from './media';
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';
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Foir media types + adapters.
3
+ *
4
+ * Framework-agnostic plumbing for rendering Foir image/video fields with
5
+ * `<ProgressiveMedia>`: the canonical source shapes, GraphQL-value → source
6
+ * adapters (crop normalization, per-format variant sets, HLS URL
7
+ * absolutising), and crop-aware dimension maths for CLS-free layout.
8
+ *
9
+ * HLS manifest URLs are absolutised via the shared `absolutiseFoirMediaUrl`
10
+ * from the client transport — not re-implemented here.
11
+ */
12
+ /** One rendered tier: a URL + its TRUE (crop-aware) dims (for srcset `w` + CLS). */
13
+ export interface VariantTier {
14
+ url: string;
15
+ width?: number | null;
16
+ height?: number | null;
17
+ }
18
+ /** Curated tier set in one format (Foir `ImageVariantSet`). */
19
+ export interface VariantSet {
20
+ thumbnail?: VariantTier | null;
21
+ small?: VariantTier | null;
22
+ medium?: VariantTier | null;
23
+ large?: VariantTier | null;
24
+ xlarge?: VariantTier | null;
25
+ _2xl?: VariantTier | null;
26
+ _3xl?: VariantTier | null;
27
+ _4xl?: VariantTier | null;
28
+ }
29
+ /** A `VariantSet` tier name. */
30
+ export type VariantTierKey = keyof VariantSet;
31
+ /**
32
+ * Default tier order, smallest→largest, for walking a `VariantSet`
33
+ * deterministically when building a `srcset`. Override per `<ProgressiveMedia>`
34
+ * via the `variantTiers` prop when a project curates a different tier set.
35
+ */
36
+ export declare const DEFAULT_VARIANT_TIERS: readonly VariantTierKey[];
37
+ /** Image source with progressive loading data. */
38
+ export interface ProgressiveImageSource {
39
+ url: string;
40
+ alt?: string;
41
+ width?: number;
42
+ height?: number;
43
+ blurhash?: string;
44
+ dominantColor?: string;
45
+ thumbnailUrl?: string;
46
+ /** Per-format tier sets (WebP universal; AVIF preferred via `<picture>`). */
47
+ webp?: VariantSet | null;
48
+ avif?: VariantSet | null;
49
+ /**
50
+ * Optional crop rectangle expressed as percentages of the source image
51
+ * (`{x, y, width, height}` in 0–100). Foir's image pipeline serves the
52
+ * cropped file at the cropped dimensions, so consumers need this to compute
53
+ * correct `width`/`height` attrs for CLS-free rendering.
54
+ */
55
+ crop?: {
56
+ x?: number;
57
+ y?: number;
58
+ width?: number;
59
+ height?: number;
60
+ };
61
+ }
62
+ /** Video source with progressive loading data + optional HLS. */
63
+ export interface ProgressiveVideoSource {
64
+ url: string;
65
+ width?: number;
66
+ height?: number;
67
+ blurhash?: string;
68
+ dominantColor?: string;
69
+ thumbnailUrl?: string;
70
+ previewUrl?: string;
71
+ hlsUrl?: string;
72
+ duration?: number;
73
+ }
74
+ /**
75
+ * Foir GraphQL `Image`-like input — the shape a consumer's codegen
76
+ * produces for an image field (intrinsic dims + per-format variant sets +
77
+ * crop as opaque JSON). Structurally compatible with the generated type.
78
+ */
79
+ export interface FoirImageInput {
80
+ url?: string | null;
81
+ alt?: string | null;
82
+ width?: number | null;
83
+ height?: number | null;
84
+ blurhash?: string | null;
85
+ dominantColor?: string | null;
86
+ webp?: VariantSet | null;
87
+ avif?: VariantSet | null;
88
+ crop?: {
89
+ [key: string]: unknown;
90
+ } | null;
91
+ }
92
+ /** Foir GraphQL `Video`-like input. */
93
+ export interface FoirVideoInput {
94
+ url?: string | null;
95
+ width?: number | null;
96
+ height?: number | null;
97
+ hlsManifestUrl?: string | null;
98
+ thumbnailUrl?: string | null;
99
+ previewUrl?: string | null;
100
+ blurhash?: string | null;
101
+ dominantColor?: string | null;
102
+ }
103
+ /**
104
+ * Adapt a Foir image field to the `ProgressiveImageSource` consumed by
105
+ * `<ProgressiveMedia>`. Returns `undefined` when the field is empty (the
106
+ * caller falls back to a placeholder). Narrows the JSON-typed `crop` to the
107
+ * numeric keys; out-of-range values are filtered in `effectiveImageDimensions`.
108
+ */
109
+ export declare function toProgressiveImage(img: FoirImageInput | null | undefined): ProgressiveImageSource | undefined;
110
+ /**
111
+ * Adapt a Foir video field to the `ProgressiveVideoSource` consumed by
112
+ * `<ProgressiveMedia>`. Absolutises the HLS manifest URL (Foir returns it
113
+ * relative to its media proxy). Returns `undefined` when the field is empty.
114
+ */
115
+ export declare function toProgressiveVideo(v: FoirVideoInput | null | undefined): ProgressiveVideoSource | undefined;
116
+ /**
117
+ * Resolve the dimensions of the file Foir's pipeline will actually serve,
118
+ * applying any crop percentages to the source's intrinsic size. Returns null
119
+ * when the source has no width/height. Drives both the wrapper aspect-ratio
120
+ * and the `<img>` `width`/`height` attrs.
121
+ */
122
+ export declare function effectiveImageDimensions(image: {
123
+ width?: number | null;
124
+ height?: number | null;
125
+ crop?: unknown;
126
+ } | null | undefined): {
127
+ width: number;
128
+ height: number;
129
+ } | null;
130
+ /**
131
+ * Compute a CSS `aspect-ratio` string from a Foir image's natural dimensions
132
+ * and optional crop. When a usable crop is set the cropped rectangle's aspect
133
+ * wins; otherwise the intrinsic width/height; otherwise `null`.
134
+ */
135
+ export declare function cropAspectRatio(image: {
136
+ width?: number | null;
137
+ height?: number | null;
138
+ crop?: unknown;
139
+ } | null | undefined): string | 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>;
@@ -0,0 +1,23 @@
1
+ export type LoadStage = 'color' | 'blurhash' | 'thumbnail' | 'full';
2
+ export interface UseProgressiveLoadOptions {
3
+ blurhash?: string;
4
+ thumbnailUrl?: string;
5
+ fullUrl?: string;
6
+ /**
7
+ * Optional srcset + sizes — when provided to image sources, the preloader
8
+ * matches the variant the visible `<img>` will pick, so the browser dedupes
9
+ * the fetch instead of downloading two sizes.
10
+ */
11
+ fullSrcSet?: string;
12
+ fullSizes?: string;
13
+ isVideo?: boolean;
14
+ }
15
+ /**
16
+ * State machine for progressive media loading.
17
+ * Stages advance forward only: color → blurhash → thumbnail → full.
18
+ * Skips stages when data is missing. Never regresses.
19
+ */
20
+ export declare function useProgressiveLoad({ blurhash, thumbnailUrl, fullUrl, fullSrcSet, fullSizes, isVideo, }: UseProgressiveLoadOptions): {
21
+ stage: LoadStage;
22
+ onFullReady: () => void;
23
+ };
package/dist/media.js CHANGED
@@ -2,7 +2,12 @@
2
2
  import { useRef as useRef2, useState as useState2, useCallback as useCallback2, useEffect as useEffect3 } from "react";
3
3
 
4
4
  // src/graphql/foir-request.ts
5
+ import { print as print2 } from "graphql";
6
+
7
+ // src/graphql/cdn-route.ts
5
8
  import { print } from "graphql";
9
+
10
+ // src/graphql/foir-request.ts
6
11
  var FOIR_CDN_BASE = "https://cdn.foir.dev/";
7
12
  function absolutiseFoirMediaUrl(url, cdnBase = FOIR_CDN_BASE) {
8
13
  if (!url) return void 0;
@@ -387,12 +392,109 @@ function ProgressiveMedia({
387
392
  }
388
393
  );
389
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
+ }
390
490
  export {
391
491
  BlurhashCanvas,
392
492
  DEFAULT_VARIANT_TIERS,
393
493
  ProgressiveMedia,
494
+ captureFoirScreen,
394
495
  cropAspectRatio,
395
496
  effectiveImageDimensions,
497
+ isFoirScreenCaptureSupported,
396
498
  toProgressiveImage,
397
499
  toProgressiveVideo,
398
500
  useProgressiveLoad
@@ -0,0 +1,26 @@
1
+ import type { SessionAdapter } from '../auth';
2
+ /**
3
+ * The minimal cookie-store shape this adapter needs. It's satisfied by Next's
4
+ * `cookies()` (route handlers / server actions — `await cookies()` on Next 15),
5
+ * and by `NextResponse.cookies` in middleware. Kept structural on purpose so the
6
+ * SDK doesn't import (or pin a version of) `next/headers`.
7
+ */
8
+ export interface FoirCookieStore {
9
+ get(name: string): {
10
+ value: string;
11
+ } | undefined;
12
+ set(name: string, value: string, options?: Record<string, unknown>): void;
13
+ delete(name: string): void;
14
+ }
15
+ export interface CookieAdapterOptions {
16
+ /** Cookie attributes for writes, merged over the secure defaults. */
17
+ cookieOptions?: Record<string, unknown>;
18
+ }
19
+ export declare function foirCookieName(key: string): string;
20
+ /**
21
+ * A {@link SessionAdapter} backed by an HttpOnly cookie store — the seam for
22
+ * server-side (BFF) Foir sessions in Next.js. Pass the cookie store from a route
23
+ * handler / server action (`await cookies()`) for reads and writes; a Server
24
+ * Component's `cookies()` is read-only, so use it only for reads there.
25
+ */
26
+ export declare function createFoirCookieAdapter(store: FoirCookieStore, opts?: CookieAdapterOptions): SessionAdapter;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@foir/sdk/next` — server-side Foir customer auth for the Next.js App Router
3
+ * (the BFF pattern): an HttpOnly-cookie session adapter, server-side session
4
+ * helpers, a provider-callback exchange, and a route-protection middleware.
5
+ *
6
+ * This is the server counterpart to `@foir/sdk/react`. `next` is a peer
7
+ * dependency (for the middleware helper).
8
+ */
9
+ export { createFoirCookieAdapter, foirCookieName, } from './cookieAdapter';
10
+ export type { FoirCookieStore, CookieAdapterOptions } from './cookieAdapter';
11
+ export { getFoirSession, getFoirToken, foirSignOut, foirSignInWithPassword, foirRegisterWithPassword, foirSignInWithOtp, completeFoirProviderLogin, } from './session';
12
+ export { createFoirMiddleware } from './middleware';
13
+ export type { FoirMiddlewareConfig, FoirMiddlewareRequest } from './middleware';
14
+ export type { FoirCustomer, SessionAdapter } from '../auth';
15
+ export type { FoirEnv } from '../graphql/foir-request';
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The slice of `NextRequest` the middleware reads. Typed structurally so the SDK
3
+ * doesn't need `next` installed to build — a real `NextRequest` satisfies it, so
4
+ * the returned function drops straight into `export const middleware`.
5
+ */
6
+ export interface FoirMiddlewareRequest {
7
+ cookies: {
8
+ get(name: string): {
9
+ value: string;
10
+ } | undefined;
11
+ };
12
+ nextUrl: {
13
+ pathname: string;
14
+ search: string;
15
+ };
16
+ url: string;
17
+ }
18
+ export interface FoirMiddlewareConfig {
19
+ /** Where to send unauthenticated requests (path or absolute URL). */
20
+ signInUrl: string;
21
+ /**
22
+ * The session cookie whose presence means "signed in". Defaults to the
23
+ * access-token cookie the cookie adapter writes (`foir_token`).
24
+ */
25
+ cookieName?: string;
26
+ /**
27
+ * Return true for requests that require auth. Omit to protect every request
28
+ * the middleware `matcher` passes through.
29
+ */
30
+ isProtected?(request: FoirMiddlewareRequest): boolean;
31
+ /** Query param carrying the original path on the sign-in URL. Default `returnTo`. */
32
+ returnParam?: string;
33
+ }
34
+ /**
35
+ * Build a Next.js middleware that redirects unauthenticated requests to your
36
+ * sign-in URL. Edge-safe: it checks for the presence of the session cookie only
37
+ * — the bearer itself is validated server-side when you call the Foir API — so
38
+ * pair it with a real `getFoirSession` check in protected route handlers /
39
+ * server components. Returns a standard `Response` (redirect) or `undefined`
40
+ * (continue), both of which Next.js middleware accepts.
41
+ *
42
+ * ```ts
43
+ * // middleware.ts
44
+ * import { createFoirMiddleware } from '@foir/sdk/next';
45
+ * export const middleware = createFoirMiddleware({ signInUrl: '/sign-in' });
46
+ * export const config = { matcher: ['/dashboard/:path*'] };
47
+ * ```
48
+ */
49
+ export declare function createFoirMiddleware(config: FoirMiddlewareConfig): (request: FoirMiddlewareRequest) => Response | undefined;
@@ -0,0 +1,53 @@
1
+ import type { FoirCustomer } from '../auth';
2
+ import type { FoirEnv } from '../graphql/foir-request';
3
+ import type { CookieAdapterOptions, FoirCookieStore } from './cookieAdapter';
4
+ /**
5
+ * Server-side session helpers for the Next.js BFF pattern. Each takes the Next
6
+ * cookie store (`await cookies()` in a route handler / server action, or the
7
+ * read-only `cookies()` in a Server Component for reads).
8
+ *
9
+ * Note on "verification": the customer access token is opaque to your app and
10
+ * validated by the Foir API on use, so `getFoirSession` returns the identity
11
+ * stored in the cookies rather than locally verifying a JWT. A rejected/expired
12
+ * bearer surfaces when you call the API (and `@foir/sdk/auth`'s `authedRequest`
13
+ * transparently refreshes it).
14
+ */
15
+ /** The signed-in customer from the session cookies, or null. */
16
+ export declare function getFoirSession(store: FoirCookieStore): Promise<FoirCustomer | null>;
17
+ /** The current access token from the session cookies, or null. */
18
+ export declare function getFoirToken(store: FoirCookieStore): Promise<string | null>;
19
+ /** Clear the session cookies (sign out). */
20
+ export declare function foirSignOut(store: FoirCookieStore, opts?: CookieAdapterOptions): Promise<void>;
21
+ interface PasswordArgs {
22
+ env: FoirEnv;
23
+ store: FoirCookieStore;
24
+ email: string;
25
+ password: string;
26
+ cookieOptions?: CookieAdapterOptions['cookieOptions'];
27
+ }
28
+ /** Password sign-in in a route handler; persists the session to cookies. */
29
+ export declare function foirSignInWithPassword({ env, store, email, password, cookieOptions, }: PasswordArgs): Promise<FoirCustomer>;
30
+ /** Register a customer in a route handler; persists the session to cookies. */
31
+ export declare function foirRegisterWithPassword({ env, store, email, password, cookieOptions, }: PasswordArgs): Promise<FoirCustomer>;
32
+ /** Complete an OTP sign-in in a route handler; persists the session to cookies. */
33
+ export declare function foirSignInWithOtp({ env, store, email, otp, cookieOptions, }: {
34
+ env: FoirEnv;
35
+ store: FoirCookieStore;
36
+ email: string;
37
+ otp: string;
38
+ cookieOptions?: CookieAdapterOptions['cookieOptions'];
39
+ }): Promise<FoirCustomer>;
40
+ /**
41
+ * Complete a provider (OAuth/OIDC) login in your callback route handler:
42
+ * exchange the `code` + `state` for tokens and persist them to the session
43
+ * cookies. Returns the customer.
44
+ */
45
+ export declare function completeFoirProviderLogin(opts: {
46
+ env: FoirEnv;
47
+ store: FoirCookieStore;
48
+ providerKey: string;
49
+ code?: string;
50
+ state?: string;
51
+ cookieOptions?: CookieAdapterOptions['cookieOptions'];
52
+ }): Promise<FoirCustomer>;
53
+ export {};