@bty/feed_app-runtime-sdk 0.0.9 → 0.1.1

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 CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  Runtime SDK for Feed-App pages.
4
4
 
5
- It provides a small set of browser-safe APIs for reading host user context and
6
- calling the Feed-App AI gateway. The package has no root entry; import from the
7
- capability sub-entries directly.
5
+ It provides a small set of browser-safe APIs for reading host user context,
6
+ calling the Feed-App AI gateway, and reaching native device capabilities. The
7
+ package has no root entry; import from the capability sub-entries directly
8
+ (`/user`, `/ai`, `/react`, `/device`).
8
9
 
9
10
  ## Install
10
11
 
@@ -12,6 +13,23 @@ capability sub-entries directly.
12
13
  pnpm add @bty/feed_app-runtime-sdk
13
14
  ```
14
15
 
16
+ ## Failure conventions
17
+
18
+ Two failure styles, split by sub-entry — know which you're calling:
19
+
20
+ | Sub-entry | On failure | Why |
21
+ |---|---|---|
22
+ | `/ai` | **throws** a typed `AiError` subclass | callers must distinguish 401 / 402 / 429 / 4xx / 5xx to react correctly |
23
+ | `/user`, `/device` | **returns an empty value** (never throws) | "no host / denied / cancelled" is a normal runtime state, not an exception — branch on the value |
24
+
25
+ Empty value = `""` for the one string API (`getAuthTokenAsync`), `null`
26
+ everywhere else (`getUserInfoAsync`, every `/device` call). `saveFile` is the
27
+ one richer case — it resolves to `"saved" | "cancelled" | "failed"` so an
28
+ export button can tell a real save from a dismissed dialog.
29
+
30
+ Rule of thumb: wrap `/ai` calls in `try/catch`; branch on the return value for
31
+ `/user` and `/device`.
32
+
15
33
  ## User
16
34
 
17
35
  ```ts
@@ -64,6 +82,14 @@ The AI entry supports:
64
82
  - `AbortSignal` cancellation
65
83
  - Structured AI errors
66
84
 
