@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.
- package/CHANGELOG.md +1126 -0
- package/README.md +94 -0
- package/dist/auth/auth.d.ts +243 -0
- package/dist/auth/hosted.d.ts +40 -0
- package/dist/auth/index.d.ts +21 -0
- package/dist/auth/operations.d.ts +144 -0
- package/dist/auth/ops.d.ts +99 -0
- package/dist/auth/session-client.d.ts +76 -0
- package/dist/auth-session.js +137 -0
- package/dist/auth.js +973 -92
- package/dist/chunk-IGNLWT3H.js +35 -0
- package/dist/chunk-OHYVKTGB.js +45 -0
- package/dist/chunk-TAL5H6VA.js +39 -0
- package/dist/client/platform-client.d.ts +29 -0
- package/dist/client.js +275 -37
- package/dist/codegen/block-registry.d.ts +13 -23
- package/dist/codegen/block-registry.js +3 -31
- package/dist/codegen/block-scaffold.d.ts +12 -25
- package/dist/codegen/block-scaffold.js +3 -41
- package/dist/codegen/index.d.ts +12 -61
- 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 +67 -0
- package/dist/codegen/shared.d.ts +36 -0
- package/dist/components/EditorProvider.d.ts +36 -0
- package/dist/content/content-renderer.d.ts +113 -0
- package/dist/content/index.d.ts +18 -0
- package/dist/content/richtext.d.ts +118 -0
- package/dist/content.js +227 -80
- package/dist/data/builtins.d.ts +506 -0
- package/dist/data/client.d.ts +166 -0
- package/dist/data/document.d.ts +251 -0
- package/dist/data/index.d.ts +73 -0
- package/dist/data/manifest.d.ts +424 -0
- package/dist/data/schema.d.ts +313 -0
- package/dist/data/selection.d.ts +524 -0
- 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 +325 -0
- package/dist/data/ws-transport.d.ts +197 -0
- package/dist/data.js +1683 -0
- package/dist/graphql/cdn-route.d.ts +26 -0
- package/dist/graphql/client.d.ts +30 -0
- package/dist/graphql/content-client.d.ts +66 -0
- package/dist/graphql/foir-request.d.ts +47 -0
- package/dist/hooks/useAutoResize.d.ts +22 -0
- package/dist/hooks/useEditor.d.ts +16 -0
- package/dist/index.d.ts +10 -394
- package/dist/media/BlurhashCanvas.d.ts +10 -0
- package/dist/media/ProgressiveMedia.d.ts +56 -0
- package/dist/media/index.d.ts +18 -0
- package/dist/media/media.d.ts +139 -0
- package/dist/media/screen-capture.d.ts +81 -0
- package/dist/media/useProgressiveLoad.d.ts +23 -0
- package/dist/media.js +102 -0
- package/dist/next/cookieAdapter.d.ts +26 -0
- package/dist/next/index.d.ts +15 -0
- package/dist/next/middleware.d.ts +49 -0
- package/dist/next/session.d.ts +53 -0
- package/dist/next.js +763 -78
- package/dist/protocol/constants.d.ts +6 -0
- package/dist/protocol/messages.d.ts +155 -0
- package/dist/protocol/validation.d.ts +8 -0
- package/dist/push/index.d.ts +14 -0
- package/dist/push/operations.d.ts +22 -0
- package/dist/push/web-push.d.ts +39 -0
- package/dist/push.js +892 -78
- package/dist/react/FoirFeedback.d.ts +62 -0
- package/dist/react/FoirProvider.d.ts +116 -0
- package/dist/react/OAuthButtons.d.ts +20 -0
- package/dist/react/SignIn.d.ts +22 -0
- package/dist/react/UserButton.d.ts +13 -0
- package/dist/react/feedback-notes.d.ts +101 -0
- package/dist/react/index.d.ts +25 -0
- package/dist/react/localStorageAdapter.d.ts +10 -0
- package/dist/react.js +1373 -91
- package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
- package/dist/realtime.js +1 -1
- package/dist/server/callback-client.d.ts +92 -0
- package/dist/server/events-client.d.ts +279 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/operation-types.d.ts +134 -0
- package/dist/server/require-scoped-token.d.ts +76 -0
- package/dist/server/server-client.d.ts +34 -0
- package/dist/server/types.d.ts +54 -0
- package/dist/server/verify-scoped-token.d.ts +100 -0
- package/dist/server/webhook-helpers.d.ts +36 -0
- package/dist/server.js +298 -0
- package/dist/transport/foir-http.d.ts +234 -0
- package/dist/transport/persisted-query.d.ts +68 -0
- package/dist/transport/retry.d.ts +61 -0
- package/dist/types/index.d.ts +124 -0
- package/package.json +35 -13
- package/dist/auth.d.ts +0 -382
- package/dist/chunk-ADO5FJNM.js +0 -30
- package/dist/client.d.ts +0 -135
- package/dist/content.d.ts +0 -96
- package/dist/media.d.ts +0 -234
- package/dist/next.d.ts +0 -178
- package/dist/push.d.ts +0 -109
- package/dist/react.d.ts +0 -319
- package/dist/server.d.ts +0 -534
- 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 {};
|