@colixsystems/widget-sdk 0.190.0 → 0.192.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/README.md +41 -1
- package/dist/audio.js +448 -0
- package/dist/contract.cjs +115 -6
- package/dist/contract.js +115 -6
- package/dist/hooks.js +230 -0
- package/dist/index.d.ts +172 -2
- package/dist/index.js +10 -0
- package/dist/index.native.js +10 -0
- package/package.json +1 -1
- package/src/contract.js +115 -6
package/dist/index.d.ts
CHANGED
|
@@ -697,6 +697,8 @@ export interface WidgetContext<TProps = unknown> {
|
|
|
697
697
|
identification: IdentificationClient;
|
|
698
698
|
/** sc-7991 — injected @colixsystems/agents-client backing useAgent(). */
|
|
699
699
|
agents?: { conversations(agentId: string): unknown };
|
|
700
|
+
/** Injected @colixsystems/link-preview-client backing useLinkPreview(). */
|
|
701
|
+
linkPreviews?: { get(url: string): Promise<LinkPreview> };
|
|
700
702
|
/** Host child-node renderer; backs WidgetTree / useChildRenderer. */
|
|
701
703
|
renderer: { renderNode(node: unknown): unknown };
|
|
702
704
|
events: { emit(eventName: string, payload?: unknown): void };
|
|
@@ -715,10 +717,40 @@ export interface WidgetContext<TProps = unknown> {
|
|
|
715
717
|
showToast(args: { kind?: string; message: string }): void;
|
|
716
718
|
};
|
|
717
719
|
/**
|
|
718
|
-
* Optional host-brokered device capabilities; backs useGeolocation
|
|
719
|
-
* useSpeechToText.
|
|
720
|
+
* Optional host-brokered device capabilities; backs useGeolocation,
|
|
721
|
+
* useSpeechToText, useAudioRecorder and useAudioPlayer.
|
|
720
722
|
*/
|
|
721
723
|
device?: {
|
|
724
|
+
/** sc-10590 — backs useAudioRecorder. */
|
|
725
|
+
audioRecorder?: {
|
|
726
|
+
isSupported?(): boolean;
|
|
727
|
+
getPermission?(): Promise<AudioPermissionState>;
|
|
728
|
+
start(): Promise<{
|
|
729
|
+
/** `ended`: the host stopped it itself; stop() still resolves the clip. */
|
|
730
|
+
getStatus(): { durationMs: number; levelDb: number | null; ended?: boolean };
|
|
731
|
+
stop(): Promise<AudioClip & { release?(): void }>;
|
|
732
|
+
cancel(): void;
|
|
733
|
+
}>;
|
|
734
|
+
};
|
|
735
|
+
/** sc-10590 — backs useAudioPlayer. */
|
|
736
|
+
audioPlayer?: {
|
|
737
|
+
isSupported?(): boolean;
|
|
738
|
+
load(
|
|
739
|
+
uri: string,
|
|
740
|
+
onStatus: (status: {
|
|
741
|
+
loaded: boolean;
|
|
742
|
+
playing: boolean;
|
|
743
|
+
positionMs: number;
|
|
744
|
+
durationMs: number | null;
|
|
745
|
+
error?: { code?: string; message?: string };
|
|
746
|
+
}) => void,
|
|
747
|
+
): {
|
|
748
|
+
play(): Promise<void>;
|
|
749
|
+
pause(): void;
|
|
750
|
+
seek(positionMs: number): Promise<void>;
|
|
751
|
+
release(): void;
|
|
752
|
+
};
|
|
753
|
+
};
|
|
722
754
|
speech?: {
|
|
723
755
|
isSupported?(): boolean;
|
|
724
756
|
start(
|
|
@@ -1535,6 +1567,36 @@ export function useTranslate(): {
|
|
|
1535
1567
|
available: boolean;
|
|
1536
1568
|
};
|
|
1537
1569
|
|
|
1570
|
+
/**
|
|
1571
|
+
* The preview of one page, with the wire's snake_case names. A page with
|
|
1572
|
+
* nothing to show has every field except `url` null. `image_url` loads
|
|
1573
|
+
* directly in an <Image> and needs no auth headers.
|
|
1574
|
+
*/
|
|
1575
|
+
export interface LinkPreview {
|
|
1576
|
+
url: string;
|
|
1577
|
+
title: string | null;
|
|
1578
|
+
description: string | null;
|
|
1579
|
+
site_name: string | null;
|
|
1580
|
+
image_url: string | null;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/**
|
|
1584
|
+
* Preview a link: the page's title, description, site name and image, fetched
|
|
1585
|
+
* by the platform so the device never contacts the linked site.
|
|
1586
|
+
*
|
|
1587
|
+
* A `url` that is not an absolute http(s) URL returns `preview: null` without
|
|
1588
|
+
* a request. `available` is false on a host that brokers no link-preview
|
|
1589
|
+
* client; the hook then never fetches. Results are shared across every widget
|
|
1590
|
+
* for up to an hour, and concurrent requests for one URL are made once. A
|
|
1591
|
+
* RATE_LIMITED failure is retried once after `retryAfter` (clamped to 1–60 s).
|
|
1592
|
+
*/
|
|
1593
|
+
export function useLinkPreview(url: string | null | undefined): {
|
|
1594
|
+
preview: LinkPreview | null;
|
|
1595
|
+
loading: boolean;
|
|
1596
|
+
error: LinkPreviewError | null;
|
|
1597
|
+
available: boolean;
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1538
1600
|
export interface TranslateOptions {
|
|
1539
1601
|
/** Target language code. Defaults to the app user's selected language. */
|
|
1540
1602
|
target?: string;
|
|
@@ -1898,6 +1960,94 @@ export class SpeechToTextError extends Error {
|
|
|
1898
1960
|
);
|
|
1899
1961
|
}
|
|
1900
1962
|
|
|
1963
|
+
/** Microphone permission as `useAudioRecorder()` reports it. */
|
|
1964
|
+
export type AudioPermissionState = "granted" | "denied" | "undetermined";
|
|
1965
|
+
|
|
1966
|
+
/** A finished recording, as `useAudioRecorder().stop()` resolves it. */
|
|
1967
|
+
export interface AudioClip {
|
|
1968
|
+
/**
|
|
1969
|
+
* The upload part for this host: a `File` on web, expo-file-system's `File`
|
|
1970
|
+
* on native (not `instanceof Blob` at runtime) — append it, don't branch on it.
|
|
1971
|
+
*/
|
|
1972
|
+
file: unknown;
|
|
1973
|
+
/** Plays the clip locally via `useAudioPlayer(uri)` until the hook releases it. */
|
|
1974
|
+
uri: string;
|
|
1975
|
+
durationMs: number;
|
|
1976
|
+
/** e.g. "audio/mp4" or "audio/webm". */
|
|
1977
|
+
mime: string;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
export interface AudioRecorderResult {
|
|
1981
|
+
/** False when the host brokers no recorder; disable the record control. */
|
|
1982
|
+
supported: boolean;
|
|
1983
|
+
permission: AudioPermissionState;
|
|
1984
|
+
recording: boolean;
|
|
1985
|
+
/** Length of the current (or last) recording. */
|
|
1986
|
+
durationMs: number;
|
|
1987
|
+
/** Input level from 0 (silence) to 1, for a live meter. */
|
|
1988
|
+
level: number;
|
|
1989
|
+
error: AudioRecorderError | null;
|
|
1990
|
+
/** Start recording — call from a user gesture. Rejects with AudioRecorderError. */
|
|
1991
|
+
start(): Promise<void>;
|
|
1992
|
+
/** Finish and keep the clip; `null` when nothing was recording. */
|
|
1993
|
+
stop(): Promise<AudioClip | null>;
|
|
1994
|
+
/** Stop and discard the clip. */
|
|
1995
|
+
cancel(): void;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
/**
|
|
1999
|
+
* Record a clip from the microphone. The web Player brokers it via
|
|
2000
|
+
* MediaRecorder, the Expo export via `expo-audio`. Capture is imperative (call
|
|
2001
|
+
* `start()` from a user gesture). Safe to call on a host that brokers no
|
|
2002
|
+
* recorder: `supported` is then false and `start()` rejects with
|
|
2003
|
+
* `code: "UNSUPPORTED"`.
|
|
2004
|
+
*/
|
|
2005
|
+
export function useAudioRecorder(): AudioRecorderResult;
|
|
2006
|
+
|
|
2007
|
+
/** Error surfaced by `useAudioRecorder()`. */
|
|
2008
|
+
export class AudioRecorderError extends Error {
|
|
2009
|
+
code: "PERMISSION_DENIED" | "UNSUPPORTED" | "INTERNAL";
|
|
2010
|
+
constructor(
|
|
2011
|
+
code: AudioRecorderError["code"],
|
|
2012
|
+
message: string,
|
|
2013
|
+
opts?: { cause?: unknown },
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
export interface AudioPlayerResult {
|
|
2018
|
+
/** False when the host brokers no player. */
|
|
2019
|
+
supported: boolean;
|
|
2020
|
+
/** True from a new `uri` until the host has loaded it. */
|
|
2021
|
+
loading: boolean;
|
|
2022
|
+
playing: boolean;
|
|
2023
|
+
positionMs: number;
|
|
2024
|
+
/** `null` until the host knows the clip's length. */
|
|
2025
|
+
durationMs: number | null;
|
|
2026
|
+
error: AudioPlayerError | null;
|
|
2027
|
+
/** Start or resume — call from a user gesture. Rejects with AudioPlayerError. */
|
|
2028
|
+
play(): Promise<void>;
|
|
2029
|
+
pause(): void;
|
|
2030
|
+
seek(positionMs: number): Promise<void>;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
/**
|
|
2034
|
+
* Play one clip from `uri` (a stored file's URL, or a recording's local
|
|
2035
|
+
* `uri`). The web Player brokers it via an `<audio>` element, the Expo export
|
|
2036
|
+
* via `expo-audio`. When the clip ends, `playing` turns false and
|
|
2037
|
+
* `positionMs` returns to 0.
|
|
2038
|
+
*/
|
|
2039
|
+
export function useAudioPlayer(uri: string | null | undefined): AudioPlayerResult;
|
|
2040
|
+
|
|
2041
|
+
/** Error surfaced by `useAudioPlayer()`. */
|
|
2042
|
+
export class AudioPlayerError extends Error {
|
|
2043
|
+
code: "UNSUPPORTED" | "LOAD_FAILED" | "BLOCKED" | "INTERNAL";
|
|
2044
|
+
constructor(
|
|
2045
|
+
code: AudioPlayerError["code"],
|
|
2046
|
+
message: string,
|
|
2047
|
+
opts?: { cause?: unknown },
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
1901
2051
|
/**
|
|
1902
2052
|
* Options for `useCamera(...)`. Both are HINTS the host honours where it can:
|
|
1903
2053
|
* the Expo export applies them, the web file input applies neither — so never
|
|
@@ -2198,6 +2348,26 @@ export class AgentError extends Error {
|
|
|
2198
2348
|
constructor(code: string, message: string, opts?: { cause?: unknown });
|
|
2199
2349
|
}
|
|
2200
2350
|
|
|
2351
|
+
/**
|
|
2352
|
+
* Error surfaced by `useLinkPreview().error`. A page that simply has no
|
|
2353
|
+
* preview is not an error. `retryAfter` (seconds) is set on RATE_LIMITED.
|
|
2354
|
+
*/
|
|
2355
|
+
export class LinkPreviewError extends Error {
|
|
2356
|
+
code:
|
|
2357
|
+
| "VALIDATION"
|
|
2358
|
+
| "AUTH_REQUIRED"
|
|
2359
|
+
| "RATE_LIMITED"
|
|
2360
|
+
| "NETWORK"
|
|
2361
|
+
| "INTERNAL"
|
|
2362
|
+
| (string & {});
|
|
2363
|
+
retryAfter: number | null;
|
|
2364
|
+
constructor(
|
|
2365
|
+
code: string,
|
|
2366
|
+
message: string,
|
|
2367
|
+
opts?: { cause?: unknown; retryAfter?: number | null },
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2201
2371
|
export class TranslateError extends Error {
|
|
2202
2372
|
code:
|
|
2203
2373
|
| "UNSUPPORTED"
|
package/dist/index.js
CHANGED
|
@@ -71,6 +71,8 @@ export {
|
|
|
71
71
|
useI18n,
|
|
72
72
|
useTranslate,
|
|
73
73
|
TranslateError,
|
|
74
|
+
useLinkPreview,
|
|
75
|
+
LinkPreviewError,
|
|
74
76
|
useUser,
|
|
75
77
|
useUserPhoto,
|
|
76
78
|
useFill,
|
|
@@ -106,6 +108,14 @@ export {
|
|
|
106
108
|
// the web variant and index.native.js picks the native variant.
|
|
107
109
|
export { useClipboard, ClipboardError } from "./clipboard.js";
|
|
108
110
|
export { useToast } from "./toast.js";
|
|
111
|
+
// sc-10590 — one host-brokered source for both entries: each host injects
|
|
112
|
+
// ctx.device.audioRecorder / audioPlayer, so neither file splits by platform.
|
|
113
|
+
export {
|
|
114
|
+
useAudioRecorder,
|
|
115
|
+
AudioRecorderError,
|
|
116
|
+
useAudioPlayer,
|
|
117
|
+
AudioPlayerError,
|
|
118
|
+
} from "./audio.js";
|
|
109
119
|
export { useDatastoreFigure } from "./datastore-figure.js";
|
|
110
120
|
// sc-6607 — `<Overlay>` is one shared component (./overlay-view.js) bound to
|
|
111
121
|
// each platform's primitives, so a widget's modal escapes its container box
|
package/dist/index.native.js
CHANGED
|
@@ -71,6 +71,8 @@ export {
|
|
|
71
71
|
useI18n,
|
|
72
72
|
useTranslate,
|
|
73
73
|
TranslateError,
|
|
74
|
+
useLinkPreview,
|
|
75
|
+
LinkPreviewError,
|
|
74
76
|
useUser,
|
|
75
77
|
useUserPhoto,
|
|
76
78
|
useFill,
|
|
@@ -104,6 +106,14 @@ export {
|
|
|
104
106
|
// REQ-WSDK-PLATFORM §6 — Tier A hooks (native variants).
|
|
105
107
|
export { useClipboard, ClipboardError } from "./clipboard.native.js";
|
|
106
108
|
export { useToast } from "./toast.native.js";
|
|
109
|
+
// sc-10590 — one host-brokered source for both entries: each host injects
|
|
110
|
+
// ctx.device.audioRecorder / audioPlayer, so neither file splits by platform.
|
|
111
|
+
export {
|
|
112
|
+
useAudioRecorder,
|
|
113
|
+
AudioRecorderError,
|
|
114
|
+
useAudioPlayer,
|
|
115
|
+
AudioPlayerError,
|
|
116
|
+
} from "./audio.js";
|
|
107
117
|
export { useDatastoreFigure } from "./datastore-figure.js";
|
|
108
118
|
// sc-6607 — see the note on the web mirror in ./index.js.
|
|
109
119
|
export { Overlay } from "./overlay.native.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.192.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -784,6 +784,31 @@ const HOOKS = [
|
|
|
784
784
|
requiredContextSlice: ["i18n.locale"],
|
|
785
785
|
scopes: null,
|
|
786
786
|
},
|
|
787
|
+
{
|
|
788
|
+
name: "useLinkPreview",
|
|
789
|
+
signature: "useLinkPreview(url)",
|
|
790
|
+
description:
|
|
791
|
+
"Preview a link the way a messenger does: the page's title, description, site name and image. The PLATFORM fetches the page, so the " +
|
|
792
|
+
"device never contacts the linked site, and private or internal addresses are refused. Returns { preview, loading, error, available }. " +
|
|
793
|
+
"`preview` is { url, title, description, site_name, image_url } with the wire's snake_case names, or null while loading. A page with " +
|
|
794
|
+
"nothing to show (unreachable, not HTML, too slow) resolves with every field but url null — that is NOT an error: render the plain link. " +
|
|
795
|
+
"image_url is a signed URL that loads directly in <Image> with no auth headers. A url that is not an absolute http(s) URL returns " +
|
|
796
|
+
"preview: null with no request, so pass the link you found in the text straight in. Results are memoized for up to an hour across every " +
|
|
797
|
+
"widget and concurrent calls for one URL make one request, so a list of messages may call it per row. error is a LinkPreviewError whose " +
|
|
798
|
+
".code is VALIDATION | AUTH_REQUIRED | RATE_LIMITED | NETWORK | INTERNAL; render the plain link on any error. A RATE_LIMITED failure is " +
|
|
799
|
+
"retried ONCE automatically after the server's retryAfter (clamped to 1–60 s), so the card can still appear. `available` is false on a " +
|
|
800
|
+
"host that brokers no link-preview client; the hook then never fetches. Never block a message on it: render the text first and add the " +
|
|
801
|
+
"preview card when it arrives. Reads ctx.linkPreviews. Identical on web (Player) and the Expo export.",
|
|
802
|
+
returnShape: {
|
|
803
|
+
preview:
|
|
804
|
+
"{ url: string, title: string | null, description: string | null, site_name: string | null, image_url: string | null } | null",
|
|
805
|
+
loading: "boolean",
|
|
806
|
+
error: "LinkPreviewError | null",
|
|
807
|
+
available: "boolean // false when the host brokers no link-preview client",
|
|
808
|
+
},
|
|
809
|
+
requiredContextSlice: ["linkPreviews.get"],
|
|
810
|
+
scopes: null,
|
|
811
|
+
},
|
|
787
812
|
{
|
|
788
813
|
name: "useStableQuery",
|
|
789
814
|
signature: "useStableQuery(buildQuery)",
|
|
@@ -2187,6 +2212,64 @@ const HOOKS = [
|
|
|
2187
2212
|
requiredContextSlice: [],
|
|
2188
2213
|
scopes: null,
|
|
2189
2214
|
},
|
|
2215
|
+
// sc-10590 — host-brokered audio recording + playback. Optional slices; the
|
|
2216
|
+
// hooks report supported:false rather than throwing at render.
|
|
2217
|
+
{
|
|
2218
|
+
name: "useAudioRecorder",
|
|
2219
|
+
signature: "useAudioRecorder()",
|
|
2220
|
+
description:
|
|
2221
|
+
"Record a clip from the microphone. Returns { supported, permission, recording, durationMs, level, error, start, stop, " +
|
|
2222
|
+
"cancel }. Capture is IMPERATIVE — call start() from a user gesture (a tap on a record button); the browser and the " +
|
|
2223
|
+
"mobile OS gate the microphone prompt on a gesture, so it NEVER records on mount. stop() resolves { file, uri, " +
|
|
2224
|
+
"durationMs, mime }, or NULL when nothing was recording; cancel() discards the clip. `file` is already the upload part " +
|
|
2225
|
+
"for the host (a File on web, expo-file-system's File on native) — pass it straight to upload(file) from " +
|
|
2226
|
+
"useFilestoreUpload(). `uri` plays the clip locally through useAudioPlayer(uri) before it is uploaded; the hook " +
|
|
2227
|
+
"releases it on the next start(), on cancel() and on unmount. `permission` is granted | denied | undetermined; " +
|
|
2228
|
+
"`level` is the input level from 0 (silence) to 1, for a live meter. start() and stop() reject with an " +
|
|
2229
|
+
"AudioRecorderError whose .code is one of PERMISSION_DENIED | UNSUPPORTED | INTERNAL. The web records AAC (audio/mp4) " +
|
|
2230
|
+
"where the browser can, otherwise Opus in WebM/Ogg — an Opus or Vorbis clip uploaded to the File library or the asset " +
|
|
2231
|
+
"library by a signed-in user is converted to AAC (audio/mp4, .m4a) on upload so it plays on every host, unless the " +
|
|
2232
|
+
"upload opts out of compression (compress: false) or the clip is over 25 MB or 15 minutes. The Expo export always records AAC. Check " +
|
|
2233
|
+
"`supported` before enabling the record control. Identical on web (MediaRecorder) and the Expo export (expo-audio).",
|
|
2234
|
+
returnShape: {
|
|
2235
|
+
supported: "boolean // false when the host brokers no recorder",
|
|
2236
|
+
permission: "'granted' | 'denied' | 'undetermined'",
|
|
2237
|
+
recording: "boolean",
|
|
2238
|
+
durationMs: "number // length of the current (or last) recording",
|
|
2239
|
+
level: "number // input level 0..1 while recording",
|
|
2240
|
+
error: "AudioRecorderError | null",
|
|
2241
|
+
start: "() => Promise<void> // rejects with AudioRecorderError",
|
|
2242
|
+
stop: "() => Promise<{ file, uri, durationMs, mime } | null> // null if not recording",
|
|
2243
|
+
cancel: "() => void // stop and discard the clip",
|
|
2244
|
+
},
|
|
2245
|
+
requiredContextSlice: [],
|
|
2246
|
+
scopes: null,
|
|
2247
|
+
},
|
|
2248
|
+
{
|
|
2249
|
+
name: "useAudioPlayer",
|
|
2250
|
+
signature: "useAudioPlayer(uri)",
|
|
2251
|
+
description:
|
|
2252
|
+
"Play one audio clip. Returns { supported, loading, playing, positionMs, durationMs, error, play, pause, seek }. `uri` " +
|
|
2253
|
+
"is any playable URL — a stored file's URL (useFilestoreFile(id).url) or the local `uri` a useAudioRecorder() clip " +
|
|
2254
|
+
"resolves; changing it loads the new clip and a falsy uri loads nothing. `durationMs` is null until the host knows the " +
|
|
2255
|
+
"length. When the clip ends, `playing` turns false and `positionMs` returns to 0, so play() replays it. seek(ms) moves " +
|
|
2256
|
+
"the playhead. Call play() from a user gesture — browsers refuse to start audio otherwise. play() and seek() reject " +
|
|
2257
|
+
"with an AudioPlayerError whose .code is one of UNSUPPORTED | LOAD_FAILED | BLOCKED | INTERNAL. Plays MP3, AAC/M4A and " +
|
|
2258
|
+
"WAV on both hosts; WebM/Ogg do not play on iOS. Identical on web (<audio>) and the Expo export (expo-audio).",
|
|
2259
|
+
returnShape: {
|
|
2260
|
+
supported: "boolean // false when the host brokers no player",
|
|
2261
|
+
loading: "boolean // true until the host has loaded the clip",
|
|
2262
|
+
playing: "boolean",
|
|
2263
|
+
positionMs: "number",
|
|
2264
|
+
durationMs: "number | null // null until known",
|
|
2265
|
+
error: "AudioPlayerError | null",
|
|
2266
|
+
play: "() => Promise<void> // rejects with AudioPlayerError",
|
|
2267
|
+
pause: "() => void",
|
|
2268
|
+
seek: "(positionMs) => Promise<void>",
|
|
2269
|
+
},
|
|
2270
|
+
requiredContextSlice: [],
|
|
2271
|
+
scopes: null,
|
|
2272
|
+
},
|
|
2190
2273
|
// sc-6448 — host-brokered camera capture / photo picking. Optional slice;
|
|
2191
2274
|
// the hook reports supported:false rather than throwing at render.
|
|
2192
2275
|
{
|
|
@@ -2922,6 +3005,15 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2922
3005
|
required: false,
|
|
2923
3006
|
fields: { conversations: "function" },
|
|
2924
3007
|
},
|
|
3008
|
+
linkPreviews: {
|
|
3009
|
+
description:
|
|
3010
|
+
"Injected @colixsystems/link-preview-client instance. { get(url) -> Promise<{ url, title, description, site_name, image_url }> }. " +
|
|
3011
|
+
"Backs useLinkPreview(). The server fetches the page; a page with nothing to show resolves with null fields rather than rejecting. " +
|
|
3012
|
+
"image_url is absolute on every host and needs no auth headers. Any signed-in principal may call it (the app user, or the studio " +
|
|
3013
|
+
"user previewing on the canvas). Absent on a host that brokers none, where useLinkPreview() degrades to available: false.",
|
|
3014
|
+
required: false,
|
|
3015
|
+
fields: { get: "function" },
|
|
3016
|
+
},
|
|
2925
3017
|
datastore: {
|
|
2926
3018
|
description:
|
|
2927
3019
|
"Injected @colixsystems/datastore-client instance. " +
|
|
@@ -3116,10 +3208,17 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
3116
3208
|
"camera: { isSupported() -> boolean, capture(options?) -> Promise<asset | null>, pick(options?) -> Promise<asset | null> }, " +
|
|
3117
3209
|
"imageEditor: { isSupported() -> boolean, edit(uri, actions, options?) -> Promise<asset> }, " +
|
|
3118
3210
|
"barcode: { isSupported() -> boolean, scan(options?) -> Promise<{ value, format } | null> }, " +
|
|
3119
|
-
"pdfViewer: { isSupported() -> boolean, open({ fileName, load: () => Promise<ArrayBuffer> }) -> Promise<void> }
|
|
3120
|
-
"
|
|
3121
|
-
"
|
|
3122
|
-
"
|
|
3211
|
+
"pdfViewer: { isSupported() -> boolean, open({ fileName, load: () => Promise<ArrayBuffer> }) -> Promise<void> }, " +
|
|
3212
|
+
"audioRecorder: { isSupported() -> boolean, getPermission() -> Promise<'granted' | 'denied' | 'undetermined'>, " +
|
|
3213
|
+
"start() -> Promise<{ getStatus() -> { durationMs, levelDb, ended }, stop() -> Promise<{ file, uri, durationMs, mime, release? }>, cancel() }> }, " +
|
|
3214
|
+
"audioPlayer: { isSupported() -> boolean, load(uri, onStatus({ loaded, playing, positionMs, durationMs, error? })) -> " +
|
|
3215
|
+
"{ play() -> Promise<void>, pause(), seek(ms) -> Promise<void>, release() } } }. " +
|
|
3216
|
+
"Backs useGeolocation(), useSpeechToText(), useCamera(), useImageEditor(), useBarcodeScanner(), usePdfExport().openPdf, useAudioRecorder() and useAudioPlayer(). The web Player brokers them via " +
|
|
3217
|
+
"navigator.geolocation, window.SpeechRecognition, a getUserMedia camera preview, a host-side canvas, BarcodeDetector, a new browser tab, MediaRecorder and an <audio> element; the Expo export via " +
|
|
3218
|
+
"expo-location, expo-speech-recognition, expo-image-picker, expo-image-manipulator, expo-camera, expo-print and expo-audio. " +
|
|
3219
|
+
"audioRecorder.start rejects with .code PERMISSION_DENIED | UNSUPPORTED | INTERNAL; getStatus().levelDb is the input level in dBFS " +
|
|
3220
|
+
"(null when unknown); `ended` is true once the host stopped the recording itself (a lost headset, a call), and stop() then " +
|
|
3221
|
+
"still resolves that clip. audioPlayer reports the end of a clip as playing:false at positionMs 0. " +
|
|
3123
3222
|
"pdfViewer.open must be CALLED synchronously from the user's press: the web broker opens its tab before awaiting `load` " +
|
|
3124
3223
|
"(the server render), which is what keeps a popup blocker from refusing it; the Expo export writes the bytes to the " +
|
|
3125
3224
|
"cache and hands them to the OS print sheet. " +
|
|
@@ -3151,6 +3250,8 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
3151
3250
|
imageEditor: "object",
|
|
3152
3251
|
barcode: "object",
|
|
3153
3252
|
pdfViewer: "object",
|
|
3253
|
+
audioRecorder: "object",
|
|
3254
|
+
audioPlayer: "object",
|
|
3154
3255
|
},
|
|
3155
3256
|
},
|
|
3156
3257
|
};
|
|
@@ -3342,7 +3443,8 @@ const VETTED_IMPORTS = [
|
|
|
3342
3443
|
platforms: ["native"],
|
|
3343
3444
|
category: "media",
|
|
3344
3445
|
description:
|
|
3345
|
-
"Native audio playback (Expo SDK 56; replaces the removed expo-av). Native-only; pair with the browser <audio> element in widget.web.jsx."
|
|
3446
|
+
"Native audio playback (Expo SDK 56; replaces the removed expo-av). Native-only; pair with the browser <audio> element in widget.web.jsx. " +
|
|
3447
|
+
"To record or play a clip on BOTH hosts, prefer the SDK's useAudioRecorder() / useAudioPlayer(uri) — no platform split needed.",
|
|
3346
3448
|
},
|
|
3347
3449
|
{
|
|
3348
3450
|
specifier: "expo-video",
|
|
@@ -4957,7 +5059,14 @@ const CONTRACT = deepFreeze({
|
|
|
4957
5059
|
// `{ typingUserIds, setTyping, status }`: the other users typing in one
|
|
4958
5060
|
// record, over the datastore client's single realtime socket. A host
|
|
4959
5061
|
// whose client lacks `records(t).typing` reads `status: "unavailable"`.
|
|
4960
|
-
|
|
5062
|
+
// 1.152.0: additive (sc-10580) — new `useLinkPreview(url)` returns
|
|
5063
|
+
// `{ preview, loading, error, available }` from the new optional
|
|
5064
|
+
// `linkPreviews` slice (@colixsystems/link-preview-client).
|
|
5065
|
+
// 1.153.0: additive (sc-10590) — new `useAudioRecorder()` and
|
|
5066
|
+
// `useAudioPlayer(uri)`, backed by the optional `device.audioRecorder` /
|
|
5067
|
+
// `device.audioPlayer` slices (MediaRecorder + <audio> on web, expo-audio
|
|
5068
|
+
// on native), with `AudioRecorderError` / `AudioPlayerError`.
|
|
5069
|
+
version: "1.153.0",
|
|
4961
5070
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4962
5071
|
hooks: HOOKS,
|
|
4963
5072
|
primitives: PRIMITIVES,
|