85
+ > **Media payloads are pass-through.** `images.generate` / `audio.speech.create`
86
+ > / `video.generations.create` forward the body verbatim to the upstream
87
+ > provider — there is no client-side reshaping, and each model has its own
88
+ > shape. The param types only require `model`; build the rest of the body from
89
+ > the model's `parameters.shape` in the runtime model catalog, not from generic
90
+ > OpenAI SDK fields (`quality: 'standard'`, `style: 'vivid'`, … may be ignored
91
+ > or rejected by the target model).
92
+
67
93
  ```ts
68
94
  import {
69
95
  AuthRequiredError,
@@ -109,6 +135,49 @@ varies enough that a generic hook tends to leak abstractions.
109
135
  React is an optional peer dependency. Projects that do not import the `/react`
110
136
  entry do not need to install React.
111
137
 
138
+ ## Device
139
+
140
+ Five host-bridged capabilities, each with a three-layer fallback: native App
141
+ bridge → browser Web API → safe default. Import the `device` namespace, or the
142
+ individual capabilities for tree-shaking.
143
+
144
+ ```ts
145
+ import {
146
+ haptics,
147
+ geolocation,
148
+ sensors,
149
+ camera,
150
+ files,
151
+ } from '@bty/feed_app-runtime-sdk/device'
152
+
153
+ await haptics.impact('medium')
154
+ const pos = await geolocation.getCurrentPosition() // PositionSample | null
155
+ const stop = sensors.watchMotion((s) => console.log(s.accelerationWithGravity))
156
+ const photo = await camera.capturePhoto({ camera: 'back' }) // CapturedPhoto | null
157
+ const picked = await files.pickFiles({ accept: ['image/*'], multiple: true })
158
+ const result = await files.saveFile(blob, 'export.json') // "saved" | "cancelled" | "failed"
159
+ stop() // dispose the motion subscription
160
+ ```
161
+
162
+ Every call returns the safe default (`null`, or `false` for `haptics`) on
163
+ denial / cancel / unavailability — none of them throw. `pickFiles` resolves to
164
+ `{ files: { file, path? }[] }` (path present only on the native bridge),
165
+ `saveFile` to a `FileSaveResult`.
166
+
167
+ Capability detection (synchronous, cheap — gate UI ahead of a call):
168
+
169
+ | Capability | Probe |
170
+ |---|---|
171
+ | Haptics | `haptics.isSupported()` |
172
+ | Geolocation | `geolocation.isSupported()` |
173
+ | Camera | `camera.isSupported()` |
174
+ | Sensors | `sensors.isMotionSupported()` / `sensors.isOrientationSupported()` |
175
+ | Files | `files.isPickerSupported()` (the *modern* picker; plain pick always works) |
176
+
177
+ `requestMotionPermission()`, `capturePhoto()`, `saveFile()`, and `pickFiles()`
178
+ must be called from inside a user-gesture handler (tap / click) — browsers
179
+ silently deny these outside a gesture.
180
+
112
181
  ## Published Files
113
182
 
114
183
  The npm package publishes only built output and this README:
@@ -99,13 +99,9 @@ interface ChatCompletionChunk {
99
99
  [key: string]: unknown;
100
100
  }
101
101
  interface ImageGenerateParams extends ApiRequestOptions {
102
- prompt: string;
103
- model?: string;
104
- n?: number;
105
- size?: string;
106
- quality?: "standard" | "hd";
107
- style?: "vivid" | "natural";
108
- response_format?: "url" | "b64_json";
102
+ /** Required. A concrete backend-allowlisted image model id. */
103
+ model: string;
104
+ /** Provider-shape fields — copy keys from the model's `parameters.shape`. */
109
105
  [key: string]: unknown;
110
106
  }
111
107
  interface ImageGenerateResponse {
@@ -115,20 +111,18 @@ interface ImageGenerateResponse {
115
111
  b64_json?: string;
116
112
  revised_prompt?: string;
117
113
  }[];
114
+ [key: string]: unknown;
118
115
  }
119
116
  interface AudioSpeechCreateParams extends ApiRequestOptions {
117
+ /** Required. A concrete backend-allowlisted TTS model id. */
120
118
  model: string;
121
- input: string;
122
- voice: string;
123
- response_format?: "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm";
124
- speed?: number;
119
+ /** Provider-shape fields — copy keys from the model's `parameters.shape`. */
125
120
  [key: string]: unknown;
126
121
  }
127
122
  interface VideoGenerateParams extends ApiRequestOptions {
128
- prompt: string;
129
- model?: string;
130
- duration?: number;
131
- size?: string;
123
+ /** Required. A concrete backend-allowlisted video model id. */
124
+ model: string;
125
+ /** Provider-shape fields — copy keys from the model's `parameters.shape`. */
132
126
  [key: string]: unknown;
133
127
  }
134
128
  interface VideoGenerateResponse {
package/dist/ai/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import {d as d$1,c}from'../chunk-AKZVV563.js';import {a}from'../chunk-ALNJCFV4.js';import {createParser}from'eventsource-parser';var O={version:"0.0.9"};var m="/v1/feed-app/runtime/ai",E=`${m}/chat/completions`,_=`${m}/messages`,v=`${m}/images/generations`,I=`${m}/audio/speech`,B=`${m}/video/generations`,M="Authorization",H="x-bty-extend",N="x-bty-app",D=O.version;var j=()=>{};async function*U(e){let t=e.getReader(),r=new TextDecoder,n=[],s=createParser({onEvent(c){n.push({event:c.event||"message",data:c.data});},onRetry:j,onComment:j}),a=false;try{for(;;){let{done:c,value:u}=await t.read();for(u&&s.feed(r.decode(u,{stream:!0}));n.length>0;)yield n.shift();if(c){for(s.reset({consume:!0});n.length>0;)yield n.shift();a=!0;return}}}finally{if(!a)try{await t.cancel();}catch{}t.releaseLock();}}async function*G(e){for await(let t of U(e)){if(t.data==="[DONE]")return;t.data&&(yield JSON.parse(t.data));}}async function*q(e){for await(let t of U(e))t.data&&(yield JSON.parse(t.data));}var Q="https://reactus-api.happyseeds.ai",J=e=>{try{let t=e();if(typeof t=="string"&&t.length>0)return t}catch{}},X=()=>J(()=>typeof __BTY_RUNTIME_API_BASE_URL__=="string"?__BTY_RUNTIME_API_BASE_URL__:void 0),z=()=>J(()=>typeof __BTY_RUNTIME_PROJECT_ID__=="string"?__BTY_RUNTIME_PROJECT_ID__:void 0),T={apiBaseUrl:(X()??Q).replace(/\/+$/,""),projectId:z()??""},W=e=>{T={...T,...e,...e.apiBaseUrl?{apiBaseUrl:e.apiBaseUrl.replace(/\/+$/,"")}:{}};},f=()=>T;var o=class extends Error{status;code;body;constructor(t,r,n,s=null){super(t),this.name="AiError",this.code=r,this.status=n,this.body=s;}},i=class extends o{constructor(t="Auth required",r=null){super(t,"auth_required",401,r),this.name="AuthRequiredError";}},h=class extends o{constructor(t="Rate limit exceeded",r=null){super(t,"rate_limit",429,r),this.name="RateLimitError";}},g=class extends o{constructor(t="Quota exceeded",r=null){super(t,"quota_exceeded",402,r),this.name="QuotaExceededError";}},y=class extends o{constructor(t,r=400,n=null){super(t,"bad_input",r,n),this.name="BadInputError";}},A=class extends o{constructor(t,r=500,n=null){super(t,"server",r,n),this.name="ServerError";}},p=class extends o{constructor(t="Network error",r){super(t,"network",-1,r),this.name="NetworkError";}},l=class extends o{constructor(t="Request aborted"){super(t,"aborted",-1,null),this.name="AbortedError";}},V=e=>typeof e=="object"&&e!==null,Z=(e,t)=>{if(typeof e=="string")return e||t;if(!V(e))return t;let r=e.message;if(typeof r=="string"&&r.length>0)return r;let n=e.error;if(typeof n=="string"&&n.length>0)return n;if(V(n)&&typeof n.message=="string"&&n.message.length>0)return n.message;let s=e.detail;return typeof s=="string"&&s.length>0?s:t},x=(e,t)=>{let r=Z(t,`HTTP ${e}`);return e===401||e===403?new i(r,t):e===402?new g(r,t):e===429?new h(r,t):e>=400&&e<500?new y(r,e,t):e>=500?new A(r,e,t):new o(r,"unknown",e,t)};var ee=e=>JSON.stringify({"sdk-version":D,...e}),$=async(e={})=>{let t=await c(),r=new Headers;t&&r.set(M,`Bearer ${t}`),r.set(H,ee(e.extend));let{projectId:n}=f();if(n&&r.set(N,n),e.contentType&&r.set("Content-Type",e.contentType),e.accept&&r.set("Accept",e.accept),e.extra)for(let[s,a]of Object.entries(e.extra))r.set(s,a);return r};var te="feed-app-runtime-sdk:auth-required",w=e=>{a()&&window.dispatchEvent(new CustomEvent(te,{detail:e}));};var re=e=>e instanceof DOMException&&e.name==="AbortError",ne=async e=>{let t=e.headers.get("content-type")??"";try{return t.includes("application/json")?await e.json():await e.text()}catch{return null}},se=e=>typeof e=="object"&&e!==null,oe=(e,t)=>{if(!se(e)||typeof e.success!="boolean")return e;if(e.success)return e.data;let r=typeof e.code=="number"?e.code:t;throw x(r,e)},L=async(e,t)=>{let{apiBaseUrl:r}=f(),n=`${r}${e}`,s=await $(t),a;try{a=await fetch(n,{method:t.method??"POST",headers:s,body:t.body,signal:t.signal});}catch(u){throw re(u)?new l:new p("Failed to reach AI backend",u)}if(a.ok)return a;let c=await ne(a);throw x(a.status,c)},P=async(e,t={})=>{try{return await L(e,t)}catch(r){if(r instanceof i&&!t.skipAuthRetry&&!t.signal?.aborted){if(!await d$1())throw w({reason:"refresh_failed",error:r}),r;try{return await L(e,{...t,skipAuthRetry:!0})}catch(s){throw s instanceof i&&w({reason:"retry_rejected",error:s}),s}}throw r}},d=async(e,t,r={})=>{let n=await P(e,{...r,method:"POST",contentType:"application/json",body:JSON.stringify(t)});return oe(await n.json(),n.status)};var C=async(e,t,r={})=>{let n=await P(e,{...r,method:"POST",contentType:"application/json",accept:"text/event-stream",body:JSON.stringify(t)});if(!n.body)throw new p("Streaming response has no body");return n.body},F=async(e,t,r={})=>await(await P(e,{...r,method:"POST",contentType:"application/json",body:JSON.stringify(t)})).blob();var R=e=>{let{signal:t,headers:r,...n}=e;return {transport:{signal:t,extra:r},payload:n}},ae=(async e=>{let{transport:t,payload:r}=R(e);if(e.stream){let n=await C(E,r,t);return G(n)}return await d(E,r,t)}),ie={create:ae},ce={async generate(e){let{transport:t,payload:r}=R(e);return d(v,r,t)}},pe={speech:{async create(e){let{transport:t,payload:r}=R(e);return F(I,r,t)}}},de={generations:{async create(e){let{transport:t,payload:r}=R(e);return d(B,r,t)}}},ue={chat:{completions:ie},images:ce,audio:pe,video:de};var me=e=>{let{signal:t,headers:r,...n}=e;return {transport:{signal:t,extra:r},payload:n}},le=(async e=>{let{transport:t,payload:r}=me(e);if(e.stream){let n=await C(_,r,t);return q(n)}return await d(_,r,t)}),fe={messages:{create:le}};
1
+ import {d as d$1,c}from'../chunk-AKZVV563.js';import {a}from'../chunk-ALNJCFV4.js';import {createParser}from'eventsource-parser';var O={version:"0.1.1"};var m="/v1/feed-app/runtime/ai",E=`${m}/chat/completions`,_=`${m}/messages`,v=`${m}/images/generations`,I=`${m}/audio/speech`,B=`${m}/video/generations`,M="Authorization",H="x-bty-extend",N="x-bty-app",D=O.version;var j=()=>{};async function*U(e){let t=e.getReader(),r=new TextDecoder,n=[],s=createParser({onEvent(c){n.push({event:c.event||"message",data:c.data});},onRetry:j,onComment:j}),a=false;try{for(;;){let{done:c,value:u}=await t.read();for(u&&s.feed(r.decode(u,{stream:!0}));n.length>0;)yield n.shift();if(c){for(s.reset({consume:!0});n.length>0;)yield n.shift();a=!0;return}}}finally{if(!a)try{await t.cancel();}catch{}t.releaseLock();}}async function*G(e){for await(let t of U(e)){if(t.data==="[DONE]")return;t.data&&(yield JSON.parse(t.data));}}async function*q(e){for await(let t of U(e))t.data&&(yield JSON.parse(t.data));}var Q="https://reactus-api.happyseeds.ai",J=e=>{try{let t=e();if(typeof t=="string"&&t.length>0)return t}catch{}},X=()=>J(()=>typeof __BTY_RUNTIME_API_BASE_URL__=="string"?__BTY_RUNTIME_API_BASE_URL__:void 0),z=()=>J(()=>typeof __BTY_RUNTIME_PROJECT_ID__=="string"?__BTY_RUNTIME_PROJECT_ID__:void 0),T={apiBaseUrl:(X()??Q).replace(/\/+$/,""),projectId:z()??""},W=e=>{T={...T,...e,...e.apiBaseUrl?{apiBaseUrl:e.apiBaseUrl.replace(/\/+$/,"")}:{}};},f=()=>T;var o=class extends Error{status;code;body;constructor(t,r,n,s=null){super(t),this.name="AiError",this.code=r,this.status=n,this.body=s;}},i=class extends o{constructor(t="Auth required",r=null){super(t,"auth_required",401,r),this.name="AuthRequiredError";}},h=class extends o{constructor(t="Rate limit exceeded",r=null){super(t,"rate_limit",429,r),this.name="RateLimitError";}},g=class extends o{constructor(t="Quota exceeded",r=null){super(t,"quota_exceeded",402,r),this.name="QuotaExceededError";}},y=class extends o{constructor(t,r=400,n=null){super(t,"bad_input",r,n),this.name="BadInputError";}},A=class extends o{constructor(t,r=500,n=null){super(t,"server",r,n),this.name="ServerError";}},p=class extends o{constructor(t="Network error",r){super(t,"network",-1,r),this.name="NetworkError";}},l=class extends o{constructor(t="Request aborted"){super(t,"aborted",-1,null),this.name="AbortedError";}},V=e=>typeof e=="object"&&e!==null,Z=(e,t)=>{if(typeof e=="string")return e||t;if(!V(e))return t;let r=e.message;if(typeof r=="string"&&r.length>0)return r;let n=e.error;if(typeof n=="string"&&n.length>0)return n;if(V(n)&&typeof n.message=="string"&&n.message.length>0)return n.message;let s=e.detail;return typeof s=="string"&&s.length>0?s:t},x=(e,t)=>{let r=Z(t,`HTTP ${e}`);return e===401||e===403?new i(r,t):e===402?new g(r,t):e===429?new h(r,t):e>=400&&e<500?new y(r,e,t):e>=500?new A(r,e,t):new o(r,"unknown",e,t)};var ee=e=>JSON.stringify({"sdk-version":D,...e}),$=async(e={})=>{let t=await c(),r=new Headers;t&&r.set(M,`Bearer ${t}`),r.set(H,ee(e.extend));let{projectId:n}=f();if(n&&r.set(N,n),e.contentType&&r.set("Content-Type",e.contentType),e.accept&&r.set("Accept",e.accept),e.extra)for(let[s,a]of Object.entries(e.extra))r.set(s,a);return r};var te="feed-app-runtime-sdk:auth-required",w=e=>{a()&&window.dispatchEvent(new CustomEvent(te,{detail:e}));};var re=e=>e instanceof DOMException&&e.name==="AbortError",ne=async e=>{let t=e.headers.get("content-type")??"";try{return t.includes("application/json")?await e.json():await e.text()}catch{return null}},se=e=>typeof e=="object"&&e!==null,oe=(e,t)=>{if(!se(e)||typeof e.success!="boolean")return e;if(e.success)return e.data;let r=typeof e.code=="number"?e.code:t;throw x(r,e)},L=async(e,t)=>{let{apiBaseUrl:r}=f(),n=`${r}${e}`,s=await $(t),a;try{a=await fetch(n,{method:t.method??"POST",headers:s,body:t.body,signal:t.signal});}catch(u){throw re(u)?new l:new p("Failed to reach AI backend",u)}if(a.ok)return a;let c=await ne(a);throw x(a.status,c)},P=async(e,t={})=>{try{return await L(e,t)}catch(r){if(r instanceof i&&!t.skipAuthRetry&&!t.signal?.aborted){if(!await d$1())throw w({reason:"refresh_failed",error:r}),r;try{return await L(e,{...t,skipAuthRetry:!0})}catch(s){throw s instanceof i&&w({reason:"retry_rejected",error:s}),s}}throw r}},d=async(e,t,r={})=>{let n=await P(e,{...r,method:"POST",contentType:"application/json",body:JSON.stringify(t)});return oe(await n.json(),n.status)};var C=async(e,t,r={})=>{let n=await P(e,{...r,method:"POST",contentType:"application/json",accept:"text/event-stream",body:JSON.stringify(t)});if(!n.body)throw new p("Streaming response has no body");return n.body},F=async(e,t,r={})=>await(await P(e,{...r,method:"POST",contentType:"application/json",body:JSON.stringify(t)})).blob();var R=e=>{let{signal:t,headers:r,...n}=e;return {transport:{signal:t,extra:r},payload:n}},ae=(async e=>{let{transport:t,payload:r}=R(e);if(e.stream){let n=await C(E,r,t);return G(n)}return await d(E,r,t)}),ie={create:ae},ce={async generate(e){let{transport:t,payload:r}=R(e);return d(v,r,t)}},pe={speech:{async create(e){let{transport:t,payload:r}=R(e);return F(I,r,t)}}},de={generations:{async create(e){let{transport:t,payload:r}=R(e);return d(B,r,t)}}},ue={chat:{completions:ie},images:ce,audio:pe,video:de};var me=e=>{let{signal:t,headers:r,...n}=e;return {transport:{signal:t,extra:r},payload:n}},le=(async e=>{let{transport:t,payload:r}=me(e);if(e.stream){let n=await C(_,r,t);return q(n)}return await d(_,r,t)}),fe={messages:{create:le}};
2
2
  export{l as AbortedError,o as AiError,i as AuthRequiredError,y as BadInputError,p as NetworkError,g as QuotaExceededError,h as RateLimitError,A as ServerError,fe as anthropic,W as configureRuntime,ue as openai};
@@ -72,9 +72,29 @@ interface PickFilesOptions {
72
72
  multiple?: boolean;
73
73
  directory?: boolean;
74
74
  }
75
+ interface PickedFile {
76
+ file: File;
77
+ path?: string;
78
+ }
75
79
  interface PickedFiles {
76
- files: File[];
77
- paths: string[];
80
+ files: PickedFile[];
81
+ }
82
+ type FileSaveResult = "saved" | "cancelled" | "failed";
83
+ interface AudioRecordingOptions {
84
+ maxDurationMs?: number;
85
+ mimeType?: string;
86
+ audioBitsPerSecond?: number;
87
+ }
88
+ interface AudioRecording {
89
+ blob: Blob;
90
+ mimeType: string;
91
+ durationMs: number;
92
+ size: number;
93
+ }
94
+ interface AudioRecorder {
95
+ stop(): Promise<AudioRecording | null>;
96
+ cancel(): void;
97
+ readonly active: boolean;
78
98
  }
79
99
 
80
100
  declare const camera: {
@@ -114,8 +134,10 @@ declare const files: {
114
134
  isPickerSupported(): boolean;
115
135
  /**
116
136
  * Pick one or more files. Returns null on user cancel or unavailability.
117
- * `paths` is populated only when the native bridge serves the request;
118
- * web fallbacks have no path concept.
137
+ * Each entry is a `{ file, path? }` — `path` is set only when the native
138
+ * bridge serves the request; web fallbacks have no path concept and omit
139
+ * it. Pairing path with its file avoids the index drift a separate
140
+ * `paths[]` array invites.
119
141
  */
120
142
  pickFiles(opts?: PickFilesOptions): Promise<PickedFiles | null>;
121
143
  /**
@@ -123,10 +145,14 @@ declare const files: {
123
145
  * a directory and write in place; web fallback uses the File System Access
124
146
  * save dialog when available, otherwise triggers an anchor download.
125
147
  *
126
- * Returns once the save has been initiated. There is no progress callback —
127
- * Blobs in web platforms write atomically.
148
+ * Resolves to a `FileSaveResult` `"saved"` on success, `"cancelled"` when
149
+ * the user dismisses the dialog, `"failed"` on any error or unavailable
150
+ * environment. Never throws, so a "Export" button can branch on the outcome
151
+ * (toast on saved, stay quiet on cancelled, error hint on failed) instead of
152
+ * assuming success. There is no progress callback — web Blobs write
153
+ * atomically.
128
154
  */
129
- saveFile(blob: Blob, filename: string): Promise<void>;
155
+ saveFile(blob: Blob, filename: string): Promise<FileSaveResult>;
130
156
  /**
131
157
  * Read the contents of a File / Blob as a UTF-8 string. Pure web — does
132
158
  * not consult the native bridge. The blob already lives in the page's
@@ -191,6 +217,48 @@ declare const haptics: {
191
217
  vibrate(pattern: number | number[]): Promise<void>;
192
218
  };
193
219
 
220
+ declare const microphone: {
221
+ isSupported(): boolean;
222
+ /**
223
+ * Ask the host (native App or browser) for microphone permission. The web
224
+ * path opens a short-lived `getUserMedia({audio:true})` stream purely to
225
+ * trigger the permission prompt, then releases it immediately — so it
226
+ * **must be called inside a user-gesture handler** on iOS Safari, same as
227
+ * camera/sensors. The native-bridge variant routes through the App's own
228
+ * permission prompt and has no gesture requirement.
229
+ *
230
+ * Returns true if recording is permitted, false on denial / no hardware.
231
+ */
232
+ requestPermission(): Promise<boolean>;
233
+ /**
234
+ * Record one audio clip and resolve when capture finishes. The native
235
+ * bridge runs the host App's recording UI (record button, waveform, native
236
+ * encoder) and returns a finished file; the web fallback records via
237
+ * `MediaRecorder` and **requires `maxDurationMs`** to know when to stop,
238
+ * since the headless path has no UI for the user to tap "done".
239
+ *
240
+ * Must be called inside a user-gesture handler to satisfy mic-permission /
241
+ * autoplay rules on iOS Safari and modern Android browsers. Returns null on
242
+ * permission denial, hardware absence, or any failure on either path.
243
+ *
244
+ * For press-and-hold or tap-to-stop UIs where the caller decides when to
245
+ * stop, use `startRecording` instead.
246
+ */
247
+ recordAudio(opts?: AudioRecordingOptions): Promise<AudioRecording | null>;
248
+ /**
249
+ * Begin caller-controlled recording and return a handle to stop / cancel on
250
+ * your own schedule — for push-to-talk, tap-to-start/tap-to-stop, or any UI
251
+ * that shows a live "recording…" state. Web-only by design: streaming raw
252
+ * audio through a JS bridge is impractical, and host Apps that need mic
253
+ * access route it through `recordAudio`'s full-screen UI instead.
254
+ *
255
+ * Must be called inside a user-gesture handler. Returns null when the mic is
256
+ * unavailable or the user denies access. The caller MUST eventually call
257
+ * `.stop()` or `.cancel()` to release the microphone.
258
+ */
259
+ startRecording(opts?: AudioRecordingOptions): Promise<AudioRecorder | null>;
260
+ };
261
+
194
262
  declare const sensors: {
195
263
  isMotionSupported(): boolean;
196
264
  isOrientationSupported(): boolean;
@@ -218,43 +286,49 @@ declare const sensors: {
218
286
  watchOrientation(onSample: (s: OrientationSample) => void): () => void;
219
287
  };
220
288
 
221
- type DeviceFeature = "haptics" | "geolocation" | "sensors" | "camera" | "files";
289
+ type DeviceFeature = "haptics" | "geolocation" | "sensors" | "camera" | "files" | "microphone";
222
290
 
223
291
  declare const meetsFeatureMinVersion: (feature: DeviceFeature, env: AppEnvironment) => boolean;
224
292
 
225
293
  declare const device: {
226
- haptics: {
294
+ readonly haptics: {
227
295
  isSupported(): boolean;
228
296
  impact(strength?: HapticImpactStrength): Promise<void>;
229
297
  selection(): Promise<void>;
230
298
  notification(kind: HapticNotificationKind): Promise<void>;
231
299
  vibrate(pattern: number | number[]): Promise<void>;
232
300
  };
233
- geolocation: {
301
+ readonly geolocation: {
234
302
  isSupported(): boolean;
235
303
  getCurrentPosition(opts?: PositionRequestOptions): Promise<PositionSample | null>;
236
304
  watchPosition(onSample: (sample: PositionSample) => void, opts?: PositionRequestOptions): () => void;
237
305
  };
238
- sensors: {
306
+ readonly sensors: {
239
307
  isMotionSupported(): boolean;
240
308
  isOrientationSupported(): boolean;
241
309
  requestMotionPermission(): Promise<boolean>;
242
310
  watchMotion(onSample: (s: MotionSample) => void, opts?: MotionWatchOptions): () => void;
243
311
  watchOrientation(onSample: (s: OrientationSample) => void): () => void;
244
312
  };
245
- camera: {
313
+ readonly camera: {
246
314
  isSupported(): boolean;
247
315
  capturePhoto(opts?: PhotoCaptureOptions): Promise<CapturedPhoto | null>;
248
316
  openStream(opts?: StreamOptions): Promise<MediaStream | null>;
249
317
  stopStream(stream: MediaStream): void;
250
318
  };
251
- files: {
319
+ readonly files: {
252
320
  isPickerSupported(): boolean;
253
321
  pickFiles(opts?: PickFilesOptions): Promise<PickedFiles | null>;
254
- saveFile(blob: Blob, filename: string): Promise<void>;
322
+ saveFile(blob: Blob, filename: string): Promise<FileSaveResult>;
255
323
  readAsText(file: File | Blob): Promise<string>;
256
324
  readAsDataUrl(file: File | Blob): Promise<string>;
257
325
  };
326
+ readonly microphone: {
327
+ isSupported(): boolean;
328
+ requestPermission(): Promise<boolean>;
329
+ recordAudio(opts?: AudioRecordingOptions): Promise<AudioRecording | null>;
330
+ startRecording(opts?: AudioRecordingOptions): Promise<AudioRecorder | null>;
331
+ };
258
332
  };
259
333
 
260
- export { type CameraFacing, type CapturedPhoto, type DeviceFeature, type HapticImpactStrength, type HapticNotificationKind, type MotionSample, type MotionWatchOptions, type OrientationSample, type PhotoCaptureOptions, type PhotoFormat, type PhotoQuality, type PickFilesOptions, type PickedFiles, type PositionRequestOptions, type PositionSample, type StreamOptions, type Vector3, camera, device, files, geolocation, haptics, meetsFeatureMinVersion, sensors };
334
+ export { type AudioRecorder, type AudioRecording, type AudioRecordingOptions, type CameraFacing, type CapturedPhoto, type DeviceFeature, type HapticImpactStrength, type HapticNotificationKind, type MotionSample, type MotionWatchOptions, type OrientationSample, type PhotoCaptureOptions, type PhotoFormat, type PhotoQuality, type PickFilesOptions, type PickedFiles, type PositionRequestOptions, type PositionSample, type StreamOptions, type Vector3, camera, device, files, geolocation, haptics, meetsFeatureMinVersion, microphone, sensors };
@@ -1 +1 @@
1
- import {a,d as d$1,u as u$1,t,v,c,b,x,e,s,r,w}from'../chunk-ALNJCFV4.js';import {fileSave,directoryOpen,fileOpen}from'browser-fs-access';var L=false,S=()=>{!a()||L||(L=true,d$1());};var B="device.haptics",M="device.geolocation",H="device.sensors.motion",V="device.sensors.orientation",W="device.sensors.permission",G="device.camera",R="device.files",q="device.haptics.impact",j="device.haptics.selection",z="device.haptics.notification",K="device.haptics.vibrate",Q="device.geolocation.get",J="device.geolocation.watch.start",Y="device.geolocation.watch.stop",$="device.sensors.motion.start",X="device.sensors.motion.stop",Z="device.sensors.orientation.start",ee="device.sensors.orientation.stop",te="device.sensors.requestPermission",ne="device.camera.capture",oe="device.files.pick",ie="device.files.save";var re={haptics:{iOS:null,Android:null},geolocation:{iOS:null,Android:null},sensors:{iOS:null,Android:null},camera:{iOS:null,Android:null},files:{iOS:null,Android:null}};var C=(e,n)=>{if(n.type!=="native_app"||!n.platform||!n.appVersion)return false;let t$1=re[e][n.platform];return t$1?u$1(t(n.appVersion),t$1):false},I=e=>JSON.stringify(e),ae=(e,n)=>{if(typeof e!="object"||e===null||Reflect.get(e,"ok")!==true)return null;let o=Reflect.get(e,"result");return n(o)},se=async e$1=>e$1.platform==="Android"?x(e,{pollIntervalMs:r,timeoutMs:s}):w(e),p=async e=>{if(!a())return null;S();let n=v();if(!C(e.feature,n))return null;let t=await se(n);if(!t)return null;let o=c(e.endpointPrefix),i=e.timeoutMs??3e3;return new Promise(r=>{let a=false,l=v=>{a||(a=true,c(),clearTimeout(m),r(v));},c=b.on(o,v=>{let g=ae(v,e.parseResult);l(g);}),m=setTimeout(()=>l(null),i);try{t.postMessage({command:e.command,parameters:I({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{l(null);}})},E=async e=>{if(!a())return null;S();let n=v();if(!C(e.feature,n))return null;let t=await se(n);if(!t)return null;let o=c(e.endpointPrefix),i=b.on(o,r=>{let a=ae(r,e.parseEvent);a&&e.onEvent(a);});try{t.postMessage({command:e.startCommand,parameters:I({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{return i(),null}return ()=>{i();try{t.postMessage({command:e.stopCommand,parameters:I({endpoint:o,timestamp:Date.now(),payload:null})});}catch{}}};var d=async e=>{if(!a())return e.safeDefault;let n=await e.native();if(n!==null)return n;try{let t=await e.web();if(t!==null)return t}catch{}return e.safeDefault};var le=e=>e==="front"?"user":"environment",_e={low:.5,medium:.8,high:.95},Te=e=>{let n=e.indexOf(",");if(n<0)return null;let t=e.slice(5,n),o=e.slice(n+1),r=/^([^;]+)/.exec(t)?.[1]??"application/octet-stream";try{if(/;base64/.test(t)){let l=atob(o),c=new Uint8Array(l.length);for(let m=0;m<l.length;m++)c[m]=l.charCodeAt(m);return new Blob([c],{type:r})}return new Blob([decodeURIComponent(o)],{type:r})}catch{return null}},be=e=>{if(typeof e!="object"||e===null)return null;let n=Reflect.get(e,"dataUrl"),t=Reflect.get(e,"width"),o=Reflect.get(e,"height");if(typeof n!="string"||n.length===0||typeof t!="number"||typeof o!="number")return null;let i=Te(n);return i?{blob:i,dataUrl:n,width:t,height:o}:null},Me=async e=>{if(!a()||!navigator.mediaDevices?.getUserMedia)return null;let n=null;try{n=await navigator.mediaDevices.getUserMedia({video:{facingMode:le(e?.camera),width:e?.width,height:e?.height}});let t=document.createElement("video");t.srcObject=n,t.muted=!0,t.playsInline=!0,await t.play(),await new Promise(h=>{requestAnimationFrame(()=>h());});let o=e?.width??t.videoWidth??640,i=e?.height??t.videoHeight??480,r=document.createElement("canvas");r.width=o,r.height=i;let a=r.getContext("2d");if(!a)return null;a.drawImage(t,0,0,o,i);let c=`image/${e?.format??"jpeg"}`,m=_e[e?.quality??"medium"],v=r.toDataURL(c,m),g=await new Promise(h=>{r.toBlob(he=>h(he),c,m);});return g?{blob:g,dataUrl:v,width:o,height:i}:null}catch{return null}finally{if(n)for(let t of n.getTracks())t.stop();}},ce={isSupported(){return a()?!!navigator.mediaDevices?.getUserMedia:false},capturePhoto(e){return d({native:()=>p({feature:"camera",command:ne,endpointPrefix:G,payload:e??{},parseResult:be,timeoutMs:6e4}),web:()=>Me(e),safeDefault:null})},async openStream(e){if(!a()||!navigator.mediaDevices?.getUserMedia)return null;try{return await navigator.mediaDevices.getUserMedia({video:{facingMode:le(e?.camera),width:e?.width,height:e?.height}})}catch{return null}},stopStream(e){try{for(let n of e.getTracks())n.stop();}catch{}}};var me=(e,n)=>{let t=atob(e),o=new Uint8Array(t.length);for(let i=0;i<t.length;i++)o[i]=t.charCodeAt(i);return new Blob([o],{type:n})},Ce=e=>{let n=null;if(e.dataUrl){let t=e.dataUrl.indexOf(",");if(t>0){let o=e.dataUrl.slice(5,t),i=e.dataUrl.slice(t+1),r=/^([^;]+)/.exec(o)?.[1]??"application/octet-stream";try{n=/;base64/.test(o)?me(i,r):new Blob([decodeURIComponent(i)],{type:r});}catch{n=null;}}}else e.base64&&(n=me(e.base64,e.mime??"application/octet-stream"));return n?new File([n],e.name,{type:e.mime??n.type,lastModified:Date.now()}):null},Ae=e=>{if(typeof e!="object"||e===null)return null;let n=Reflect.get(e,"files");if(!Array.isArray(n))return null;let t=[],o=[];for(let r of n){if(typeof r!="object"||r===null)continue;let a=r,l=Ce(a);l&&(t.push(l),typeof a.path=="string"&&o.push(a.path));}if(t.length===0)return null;let i=Reflect.get(e,"paths");if(Array.isArray(i)){o.length=0;for(let r of i)typeof r=="string"&&o.push(r);}return {files:t,paths:o}},Ne=e=>{let n=[],t=[];if(!e)return {mimeTypes:n,extensions:t};for(let o of e)for(let i of o.split(",")){let r=i.trim();r&&(r.startsWith(".")?t.push(r):n.push(r));}return {mimeTypes:n,extensions:t}},De=e=>e instanceof DOMException&&e.name==="AbortError",we=async e=>{if(!a())return null;try{if(e?.directory){let i=await directoryOpen({recursive:!0});return i.length>0?{files:i,paths:[]}:null}let{mimeTypes:n,extensions:t}=Ne(e?.accept);if(e?.multiple){let i=await fileOpen({mimeTypes:n,extensions:t,multiple:!0});return i.length>0?{files:i,paths:[]}:null}return {files:[await fileOpen({mimeTypes:n,extensions:t})],paths:[]}}catch(n){return De(n),null}},xe=async(e,n)=>{if(a())try{await fileSave(e,{fileName:n});}catch{}},pe={isPickerSupported(){return a()?typeof window.showOpenFilePicker=="function":false},pickFiles(e){return d({native:()=>p({feature:"files",command:oe,endpointPrefix:R,payload:e??{},parseResult:Ae,timeoutMs:6e4}),web:()=>we(e),safeDefault:null})},async saveFile(e,n){!a()||await p({feature:"files",command:ie,endpointPrefix:R,payload:{filename:n,mime:e.type,size:e.size},parseResult:()=>true,timeoutMs:6e4})||await xe(e,n);},readAsText(e){return typeof e.text=="function"?e.text():new Promise((n,t)=>{let o=new FileReader;o.onload=()=>{n(typeof o.result=="string"?o.result:"");},o.onerror=()=>t(o.error),o.readAsText(e);})},readAsDataUrl(e){return new Promise((n,t)=>{let o=new FileReader;o.onload=()=>{n(typeof o.result=="string"?o.result:"");},o.onerror=()=>t(o.error),o.readAsDataURL(e);})}};var f=(e,...n)=>{for(let t of n){let o=Reflect.get(e,t);if(typeof o=="number"&&!Number.isNaN(o))return o}},de=e=>{if(typeof e!="object"||e===null)return null;let n=f(e,"latitude"),t=f(e,"longitude"),o=f(e,"accuracyMeters","accuracy");if(n===void 0||t===void 0||o===void 0)return null;let i={latitude:n,longitude:t,accuracyMeters:o,timestamp:f(e,"timestamp")??Date.now()},r=f(e,"altitudeMeters","altitude");r!==void 0&&(i.altitudeMeters=r);let a=f(e,"altitudeAccuracyMeters","altitudeAccuracy");a!==void 0&&(i.altitudeAccuracyMeters=a);let l=f(e,"headingDegrees","heading");l!==void 0&&(i.headingDegrees=l);let c=f(e,"speedMetersPerSecond","speed");return c!==void 0&&(i.speedMetersPerSecond=c),i},fe=e=>({latitude:e.coords.latitude,longitude:e.coords.longitude,accuracyMeters:e.coords.accuracy,altitudeMeters:e.coords.altitude??void 0,altitudeAccuracyMeters:e.coords.altitudeAccuracy??void 0,headingDegrees:e.coords.heading??void 0,speedMetersPerSecond:e.coords.speed??void 0,timestamp:e.timestamp}),ve=e=>e?{enableHighAccuracy:e.enableHighAccuracy,timeout:e.timeoutMs,maximumAge:e.maximumAgeMs}:void 0,Fe=e=>!a()||!navigator.geolocation?Promise.resolve(null):new Promise(n=>{navigator.geolocation.getCurrentPosition(t=>n(fe(t)),()=>n(null),ve(e));}),Se={isSupported(){return a()?!!navigator.geolocation:false},getCurrentPosition(e){return d({native:()=>p({feature:"geolocation",command:Q,endpointPrefix:M,payload:e??{},parseResult:de,timeoutMs:e?.timeoutMs}),web:()=>Fe(e),safeDefault:null})},watchPosition(e,n){let t=false,o=null,i=null;return E({feature:"geolocation",startCommand:J,stopCommand:Y,endpointPrefix:M,payload:n??{},parseEvent:de,onEvent:r=>{t||e(r);}}).then(r=>{if(t){r?.();return}if(r){o=r;return}if(!(!a()||!navigator.geolocation))try{i=navigator.geolocation.watchPosition(a=>{t||e(fe(a));},()=>{},ve(n));}catch{}}),()=>{if(t=true,o&&o(),i!==null&&a()&&navigator.geolocation)try{navigator.geolocation.clearWatch(i);}catch{}}}};var ke={light:10,medium:20,heavy:40,soft:15,rigid:30},Ue={success:[20,60,20],warning:[40,40,40],error:[50,30,100]},Le=e=>{if(typeof navigator>"u"||typeof navigator.vibrate!="function")return false;try{return navigator.vibrate(e)}catch{return false}},O=(e,n,t)=>d({native:()=>p({feature:"haptics",command:e,endpointPrefix:B,payload:n,parseResult:()=>true}),web:async()=>Le(t),safeDefault:false}),Ee={isSupported(){return a()?typeof navigator<"u"&&"vibrate"in navigator:false},async impact(e="medium"){await O(q,{strength:e},ke[e]);},async selection(){await O(j,{},8);},async notification(e){await O(z,{kind:e},Ue[e]);},async vibrate(e){await O(K,{pattern:e},e);}};var Pe=()=>typeof DeviceMotionEvent>"u"?false:typeof DeviceMotionEvent.requestPermission=="function",ge=async()=>{if(!Pe())return true;try{let e=DeviceMotionEvent.requestPermission;return e?await e()==="granted":!0}catch{return false}},u=(e,...n)=>{for(let t of n){let o=Reflect.get(e,t);if(typeof o=="number"&&!Number.isNaN(o))return o}},A=(e,n)=>{let t=Reflect.get(e,n);if(typeof t!="object"||t===null)return;let o=u(t,"x"),i=u(t,"y"),r=u(t,"z");if(!(o===void 0||i===void 0||r===void 0))return {x:o,y:i,z:r}},Be=e=>{if(typeof e!="object"||e===null)return null;let n=A(e,"accelerationWithGravity")??A(e,"accelerationIncludingGravity");if(!n)return null;let t=Reflect.get(e,"rotationRate"),o={alpha:0,beta:0,gamma:0};typeof t=="object"&&t!==null&&(o={alpha:u(t,"alpha")??0,beta:u(t,"beta")??0,gamma:u(t,"gamma")??0});let i={accelerationWithGravity:n,rotationRate:o,timestamp:u(e,"timestamp")??Date.now()},r=A(e,"acceleration");r&&(i.acceleration=r);let a=Reflect.get(e,"attitude");if(typeof a=="object"&&a!==null){let l=u(a,"yaw"),c=u(a,"pitch"),m=u(a,"roll");l!==void 0&&c!==void 0&&m!==void 0&&(i.attitude={yaw:l,pitch:c,roll:m});}return i},He=e=>{if(typeof e!="object"||e===null)return null;let n=u(e,"alpha"),t=u(e,"beta"),o=u(e,"gamma");return n===void 0||t===void 0||o===void 0?null:{alpha:n,beta:t,gamma:o,timestamp:u(e,"timestamp")??Date.now()}},Ve=e=>{let n=e.accelerationIncludingGravity,t=e.acceleration,o=e.rotationRate,i={accelerationWithGravity:{x:n?.x??0,y:n?.y??0,z:n?.z??0},rotationRate:{alpha:o?.alpha??0,beta:o?.beta??0,gamma:o?.gamma??0},timestamp:e.timeStamp||Date.now()};return t&&(t.x!==null||t.y!==null||t.z!==null)&&(i.acceleration={x:t.x??0,y:t.y??0,z:t.z??0}),i},We=e=>({alpha:e.alpha??0,beta:e.beta??0,gamma:e.gamma??0,timestamp:e.timeStamp||Date.now()}),Oe={isMotionSupported(){return a()?typeof DeviceMotionEvent<"u":false},isOrientationSupported(){return a()?typeof DeviceOrientationEvent<"u":false},async requestMotionPermission(){if(!a())return false;let e=await p({feature:"sensors",command:te,endpointPrefix:W,payload:{},parseResult:n=>{if(typeof n=="boolean")return n;if(typeof n=="object"&&n!==null){let t=Reflect.get(n,"granted");if(typeof t=="boolean")return t}return null}});return e!==null?e:ge()},watchMotion(e,n){let t=false,o=null,i=null,r=()=>{if(t||!a())return;let l=c=>{t||e(Ve(c));};window.addEventListener("devicemotion",l),i=l;};return (async()=>{let l=await E({feature:"sensors",startCommand:$,stopCommand:X,endpointPrefix:H,payload:n??{},parseEvent:Be,onEvent:c=>{t||e(c);}});if(t){l?.();return}if(l){o=l;return}n?.autoRequestPermission&&Pe()&&(!await ge()||t)||r();})(),()=>{t=true,o&&o(),i&&a()&&(window.removeEventListener("devicemotion",i),i=null);}},watchOrientation(e){let n=false,t=null,o=null,i=()=>{if(n||!a())return;let a$1=l=>{n||e(We(l));};window.addEventListener("deviceorientation",a$1),o=a$1;};return (async()=>{let a=await E({feature:"sensors",startCommand:Z,stopCommand:ee,endpointPrefix:V,payload:{},parseEvent:He,onEvent:l=>{n||e(l);}});if(n){a?.();return}if(a){t=a;return}i();})(),()=>{n=true,t&&t(),o&&a()&&(window.removeEventListener("deviceorientation",o),o=null);}}};S();var Ct={haptics:Ee,geolocation:Se,sensors:Oe,camera:ce,files:pe};export{ce as camera,Ct as device,pe as files,Se as geolocation,Ee as haptics,C as meetsFeatureMinVersion,Oe as sensors};
1
+ import {a,d as d$1,u,t,v as v$1,c,b,x as x$1,e,s,r,w as w$1}from'../chunk-ALNJCFV4.js';import {fileSave,directoryOpen,fileOpen}from'browser-fs-access';var W=false,O=()=>{!a()||W||(W=true,d$1());};var q="device.haptics",A="device.geolocation",G="device.sensors.motion",j="device.sensors.orientation",z="device.sensors.permission",Q="device.camera",C="device.files",K="device.microphone",J="device.microphone.permission",Y="device.haptics.impact",$="device.haptics.selection",X="device.haptics.notification",Z="device.haptics.vibrate",ee="device.geolocation.get",te="device.geolocation.watch.start",ne="device.geolocation.watch.stop",oe="device.sensors.motion.start",ie="device.sensors.motion.stop",re="device.sensors.orientation.start",ae="device.sensors.orientation.stop",se="device.sensors.requestPermission",le="device.camera.capture",ce="device.files.pick",ue="device.files.save",de="device.microphone.record",me="device.microphone.requestPermission";var pe={haptics:{iOS:null,Android:null},geolocation:{iOS:null,Android:null},sensors:{iOS:null,Android:null},camera:{iOS:null,Android:null},files:{iOS:null,Android:null},microphone:{iOS:null,Android:null}};var w=(e,t$1)=>{if(t$1.type!=="native_app"||!t$1.platform||!t$1.appVersion)return false;let n=pe[e][t$1.platform];return n?u(t(t$1.appVersion),n):false},N=e=>JSON.stringify(e),fe=(e,t)=>{if(typeof e!="object"||e===null||Reflect.get(e,"ok")!==true)return null;let o=Reflect.get(e,"result");return t(o)},ve=async e$1=>e$1.platform==="Android"?x$1(e,{pollIntervalMs:r,timeoutMs:s}):w$1(e),d=async e=>{if(!a())return null;O();let t=v$1();if(!w(e.feature,t))return null;let n=await ve(t);if(!n)return null;let o=c(e.endpointPrefix),i=e.timeoutMs??3e3;return new Promise(r=>{let a=false,s=p=>{a||(a=true,c(),clearTimeout(u),r(p));},c=b.on(o,p=>{let y=fe(p,e.parseResult);s(y);}),u=setTimeout(()=>s(null),i);try{n.postMessage({command:e.command,parameters:N({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{s(null);}})},R=async e=>{if(!a())return null;O();let t=v$1();if(!w(e.feature,t))return null;let n=await ve(t);if(!n)return null;let o=c(e.endpointPrefix),i=b.on(o,r=>{let a=fe(r,e.parseEvent);a&&e.onEvent(a);});try{n.postMessage({command:e.startCommand,parameters:N({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{return i(),null}return ()=>{i();try{n.postMessage({command:e.stopCommand,parameters:N({endpoint:o,timestamp:Date.now(),payload:null})});}catch{}}};var v=async e=>{if(!a())return e.safeDefault;let t=await e.native();if(t!==null)return t;try{let n=await e.web();if(n!==null)return n}catch{}return e.safeDefault};var ge=e=>e==="front"?"user":"environment",Fe={low:.5,medium:.8,high:.95},ke=e=>{let t=e.indexOf(",");if(t<0)return null;let n=e.slice(5,t),o=e.slice(t+1),r=/^([^;]+)/.exec(n)?.[1]??"application/octet-stream";try{if(/;base64/.test(n)){let s=atob(o),c=new Uint8Array(s.length);for(let u=0;u<s.length;u++)c[u]=s.charCodeAt(u);return new Blob([c],{type:r})}return new Blob([decodeURIComponent(o)],{type:r})}catch{return null}},Ue=e=>{if(typeof e!="object"||e===null)return null;let t=Reflect.get(e,"dataUrl"),n=Reflect.get(e,"width"),o=Reflect.get(e,"height");if(typeof t!="string"||t.length===0||typeof n!="number"||typeof o!="number")return null;let i=ke(t);return i?{blob:i,dataUrl:t,width:n,height:o}:null},Be=async e=>{if(!a()||!navigator.mediaDevices?.getUserMedia)return null;let t=null;try{t=await navigator.mediaDevices.getUserMedia({video:{facingMode:ge(e?.camera),width:e?.width,height:e?.height}});let n=document.createElement("video");n.srcObject=t,n.muted=!0,n.playsInline=!0,await n.play(),await new Promise(g=>{requestAnimationFrame(()=>g());});let o=e?.width??n.videoWidth??640,i=e?.height??n.videoHeight??480,r=document.createElement("canvas");r.width=o,r.height=i;let a=r.getContext("2d");if(!a)return null;a.drawImage(n,0,0,o,i);let c=`image/${e?.format??"jpeg"}`,u=Fe[e?.quality??"medium"],p=r.toDataURL(c,u),y=await new Promise(g=>{r.toBlob(E=>g(E),c,u);});return y?{blob:y,dataUrl:p,width:o,height:i}:null}catch{return null}finally{if(t)for(let n of t.getTracks())n.stop();}},Se={isSupported(){return a()?!!navigator.mediaDevices?.getUserMedia:false},capturePhoto(e){return v({native:()=>d({feature:"camera",command:le,endpointPrefix:Q,payload:e??{},parseResult:Ue,timeoutMs:6e4}),web:()=>Be(e),safeDefault:null})},async openStream(e){if(!a()||!navigator.mediaDevices?.getUserMedia)return null;try{return await navigator.mediaDevices.getUserMedia({video:{facingMode:ge(e?.camera),width:e?.width,height:e?.height}})}catch{return null}},stopStream(e){try{for(let t of e.getTracks())t.stop();}catch{}}};var ye=(e,t)=>{let n=atob(e),o=new Uint8Array(n.length);for(let i=0;i<n.length;i++)o[i]=n.charCodeAt(i);return new Blob([o],{type:t})},Ve=e=>{let t=null;if(e.dataUrl){let n=e.dataUrl.indexOf(",");if(n>0){let o=e.dataUrl.slice(5,n),i=e.dataUrl.slice(n+1),r=/^([^;]+)/.exec(o)?.[1]??"application/octet-stream";try{t=/;base64/.test(o)?ye(i,r):new Blob([decodeURIComponent(i)],{type:r});}catch{t=null;}}}else e.base64&&(t=ye(e.base64,e.mime??"application/octet-stream"));return t?new File([t],e.name,{type:e.mime??t.type,lastModified:Date.now()}):null},We=e=>{if(typeof e!="object"||e===null)return null;let t=Reflect.get(e,"files");if(!Array.isArray(t))return null;let n=Reflect.get(e,"paths"),o=r=>{if(!Array.isArray(n))return;let a=n[r];return typeof a=="string"?a:void 0},i=[];for(let r=0;r<t.length;r++){let a=t[r];if(typeof a!="object"||a===null)continue;let s=a,c=Ve(s);if(!c)continue;let u=typeof s.path=="string"?s.path:o(r);i.push(u!==void 0?{file:c,path:u}:{file:c});}return i.length===0?null:{files:i}},qe=e=>{let t=[],n=[];if(!e)return {mimeTypes:t,extensions:n};for(let o of e)for(let i of o.split(",")){let r=i.trim();r&&(r.startsWith(".")?n.push(r):t.push(r));}return {mimeTypes:t,extensions:n}},Oe=e=>e instanceof DOMException&&e.name==="AbortError",Ee=e=>e.length>0?{files:e.map(t=>({file:t}))}:null,Ge=async e=>{if(!a())return null;try{if(e?.directory){let i=await directoryOpen({recursive:!0});return Ee(i)}let{mimeTypes:t,extensions:n}=qe(e?.accept);if(e?.multiple){let i=await fileOpen({mimeTypes:t,extensions:n,multiple:!0});return Ee(i)}return {files:[{file:await fileOpen({mimeTypes:t,extensions:n})}]}}catch(t){return Oe(t),null}},je=async(e,t)=>{if(!a())return "failed";try{return await fileSave(e,{fileName:t}),"saved"}catch(n){return Oe(n)?"cancelled":"failed"}},Re={isPickerSupported(){return a()?typeof window.showOpenFilePicker=="function":false},pickFiles(e){return v({native:()=>d({feature:"files",command:ce,endpointPrefix:C,payload:e??{},parseResult:We,timeoutMs:6e4}),web:()=>Ge(e),safeDefault:null})},async saveFile(e,t){return a()?await d({feature:"files",command:ue,endpointPrefix:C,payload:{filename:t,mime:e.type,size:e.size},parseResult:()=>true,timeoutMs:6e4})?"saved":je(e,t):"failed"},readAsText(e){return typeof e.text=="function"?e.text():new Promise((t,n)=>{let o=new FileReader;o.onload=()=>{t(typeof o.result=="string"?o.result:"");},o.onerror=()=>n(o.error),o.readAsText(e);})},readAsDataUrl(e){return new Promise((t,n)=>{let o=new FileReader;o.onload=()=>{t(typeof o.result=="string"?o.result:"");},o.onerror=()=>n(o.error),o.readAsDataURL(e);})}};var S=(e,...t)=>{for(let n of t){let o=Reflect.get(e,n);if(typeof o=="number"&&!Number.isNaN(o))return o}},he=e=>{if(typeof e!="object"||e===null)return null;let t=S(e,"latitude"),n=S(e,"longitude"),o=S(e,"accuracyMeters","accuracy");if(t===void 0||n===void 0||o===void 0)return null;let i={latitude:t,longitude:n,accuracyMeters:o,timestamp:S(e,"timestamp")??Date.now()},r=S(e,"altitudeMeters","altitude");r!==void 0&&(i.altitudeMeters=r);let a=S(e,"altitudeAccuracyMeters","altitudeAccuracy");a!==void 0&&(i.altitudeAccuracyMeters=a);let s=S(e,"headingDegrees","heading");s!==void 0&&(i.headingDegrees=s);let c=S(e,"speedMetersPerSecond","speed");return c!==void 0&&(i.speedMetersPerSecond=c),i},Me=e=>({latitude:e.coords.latitude,longitude:e.coords.longitude,accuracyMeters:e.coords.accuracy,altitudeMeters:e.coords.altitude??void 0,altitudeAccuracyMeters:e.coords.altitudeAccuracy??void 0,headingDegrees:e.coords.heading??void 0,speedMetersPerSecond:e.coords.speed??void 0,timestamp:e.timestamp}),Te=e=>e?{enableHighAccuracy:e.enableHighAccuracy,timeout:e.timeoutMs,maximumAge:e.maximumAgeMs}:void 0,ze=e=>!a()||!navigator.geolocation?Promise.resolve(null):new Promise(t=>{navigator.geolocation.getCurrentPosition(n=>t(Me(n)),()=>t(null),Te(e));}),_e={isSupported(){return a()?!!navigator.geolocation:false},getCurrentPosition(e){return v({native:()=>d({feature:"geolocation",command:ee,endpointPrefix:A,payload:e??{},parseResult:he,timeoutMs:e?.timeoutMs}),web:()=>ze(e),safeDefault:null})},watchPosition(e,t){let n=false,o=null,i=null;return R({feature:"geolocation",startCommand:te,stopCommand:ne,endpointPrefix:A,payload:t??{},parseEvent:he,onEvent:r=>{n||e(r);}}).then(r=>{if(n){r?.();return}if(r){o=r;return}if(!(!a()||!navigator.geolocation))try{i=navigator.geolocation.watchPosition(a=>{n||e(Me(a));},()=>{},Te(t));}catch{}}),()=>{if(n=true,o&&o(),i!==null&&a()&&navigator.geolocation)try{navigator.geolocation.clearWatch(i);}catch{}}}};var Qe={light:10,medium:20,heavy:40,soft:15,rigid:30},Ke={success:[20,60,20],warning:[40,40,40],error:[50,30,100]},Je=e=>{if(typeof navigator>"u"||typeof navigator.vibrate!="function")return false;try{return navigator.vibrate(e)}catch{return false}},M=(e,t,n)=>v({native:()=>d({feature:"haptics",command:e,endpointPrefix:q,payload:t,parseResult:()=>true}),web:async()=>Je(n),safeDefault:false}),be={isSupported(){return a()?typeof navigator<"u"&&"vibrate"in navigator:false},async impact(e="medium"){await M(Y,{strength:e},Qe[e]);},async selection(){await M($,{},8);},async notification(e){await M(X,{kind:e},Ke[e]);},async vibrate(e){await M(Z,{pattern:e},e);}};var D=()=>{if(!a())return null;let e=window.MediaRecorder;return typeof e=="function"?e:null},Ae=()=>a()&&!!navigator.mediaDevices?.getUserMedia&&D()!==null,Ye=e=>{let t=e.indexOf(",");if(t<0)return null;let n=e.slice(5,t),o=e.slice(t+1),r=/^([^;]+)/.exec(n)?.[1]??"application/octet-stream";try{if(/;base64/.test(n)){let a=atob(o),s=new Uint8Array(a.length);for(let c=0;c<a.length;c++)s[c]=a.charCodeAt(c);return new Blob([s],{type:r})}return new Blob([decodeURIComponent(o)],{type:r})}catch{return null}},$e=e=>{if(typeof e!="object"||e===null)return null;let t=Reflect.get(e,"dataUrl");if(typeof t!="string"||t.length===0)return null;let n=Ye(t);if(!n)return null;let o=Reflect.get(e,"mimeType"),i=Reflect.get(e,"durationMs"),r=typeof o=="string"&&o.length>0?o:n.type||"audio/octet-stream",a=typeof i=="number"&&i>=0?i:0;return {blob:n,mimeType:r,durationMs:a,size:n.size}},Xe=e=>{let t=D();if(!(!e||!t?.isTypeSupported))return t.isTypeSupported(e)?e:void 0},Ie=async e=>{if(!Ae())return null;let t=D();if(!t)return null;let n;try{n=await navigator.mediaDevices.getUserMedia({audio:!0});}catch{return null}let o=()=>{for(let f of n.getTracks())f.stop();},i;try{i=new t(n,{mimeType:Xe(e?.mimeType),audioBitsPerSecond:e?.audioBitsPerSecond});}catch{return o(),null}let r=[],a=Date.now(),s=false,c=false,u=null,p,y=new Promise(f=>{p=f;}),g=()=>{u!==null&&(clearTimeout(u),u=null);};i.ondataavailable=f=>{f.data&&f.data.size>0&&r.push(f.data);},i.onstop=()=>{if(g(),o(),s){p(null);return}let f=i.mimeType||e?.mimeType||"audio/webm",h=new Blob(r,{type:f});p(h.size===0?null:{blob:h,mimeType:h.type||f,durationMs:Date.now()-a,size:h.size});},i.onerror=()=>{g(),o(),p(null);};let E=()=>{if(!c){if(c=true,i.state==="inactive"){g(),o(),p(null);return}try{i.stop();}catch{g(),o(),p(null);}}};try{i.start();}catch{return o(),null}return e?.maxDurationMs&&e.maxDurationMs>0&&(u=setTimeout(E,e.maxDurationMs)),{get active(){return !c&&!s&&i.state==="recording"},stop(){return E(),y},cancel(){c||s||(s=true,E());}}},Ce={isSupported(){return Ae()},async requestPermission(){if(!a())return false;let e=await d({feature:"microphone",command:me,endpointPrefix:J,payload:{},parseResult:t=>{if(typeof t=="boolean")return t;if(typeof t=="object"&&t!==null){let n=Reflect.get(t,"granted");if(typeof n=="boolean")return n}return null}});if(e!==null)return e;if(!a()||!navigator.mediaDevices?.getUserMedia)return false;try{let t=await navigator.mediaDevices.getUserMedia({audio:!0});for(let n of t.getTracks())n.stop();return !0}catch{return false}},async recordAudio(e){return v({native:()=>d({feature:"microphone",command:de,endpointPrefix:K,payload:e??{},parseResult:$e,timeoutMs:6e4}),web:async()=>{let n=await Ie(e);if(!n)return null;let o=e?.maxDurationMs??6e4;return new Promise(i=>{setTimeout(()=>{n.stop().then(i);},o);})},safeDefault:null})},startRecording(e){return a()?Ie(e):Promise.resolve(null)}};var we=()=>typeof DeviceMotionEvent>"u"?false:typeof DeviceMotionEvent.requestPermission=="function",Ne=async()=>{if(!we())return true;try{let e=DeviceMotionEvent.requestPermission;return e?await e()==="granted":!0}catch{return false}},m=(e,...t)=>{for(let n of t){let o=Reflect.get(e,n);if(typeof o=="number"&&!Number.isNaN(o))return o}},x=(e,t)=>{let n=Reflect.get(e,t);if(typeof n!="object"||n===null)return;let o=m(n,"x"),i=m(n,"y"),r=m(n,"z");if(!(o===void 0||i===void 0||r===void 0))return {x:o,y:i,z:r}},Ze=e=>{if(typeof e!="object"||e===null)return null;let t=x(e,"accelerationWithGravity")??x(e,"accelerationIncludingGravity");if(!t)return null;let n=Reflect.get(e,"rotationRate"),o={alpha:0,beta:0,gamma:0};typeof n=="object"&&n!==null&&(o={alpha:m(n,"alpha")??0,beta:m(n,"beta")??0,gamma:m(n,"gamma")??0});let i={accelerationWithGravity:t,rotationRate:o,timestamp:m(e,"timestamp")??Date.now()},r=x(e,"acceleration");r&&(i.acceleration=r);let a=Reflect.get(e,"attitude");if(typeof a=="object"&&a!==null){let s=m(a,"yaw"),c=m(a,"pitch"),u=m(a,"roll");s!==void 0&&c!==void 0&&u!==void 0&&(i.attitude={yaw:s,pitch:c,roll:u});}return i},et=e=>{if(typeof e!="object"||e===null)return null;let t=m(e,"alpha"),n=m(e,"beta"),o=m(e,"gamma");return t===void 0||n===void 0||o===void 0?null:{alpha:t,beta:n,gamma:o,timestamp:m(e,"timestamp")??Date.now()}},tt=e=>{let t=e.accelerationIncludingGravity,n=e.acceleration,o=e.rotationRate,i={accelerationWithGravity:{x:t?.x??0,y:t?.y??0,z:t?.z??0},rotationRate:{alpha:o?.alpha??0,beta:o?.beta??0,gamma:o?.gamma??0},timestamp:e.timeStamp||Date.now()};return n&&(n.x!==null||n.y!==null||n.z!==null)&&(i.acceleration={x:n.x??0,y:n.y??0,z:n.z??0}),i},nt=e=>({alpha:e.alpha??0,beta:e.beta??0,gamma:e.gamma??0,timestamp:e.timeStamp||Date.now()}),De={isMotionSupported(){return a()?typeof DeviceMotionEvent<"u":false},isOrientationSupported(){return a()?typeof DeviceOrientationEvent<"u":false},async requestMotionPermission(){if(!a())return false;let e=await d({feature:"sensors",command:se,endpointPrefix:z,payload:{},parseResult:t=>{if(typeof t=="boolean")return t;if(typeof t=="object"&&t!==null){let n=Reflect.get(t,"granted");if(typeof n=="boolean")return n}return null}});return e!==null?e:Ne()},watchMotion(e,t){let n=false,o=null,i=null,r=()=>{if(n||!a())return;let s=c=>{n||e(tt(c));};window.addEventListener("devicemotion",s),i=s;};return (async()=>{let s=await R({feature:"sensors",startCommand:oe,stopCommand:ie,endpointPrefix:G,payload:t??{},parseEvent:Ze,onEvent:c=>{n||e(c);}});if(n){s?.();return}if(s){o=s;return}t?.autoRequestPermission&&we()&&(!await Ne()||n)||r();})(),()=>{n=true,o&&o(),i&&a()&&(window.removeEventListener("devicemotion",i),i=null);}},watchOrientation(e){let t=false,n=null,o=null,i=()=>{if(t||!a())return;let a$1=s=>{t||e(nt(s));};window.addEventListener("deviceorientation",a$1),o=a$1;};return (async()=>{let a=await R({feature:"sensors",startCommand:re,stopCommand:ae,endpointPrefix:j,payload:{},parseEvent:et,onEvent:s=>{t||e(s);}});if(t){a?.();return}if(a){n=a;return}i();})(),()=>{t=true,n&&n(),o&&a()&&(window.removeEventListener("deviceorientation",o),o=null);}}};O();var Kt={haptics:be,geolocation:_e,sensors:De,camera:Se,files:Re,microphone:Ce};export{Se as camera,Kt as device,Re as files,_e as geolocation,be as haptics,w as meetsFeatureMinVersion,Ce as microphone,De as sensors};
@@ -4,8 +4,11 @@ interface UseAuthTokenResult {
4
4
  token: string;
5
5
  /** 首次 fetch 或主动 refetch 未完成期间为 true。 */
6
6
  loading: boolean;
7
- /** 主动重新拉取;命中 SDK 凭证缓存时不会重复访问宿主。 */
8
- refetch: () => void;
7
+ /**
8
+ * 主动重新拉取;命中 SDK 凭证缓存时不会重复访问宿主。返回的 Promise 在
9
+ * 本次拉取完成后 resolve,便于业务 `await refetch()` 后再做后续动作。
10
+ */
11
+ refetch: () => Promise<void>;
9
12
  }
10
13
  /**
11
14
  * 鉴权 token。挂载时拉一次,业务需要刷新时调用 refetch。
@@ -22,7 +25,8 @@ declare const useAuthToken: () => UseAuthTokenResult;
22
25
  interface UseUserInfoResult {
23
26
  user: UserInfo | null;
24
27
  loading: boolean;
25
- refetch: () => void;
28
+ /** 主动重新拉取。返回的 Promise 在拉取完成后 resolve。 */
29
+ refetch: () => Promise<void>;
26
30
  }
27
31
  /**
28
32
  * 用户信息。同 useAuthToken:挂载拉一次,业务需要刷新时调用 refetch。
@@ -1 +1 @@
1
- import {b,a,c,e}from'../chunk-AKZVV563.js';import'../chunk-ALNJCFV4.js';import {useSyncExternalStore,useState,useCallback,useEffect}from'react';var d="",k=()=>{let u=useSyncExternalStore(b,a,()=>d),[o,e]=useState(()=>a()===""),t=useCallback(()=>{e(true),c().then(()=>e(false));},[]);return useEffect(()=>{if(a()!==""){e(false);return}let s=true;return c().then(()=>{s&&e(false);}),()=>{s=false;}},[]),{token:u,loading:o,refetch:t}};var I=()=>{let[u,o]=useState(null),[e$1,t]=useState(true),s=useCallback(()=>{t(true),e().then(r=>{o(r),t(false);});},[]);return useEffect(()=>{let r=true;return e().then(c=>{r&&(o(c),t(false));}),()=>{r=false;}},[]),{user:u,loading:e$1,refetch:s}};export{k as useAuthToken,I as useUserInfo};
1
+ import {b,a,c,e}from'../chunk-AKZVV563.js';import'../chunk-ALNJCFV4.js';import {useSyncExternalStore,useState,useCallback,useEffect}from'react';var k="",d=()=>{let u=useSyncExternalStore(b,a,()=>k),[s,e]=useState(()=>a()===""),t=useCallback(async()=>{e(true),await c(),e(false);},[]);return useEffect(()=>{if(a()!==""){e(false);return}let o=true;return c().then(()=>{o&&e(false);}),()=>{o=false;}},[]),{token:u,loading:s,refetch:t}};var I=()=>{let[u,s]=useState(null),[e$1,t]=useState(true),o=useCallback(async()=>{t(true);let r=await e();s(r),t(false);},[]);return useEffect(()=>{let r=true;return e().then(c=>{r&&(s(c),t(false));}),()=>{r=false;}},[]),{user:u,loading:e$1,refetch:o}};export{d as useAuthToken,I as useUserInfo};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/feed_app-runtime-sdk",
3
- "version": "0.0.9",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "browser": true,
6
6
  "description": "Runtime SDK for feed-app template: auth / AI capabilities, multi-environment bridge (native App / iframe / web).",