@bty/feed_app-runtime-sdk 0.1.0 → 0.1.2

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
@@ -82,6 +82,20 @@ The AI entry supports:
82
82
  - `AbortSignal` cancellation
83
83
  - Structured AI errors
84
84
 
85
+ When an AI HTTP request receives a non-2xx response, the SDK emits a sanitized
86
+ host notification before throwing the typed `AiError`. In the product page:
87
+
88
+ ```ts
89
+ window.addEventListener('feed-app-runtime-sdk:ai-error', (event) => {
90
+ // event.detail: { status, code, message, path, method, ... }
91
+ })
92
+ ```
93
+
94
+ When the product runs inside a host iframe, the same payload is also sent to
95
+ `window.parent.postMessage({ type: 'feed-app-runtime-sdk:ai-error', detail }, '*')`.
96
+ The payload intentionally omits raw provider bodies and headers; branch on
97
+ `detail.status` / `detail.code` for host-level UI.
98
+
85
99
  > **Media payloads are pass-through.** `images.generate` / `audio.speech.create`
86
100
  > / `video.generations.create` forward the body verbatim to the upstream
87
101
  > provider — there is no client-side reshaping, and each model has its own
@@ -137,9 +151,9 @@ entry do not need to install React.
137
151
 
138
152
  ## Device
139
153
 
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.
154
+ Six host-bridged capabilities, each with a layered fallback: native App bridge
155
+ iframe parent relay → browser Web API → safe default. Import the `device`
156
+ namespace, or the individual capabilities for tree-shaking.
143
157
 
144
158
  ```ts
145
159
  import {
@@ -148,6 +162,7 @@ import {
148
162
  sensors,
149
163
  camera,
150
164
  files,
165
+ microphone,
151
166
  } from '@bty/feed_app-runtime-sdk/device'
152
167
 
153
168
  await haptics.impact('medium')
@@ -156,6 +171,7 @@ const stop = sensors.watchMotion((s) => console.log(s.accelerationWithGravity))
156
171
  const photo = await camera.capturePhoto({ camera: 'back' }) // CapturedPhoto | null
157
172
  const picked = await files.pickFiles({ accept: ['image/*'], multiple: true })
158
173
  const result = await files.saveFile(blob, 'export.json') // "saved" | "cancelled" | "failed"
174
+ const clip = await microphone.recordAudio({ maxDurationMs: 5000 }) // AudioRecording | null
159
175
  stop() // dispose the motion subscription
160
176
  ```
161
177
 
@@ -173,11 +189,19 @@ Capability detection (synchronous, cheap — gate UI ahead of a call):
173
189
  | Camera | `camera.isSupported()` |
174
190
  | Sensors | `sensors.isMotionSupported()` / `sensors.isOrientationSupported()` |
175
191
  | Files | `files.isPickerSupported()` (the *modern* picker; plain pick always works) |
192
+ | Microphone | `microphone.isSupported()` |
176
193
 
177
194
  `requestMotionPermission()`, `capturePhoto()`, `saveFile()`, and `pickFiles()`
178
195
  must be called from inside a user-gesture handler (tap / click) — browsers
179
196
  silently deny these outside a gesture.
180
197
 
198
+ When a feed-app runs inside the host SPA iframe, one-shot device calls reuse the
199
+ same native command frame through `window.parent.postMessage`. The parent shell
200
+ forwards supported commands to the native App and returns the App envelope to
201
+ the child frame. If the parent cannot relay, SDK calls fall through to their web
202
+ fallback. `camera.openStream()` and `microphone.startRecording()` remain
203
+ web-only streaming APIs.
204
+
181
205
  ## Published Files
182
206
 
183
207
  The npm package publishes only built output and this README:
@@ -332,4 +332,20 @@ declare class AbortedError extends AiError {
332
332
  constructor(message?: string);
333
333
  }
334
334
 
335
- export { AbortedError, AiError, type AiErrorCode, type AnthropicContentBlock, type AnthropicMessage, type AnthropicMessageResponse, type AnthropicStreamEvent, type AnthropicTool, type AudioSpeechCreateParams, AuthRequiredError, BadInputError, type ChatCompletion, type ChatCompletionChunk, type ChatCompletionCreateParams, type ChatContentPart, type ChatMessage, type ChatTool, type ChatToolCall, type ImageGenerateParams, type ImageGenerateResponse, type MessagesCreateParams, NetworkError, QuotaExceededError, RateLimitError, type RuntimeConfig, ServerError, type VideoGenerateParams, type VideoGenerateResponse, anthropic, configureRuntime, openai };
335
+ declare const AI_ERROR_EVENT = "feed-app-runtime-sdk:ai-error";
336
+ interface AiErrorEventDetail {
337
+ source: "feed-app-runtime-sdk/ai";
338
+ status: number;
339
+ code: AiErrorCode;
340
+ message: string;
341
+ path: string;
342
+ method: "GET" | "POST";
343
+ sdkVersion: string;
344
+ timestamp: number;
345
+ requestId?: string;
346
+ traceId?: string;
347
+ /** True for the first 401/403 response when the SDK will still refresh + retry. */
348
+ willRetryAuth?: boolean;
349
+ }
350
+
351
+ export { AI_ERROR_EVENT, AbortedError, AiError, type AiErrorCode, type AiErrorEventDetail, type AnthropicContentBlock, type AnthropicMessage, type AnthropicMessageResponse, type AnthropicStreamEvent, type AnthropicTool, type AudioSpeechCreateParams, AuthRequiredError, BadInputError, type ChatCompletion, type ChatCompletionChunk, type ChatCompletionCreateParams, type ChatContentPart, type ChatMessage, type ChatTool, type ChatToolCall, type ImageGenerateParams, type ImageGenerateResponse, type MessagesCreateParams, NetworkError, QuotaExceededError, RateLimitError, type RuntimeConfig, ServerError, type VideoGenerateParams, type VideoGenerateResponse, anthropic, configureRuntime, openai };
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.1.0"};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
- 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};
1
+ import {d as d$1,c}from'../chunk-AKZVV563.js';import {a as a$1}from'../chunk-ALNJCFV4.js';import {createParser}from'eventsource-parser';var D={version:"0.1.2"};var m="/v1/feed-app/runtime/ai",T=`${m}/chat/completions`,_=`${m}/messages`,B=`${m}/images/generations`,M=`${m}/audio/speech`,N=`${m}/video/generations`,H="Authorization",j="x-bty-extend",G="x-bty-app",h=D.version;var q=()=>{};async function*U(e){let t=e.getReader(),r=new TextDecoder,n=[],o=createParser({onEvent(c){n.push({event:c.event||"message",data:c.data});},onRetry:q,onComment:q}),s=false;try{for(;;){let{done:c,value:p}=await t.read();for(p&&o.feed(r.decode(p,{stream:!0}));n.length>0;)yield n.shift();if(c){for(o.reset({consume:!0});n.length>0;)yield n.shift();s=!0;return}}}finally{if(!s)try{await t.cancel();}catch{}t.releaseLock();}}async function*J(e){for await(let t of U(e)){if(t.data==="[DONE]")return;t.data&&(yield JSON.parse(t.data));}}async function*V(e){for await(let t of U(e))t.data&&(yield JSON.parse(t.data));}var Z="https://reactus-api.happyseeds.ai",$=e=>{try{let t=e();if(typeof t=="string"&&t.length>0)return t}catch{}},ee=()=>$(()=>typeof __BTY_RUNTIME_API_BASE_URL__=="string"?__BTY_RUNTIME_API_BASE_URL__:void 0),te=()=>$(()=>typeof __BTY_RUNTIME_PROJECT_ID__=="string"?__BTY_RUNTIME_PROJECT_ID__:void 0),x={apiBaseUrl:(ee()??Z).replace(/\/+$/,""),projectId:te()??""},re=e=>{x={...x,...e,...e.apiBaseUrl?{apiBaseUrl:e.apiBaseUrl.replace(/\/+$/,"")}:{}};},g=()=>x;var a=class extends Error{status;code;body;constructor(t,r,n,o=null){super(t),this.name="AiError",this.code=r,this.status=n,this.body=o;}},i=class extends a{constructor(t="Auth required",r=null){super(t,"auth_required",401,r),this.name="AuthRequiredError";}},y=class extends a{constructor(t="Rate limit exceeded",r=null){super(t,"rate_limit",429,r),this.name="RateLimitError";}},E=class extends a{constructor(t="Quota exceeded",r=null){super(t,"quota_exceeded",402,r),this.name="QuotaExceededError";}},A=class extends a{constructor(t,r=400,n=null){super(t,"bad_input",r,n),this.name="BadInputError";}},R=class extends a{constructor(t,r=500,n=null){super(t,"server",r,n),this.name="ServerError";}},d=class extends a{constructor(t="Network error",r){super(t,"network",-1,r),this.name="NetworkError";}},l=class extends a{constructor(t="Request aborted"){super(t,"aborted",-1,null),this.name="AbortedError";}},L=e=>typeof e=="object"&&e!==null,ne=(e,t)=>{if(typeof e=="string")return e||t;if(!L(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(L(n)&&typeof n.message=="string"&&n.message.length>0)return n.message;let o=e.detail;return typeof o=="string"&&o.length>0?o:t},P=(e,t)=>{let r=ne(t,`HTTP ${e}`);return e===401||e===403?new i(r,t):e===402?new E(r,t):e===429?new y(r,t):e>=400&&e<500?new A(r,e,t):e>=500?new R(r,e,t):new a(r,"unknown",e,t)};var oe=e=>JSON.stringify({"sdk-version":h,...e}),F=async(e={})=>{let t=await c(),r=new Headers;t&&r.set(H,`Bearer ${t}`),r.set(j,oe(e.extend));let{projectId:n}=g();if(n&&r.set(G,n),e.contentType&&r.set("Content-Type",e.contentType),e.accept&&r.set("Accept",e.accept),e.extra)for(let[o,s]of Object.entries(e.extra))r.set(o,s);return r};var S="feed-app-runtime-sdk:ai-error",se=e=>typeof e=="object"&&e!==null,ae=(e,t)=>{if(se(e))for(let r of t){let n=e[r];if(typeof n=="string"&&n.length>0)return n}},Y=e=>{let t=ae(e.body,["request_id","requestId"]);return {source:"feed-app-runtime-sdk/ai",status:e.error.status,code:e.error.code,message:e.error.message,path:e.path,method:e.method,sdkVersion:h,timestamp:Date.now(),...t?{requestId:t}:{},...e.traceId?{traceId:e.traceId}:{},...e.willRetryAuth!==void 0?{willRetryAuth:e.willRetryAuth}:{}}},K=e=>{if(a$1()&&(window.dispatchEvent(new CustomEvent(S,{detail:e})),window.parent!==window))try{window.parent.postMessage({type:S,detail:e},"*");}catch{}};var ie="feed-app-runtime-sdk:auth-required",k=e=>{a$1()&&window.dispatchEvent(new CustomEvent(ie,{detail:e}));};var ce=e=>e instanceof DOMException&&e.name==="AbortError",pe=async e=>{let t=e.headers.get("content-type")??"";try{return t.includes("application/json")?await e.json():await e.text()}catch{return null}},de=e=>typeof e=="object"&&e!==null,ue=(e,t)=>{if(!de(e)||typeof e.success!="boolean")return e;if(e.success)return e.data;let r=typeof e.code=="number"?e.code:t;throw P(r,e)},Q=async(e,t)=>{let{apiBaseUrl:r}=g(),n=`${r}${e}`,o=await F(t),s;try{s=await fetch(n,{method:t.method??"POST",headers:o,body:t.body,signal:t.signal});}catch(b){throw ce(b)?new l:new d("Failed to reach AI backend",b)}if(s.ok)return s;let c=await pe(s),p=P(s.status,c);throw K(Y({error:p,path:e,method:t.method??"POST",body:c,traceId:s.headers.get("x-trace-id")??void 0,willRetryAuth:p instanceof i&&!t.skipAuthRetry&&!t.signal?.aborted})),p},v=async(e,t={})=>{try{return await Q(e,t)}catch(r){if(r instanceof i&&!t.skipAuthRetry&&!t.signal?.aborted){if(!await d$1())throw k({reason:"refresh_failed",error:r}),r;try{return await Q(e,{...t,skipAuthRetry:!0})}catch(o){throw o instanceof i&&k({reason:"retry_rejected",error:o}),o}}throw r}},u=async(e,t,r={})=>{let n=await v(e,{...r,method:"POST",contentType:"application/json",body:JSON.stringify(t)});return ue(await n.json(),n.status)};var w=async(e,t,r={})=>{let n=await v(e,{...r,method:"POST",contentType:"application/json",accept:"text/event-stream",body:JSON.stringify(t)});if(!n.body)throw new d("Streaming response has no body");return n.body},X=async(e,t,r={})=>await(await v(e,{...r,method:"POST",contentType:"application/json",body:JSON.stringify(t)})).blob();var C=e=>{let{signal:t,headers:r,...n}=e;return {transport:{signal:t,extra:r},payload:n}},me=(async e=>{let{transport:t,payload:r}=C(e);if(e.stream){let n=await w(T,r,t);return J(n)}return await u(T,r,t)}),le={create:me},fe={async generate(e){let{transport:t,payload:r}=C(e);return u(B,r,t)}},he={speech:{async create(e){let{transport:t,payload:r}=C(e);return X(M,r,t)}}},ge={generations:{async create(e){let{transport:t,payload:r}=C(e);return u(N,r,t)}}},ye={chat:{completions:le},images:fe,audio:he,video:ge};var Ee=e=>{let{signal:t,headers:r,...n}=e;return {transport:{signal:t,extra:r},payload:n}},Ae=(async e=>{let{transport:t,payload:r}=Ee(e);if(e.stream){let n=await w(_,r,t);return V(n)}return await u(_,r,t)}),Re={messages:{create:Ae}};
2
+ export{S as AI_ERROR_EVENT,l as AbortedError,a as AiError,i as AuthRequiredError,A as BadInputError,d as NetworkError,E as QuotaExceededError,y as RateLimitError,R as ServerError,Re as anthropic,re as configureRuntime,ye as openai};
@@ -80,6 +80,22 @@ interface PickedFiles {
80
80
  files: PickedFile[];
81
81
  }
82
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;
98
+ }
83
99
 
84
100
  declare const camera: {
85
101
  isSupported(): boolean;
@@ -201,6 +217,48 @@ declare const haptics: {
201
217
  vibrate(pattern: number | number[]): Promise<void>;
202
218
  };
203
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
+
204
262
  declare const sensors: {
205
263
  isMotionSupported(): boolean;
206
264
  isOrientationSupported(): boolean;
@@ -228,7 +286,7 @@ declare const sensors: {
228
286
  watchOrientation(onSample: (s: OrientationSample) => void): () => void;
229
287
  };
230
288
 
231
- type DeviceFeature = "haptics" | "geolocation" | "sensors" | "camera" | "files";
289
+ type DeviceFeature = "haptics" | "geolocation" | "sensors" | "camera" | "files" | "microphone";
232
290
 
233
291
  declare const meetsFeatureMinVersion: (feature: DeviceFeature, env: AppEnvironment) => boolean;
234
292
 
@@ -265,6 +323,12 @@ declare const device: {
265
323
  readAsText(file: File | Blob): Promise<string>;
266
324
  readAsDataUrl(file: File | Blob): Promise<string>;
267
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
+ };
268
332
  };
269
333
 
270
- 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,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,t$1)=>{if(t$1.type!=="native_app"||!t$1.platform||!t$1.appVersion)return false;let n=re[e][t$1.platform];return n?u(t(t$1.appVersion),n):false},I=e=>JSON.stringify(e),ae=(e,t)=>{if(typeof e!="object"||e===null||Reflect.get(e,"ok")!==true)return null;let o=Reflect.get(e,"result");return t(o)},se=async e$1=>e$1.platform==="Android"?x(e,{pollIntervalMs:r,timeoutMs:s}):w(e),d=async e=>{if(!a())return null;S();let t=v();if(!C(e.feature,t))return null;let n=await se(t);if(!n)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(u),r(v));},c=b.on(o,v=>{let P=ae(v,e.parseResult);l(P);}),u=setTimeout(()=>l(null),i);try{n.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 t=v();if(!C(e.feature,t))return null;let n=await se(t);if(!n)return null;let o=c(e.endpointPrefix),i=b.on(o,r=>{let a=ae(r,e.parseEvent);a&&e.onEvent(a);});try{n.postMessage({command:e.startCommand,parameters:I({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{return i(),null}return ()=>{i();try{n.postMessage({command:e.stopCommand,parameters:I({endpoint:o,timestamp:Date.now(),payload:null})});}catch{}}};var p=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 le=e=>e==="front"?"user":"environment",be={low:.5,medium:.8,high:.95},Me=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 l=atob(o),c=new Uint8Array(l.length);for(let u=0;u<l.length;u++)c[u]=l.charCodeAt(u);return new Blob([c],{type:r})}return new Blob([decodeURIComponent(o)],{type:r})}catch{return null}},Re=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=Me(t);return i?{blob:i,dataUrl:t,width:n,height:o}:null},Ie=async e=>{if(!a()||!navigator.mediaDevices?.getUserMedia)return null;let t=null;try{t=await navigator.mediaDevices.getUserMedia({video:{facingMode:le(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(y=>{requestAnimationFrame(()=>y());});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=be[e?.quality??"medium"],v=r.toDataURL(c,u),P=await new Promise(y=>{r.toBlob(_e=>y(_e),c,u);});return P?{blob:P,dataUrl:v,width:o,height:i}:null}catch{return null}finally{if(t)for(let n of t.getTracks())n.stop();}},ce={isSupported(){return a()?!!navigator.mediaDevices?.getUserMedia:false},capturePhoto(e){return p({native:()=>d({feature:"camera",command:ne,endpointPrefix:G,payload:e??{},parseResult:Re,timeoutMs:6e4}),web:()=>Ie(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 t of e.getTracks())t.stop();}catch{}}};var me=(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})},Ne=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)?me(i,r):new Blob([decodeURIComponent(i)],{type:r});}catch{t=null;}}}else e.base64&&(t=me(e.base64,e.mime??"application/octet-stream"));return t?new File([t],e.name,{type:e.mime??t.type,lastModified:Date.now()}):null},De=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 l=a,c=Ne(l);if(!c)continue;let u=typeof l.path=="string"?l.path:o(r);i.push(u!==void 0?{file:c,path:u}:{file:c});}return i.length===0?null:{files:i}},we=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}},pe=e=>e instanceof DOMException&&e.name==="AbortError",de=e=>e.length>0?{files:e.map(t=>({file:t}))}:null,xe=async e=>{if(!a())return null;try{if(e?.directory){let i=await directoryOpen({recursive:!0});return de(i)}let{mimeTypes:t,extensions:n}=we(e?.accept);if(e?.multiple){let i=await fileOpen({mimeTypes:t,extensions:n,multiple:!0});return de(i)}return {files:[{file:await fileOpen({mimeTypes:t,extensions:n})}]}}catch(t){return pe(t),null}},Fe=async(e,t)=>{if(!a())return "failed";try{return await fileSave(e,{fileName:t}),"saved"}catch(n){return pe(n)?"cancelled":"failed"}},fe={isPickerSupported(){return a()?typeof window.showOpenFilePicker=="function":false},pickFiles(e){return p({native:()=>d({feature:"files",command:oe,endpointPrefix:R,payload:e??{},parseResult:De,timeoutMs:6e4}),web:()=>xe(e),safeDefault:null})},async saveFile(e,t){return a()?await d({feature:"files",command:ie,endpointPrefix:R,payload:{filename:t,mime:e.type,size:e.size},parseResult:()=>true,timeoutMs:6e4})?"saved":Fe(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 f=(e,...t)=>{for(let n of t){let o=Reflect.get(e,n);if(typeof o=="number"&&!Number.isNaN(o))return o}},ve=e=>{if(typeof e!="object"||e===null)return null;let t=f(e,"latitude"),n=f(e,"longitude"),o=f(e,"accuracyMeters","accuracy");if(t===void 0||n===void 0||o===void 0)return null;let i={latitude:t,longitude:n,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},Se=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}),Ee=e=>e?{enableHighAccuracy:e.enableHighAccuracy,timeout:e.timeoutMs,maximumAge:e.maximumAgeMs}:void 0,ke=e=>!a()||!navigator.geolocation?Promise.resolve(null):new Promise(t=>{navigator.geolocation.getCurrentPosition(n=>t(Se(n)),()=>t(null),Ee(e));}),Pe={isSupported(){return a()?!!navigator.geolocation:false},getCurrentPosition(e){return p({native:()=>d({feature:"geolocation",command:Q,endpointPrefix:M,payload:e??{},parseResult:ve,timeoutMs:e?.timeoutMs}),web:()=>ke(e),safeDefault:null})},watchPosition(e,t){let n=false,o=null,i=null;return E({feature:"geolocation",startCommand:J,stopCommand:Y,endpointPrefix:M,payload:t??{},parseEvent:ve,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(Se(a));},()=>{},Ee(t));}catch{}}),()=>{if(n=true,o&&o(),i!==null&&a()&&navigator.geolocation)try{navigator.geolocation.clearWatch(i);}catch{}}}};var Ue={light:10,medium:20,heavy:40,soft:15,rigid:30},Le={success:[20,60,20],warning:[40,40,40],error:[50,30,100]},Be=e=>{if(typeof navigator>"u"||typeof navigator.vibrate!="function")return false;try{return navigator.vibrate(e)}catch{return false}},O=(e,t,n)=>p({native:()=>d({feature:"haptics",command:e,endpointPrefix:B,payload:t,parseResult:()=>true}),web:async()=>Be(n),safeDefault:false}),ge={isSupported(){return a()?typeof navigator<"u"&&"vibrate"in navigator:false},async impact(e="medium"){await O(q,{strength:e},Ue[e]);},async selection(){await O(j,{},8);},async notification(e){await O(z,{kind:e},Le[e]);},async vibrate(e){await O(K,{pattern:e},e);}};var ye=()=>typeof DeviceMotionEvent>"u"?false:typeof DeviceMotionEvent.requestPermission=="function",Oe=async()=>{if(!ye())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}},A=(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}},He=e=>{if(typeof e!="object"||e===null)return null;let t=A(e,"accelerationWithGravity")??A(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=A(e,"acceleration");r&&(i.acceleration=r);let a=Reflect.get(e,"attitude");if(typeof a=="object"&&a!==null){let l=m(a,"yaw"),c=m(a,"pitch"),u=m(a,"roll");l!==void 0&&c!==void 0&&u!==void 0&&(i.attitude={yaw:l,pitch:c,roll:u});}return i},Ve=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()}},We=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},Ge=e=>({alpha:e.alpha??0,beta:e.beta??0,gamma:e.gamma??0,timestamp:e.timeStamp||Date.now()}),he={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:te,endpointPrefix:W,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:Oe()},watchMotion(e,t){let n=false,o=null,i=null,r=()=>{if(n||!a())return;let l=c=>{n||e(We(c));};window.addEventListener("devicemotion",l),i=l;};return (async()=>{let l=await E({feature:"sensors",startCommand:$,stopCommand:X,endpointPrefix:H,payload:t??{},parseEvent:He,onEvent:c=>{n||e(c);}});if(n){l?.();return}if(l){o=l;return}t?.autoRequestPermission&&ye()&&(!await Oe()||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=l=>{t||e(Ge(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:Ve,onEvent:l=>{t||e(l);}});if(t){a?.();return}if(a){n=a;return}i();})(),()=>{t=true,n&&n(),o&&a()&&(window.removeEventListener("deviceorientation",o),o=null);}}};S();var At={haptics:ge,geolocation:Pe,sensors:he,camera:ce,files:fe};export{ce as camera,At as device,fe as files,Pe as geolocation,ge as haptics,C as meetsFeatureMinVersion,he as sensors};
1
+ import {a,d as d$1,b as b$1,u,t,v,c,x as x$1,e,s,r,w}from'../chunk-ALNJCFV4.js';import {fileSave,directoryOpen,fileOpen}from'browser-fs-access';var G="device.haptics",C="device.geolocation",j="device.sensors.motion",z="device.sensors.orientation",Q="device.sensors.permission",K="device.camera",A="device.files",J="device.microphone",Y="device.microphone.permission",$="device.haptics.impact",X="device.haptics.selection",Z="device.haptics.notification",ee="device.haptics.vibrate",te="device.geolocation.get",ne="device.geolocation.watch.start",oe="device.geolocation.watch.stop",ie="device.sensors.motion.start",re="device.sensors.motion.stop",ae="device.sensors.orientation.start",se="device.sensors.orientation.stop",le="device.sensors.requestPermission",ce="device.camera.capture",ue="device.files.pick",de="device.files.save",me="device.microphone.record",pe="device.microphone.requestPermission",fe="device-request",ge="device-response";var ye={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 Ee=false,Ve=e=>typeof e!="object"||e===null?false:Reflect.get(e,"type")===ge&&typeof Reflect.get(e,"endpoint")=="string",T=()=>{!a()||Ee||(Ee=true,d$1(),window.addEventListener("message",e=>{let t=e.data;Ve(t)&&b$1.emit(t.endpoint,t.data??t);}));};var D=(e,t$1)=>{if(t$1.type!=="native_app"||!t$1.platform||!t$1.appVersion)return false;let n=ye[e][t$1.platform];return n?u(t(t$1.appVersion),n):false},b=e=>JSON.stringify(e),qe=()=>a()&&window.parent!==window,N=(e,t)=>{if(typeof e!="object"||e===null||Reflect.get(e,"ok")!==true)return null;let o=Reflect.get(e,"result");return t(o)},he=async e$1=>e$1.platform==="Android"?x$1(e,{pollIntervalMs:r,timeoutMs:s}):w(e),We=async e=>{let t=v();if(!D(e.feature,t))return null;let n=await he(t);if(!n)return null;let o=c(e.endpointPrefix),i=e.timeoutMs??3e3;return new Promise(r=>{let a=false,s=f=>{a||(a=true,l(),clearTimeout(u),r(f));},l=b$1.on(o,f=>{let h=N(f,e.parseResult);s(h);}),u=setTimeout(()=>s(null),i);try{n.postMessage({command:e.command,parameters:b({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{s(null);}})},Ge=async e=>{if(!qe())return null;let t=c(e.endpointPrefix),n=e.timeoutMs??3e3;return new Promise(o=>{let i=false,r=l=>{i||(i=true,a(),clearTimeout(s),o(l));},a=b$1.on(t,l=>{let u=N(l,e.parseResult);r(u);}),s=setTimeout(()=>r(null),n);try{window.parent.postMessage({type:fe,command:e.command,parameters:b({endpoint:t,timestamp:Date.now(),payload:e.payload})},"*");}catch{r(null);}})},d=async e=>{if(!a())return null;T();let t=await We(e);return t!==null?t:Ge(e)},O=async e=>{if(!a())return null;T();let t=v();if(!D(e.feature,t))return null;let n=await he(t);if(!n)return null;let o=c(e.endpointPrefix),i=b$1.on(o,r=>{let a=N(r,e.parseEvent);a&&e.onEvent(a);});try{n.postMessage({command:e.startCommand,parameters:b({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{return i(),null}return ()=>{i();try{n.postMessage({command:e.stopCommand,parameters:b({endpoint:o,timestamp:Date.now(),payload:null})});}catch{}}};var g=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 Se=e=>e==="front"?"user":"environment",je={low:.5,medium:.8,high:.95},ze=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),l=new Uint8Array(s.length);for(let u=0;u<s.length;u++)l[u]=s.charCodeAt(u);return new Blob([l],{type:r})}return new Blob([decodeURIComponent(o)],{type:r})}catch{return null}},Qe=e=>new Promise(t=>{let n=new FileReader;n.onload=()=>t(typeof n.result=="string"?n.result:null),n.onerror=()=>t(null),n.readAsDataURL(e);}),Ke=e=>new Promise(t=>{let n=new Image;n.onload=()=>{t({width:n.naturalWidth,height:n.naturalHeight}),n.remove();},n.onerror=()=>{t(null),n.remove();},n.src=e;}),Je=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=ze(t);return i?{blob:i,dataUrl:t,width:n,height:o}:null},Ye=async(e,t)=>{if(t?.format!==void 0||t?.quality!==void 0||t?.width!==void 0||t?.height!==void 0)return null;let n=Reflect.get(window,"ImageCapture");if(typeof n!="function")return null;let o=e.getVideoTracks()[0];if(!o)return null;try{let r=await new n(o).takePhoto(),a=await Qe(r);if(!a)return null;let s=await Ke(a);return s?{blob:r,dataUrl:a,width:s.width,height:s.height}:null}catch{return null}},Pe=()=>new Promise(e=>requestAnimationFrame(()=>e())),x=(e,t,n)=>new Promise(o=>{let i=false,r=()=>{i||(i=true,window.clearTimeout(a),e.removeEventListener(t,r),o());},a=window.setTimeout(r,n);e.addEventListener(t,r,{once:true});}),Re=(e,t)=>new Promise(n=>{let o=e.requestVideoFrameCallback;if(!o){requestAnimationFrame(()=>requestAnimationFrame(()=>n()));return}let i=false,r=()=>{i||(i=true,window.clearTimeout(a),n());},a=window.setTimeout(r,t);o.call(e,r);}),$e=async e=>{let t=()=>e.videoWidth>0&&e.videoHeight>0;return t()||await x(e,"loadedmetadata",2e3),e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA&&await x(e,"loadeddata",2e3),e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA&&await x(e,"canplay",2e3),await Re(e,1e3),await Pe(),t()},Xe=e=>{if(e.videoWidth<=0||e.videoHeight<=0)return true;let t=document.createElement("canvas");t.width=24,t.height=24;let n=t.getContext("2d");if(!n)return false;try{n.drawImage(e,0,0,t.width,t.height);let o=n.getImageData(0,0,t.width,t.height).data,i=0,r=0;for(let s=0;s<o.length;s+=4){let l=o[s]*.2126+o[s+1]*.7152+o[s+2]*.0722;i=Math.max(i,l),r+=l;}let a=r/(o.length/4);return i<18&&a<6}catch{return false}},Ze=async(e,t)=>{let n=performance.now();for(;performance.now()-n<t;)if(await Re(e,700),await Pe(),!Xe(e))return},et=async e=>{if(!a()||!navigator.mediaDevices?.getUserMedia)return null;let t=null,n=null;try{t=await navigator.mediaDevices.getUserMedia({video:{facingMode:Se(e?.camera),width:e?.width,height:e?.height}});let o=await Ye(t,e);if(o)return o;if(n=document.createElement("video"),n.srcObject=t,n.autoplay=!0,n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.left="0",n.style.top="0",n.style.width="2px",n.style.height="2px",n.style.opacity="0.01",n.style.pointerEvents="none",n.style.zIndex="-1",(document.body??document.documentElement).appendChild(n),await n.play(),!await $e(n))return null;await Ze(n,3e3);let a=e?.width??n.videoWidth??640,s=e?.height??n.videoHeight??480,l=document.createElement("canvas");l.width=a,l.height=s;let u=l.getContext("2d");if(!u)return null;u.drawImage(n,0,0,a,s);let h=`image/${e?.format??"jpeg"}`,E=je[e?.quality??"medium"],R=l.toDataURL(h,E),p=await new Promise(S=>{l.toBlob(Be=>S(Be),h,E);});return p?{blob:p,dataUrl:R,width:a,height:s}:null}catch{return null}finally{if(n&&(n.pause(),n.srcObject=null,n.remove()),t)for(let o of t.getTracks())o.stop();}},Te={isSupported(){return a()?!!navigator.mediaDevices?.getUserMedia:false},capturePhoto(e){return g({native:()=>d({feature:"camera",command:ce,endpointPrefix:K,payload:e??{},parseResult:Je,timeoutMs:6e4}),web:()=>et(e),safeDefault:null})},async openStream(e){if(!a()||!navigator.mediaDevices?.getUserMedia)return null;try{return await navigator.mediaDevices.getUserMedia({video:{facingMode:Se(e?.camera),width:e?.width,height:e?.height}})}catch{return null}},stopStream(e){try{for(let t of e.getTracks())t.stop();}catch{}}};var Me=(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})},ot=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)?Me(i,r):new Blob([decodeURIComponent(i)],{type:r});}catch{t=null;}}}else e.base64&&(t=Me(e.base64,e.mime??"application/octet-stream"));return t?new File([t],e.name,{type:e.mime??t.type,lastModified:Date.now()}):null},it=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,l=ot(s);if(!l)continue;let u=typeof s.path=="string"?s.path:o(r);i.push(u!==void 0?{file:l,path:u}:{file:l});}return i.length===0?null:{files:i}},rt=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}},_e=e=>e instanceof DOMException&&e.name==="AbortError",be=e=>e.length>0?{files:e.map(t=>({file:t}))}:null,at=async e=>{if(!a())return null;try{if(e?.directory){let i=await directoryOpen({recursive:!0});return be(i)}let{mimeTypes:t,extensions:n}=rt(e?.accept);if(e?.multiple){let i=await fileOpen({mimeTypes:t,extensions:n,multiple:!0});return be(i)}return {files:[{file:await fileOpen({mimeTypes:t,extensions:n})}]}}catch(t){return _e(t),null}},st=async(e,t)=>{if(!a())return "failed";try{return await fileSave(e,{fileName:t}),"saved"}catch(n){return _e(n)?"cancelled":"failed"}},we={isPickerSupported(){return a()?typeof window.showOpenFilePicker=="function":false},pickFiles(e){return g({native:()=>d({feature:"files",command:ue,endpointPrefix:A,payload:e??{},parseResult:it,timeoutMs:6e4}),web:()=>at(e),safeDefault:null})},async saveFile(e,t){return a()?await d({feature:"files",command:de,endpointPrefix:A,payload:{filename:t,mime:e.type,size:e.size},parseResult:()=>true,timeoutMs:6e4})?"saved":st(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 y=(e,...t)=>{for(let n of t){let o=Reflect.get(e,n);if(typeof o=="number"&&!Number.isNaN(o))return o}},Ie=e=>{if(typeof e!="object"||e===null)return null;let t=y(e,"latitude"),n=y(e,"longitude"),o=y(e,"accuracyMeters","accuracy");if(t===void 0||n===void 0||o===void 0)return null;let i={latitude:t,longitude:n,accuracyMeters:o,timestamp:y(e,"timestamp")??Date.now()},r=y(e,"altitudeMeters","altitude");r!==void 0&&(i.altitudeMeters=r);let a=y(e,"altitudeAccuracyMeters","altitudeAccuracy");a!==void 0&&(i.altitudeAccuracyMeters=a);let s=y(e,"headingDegrees","heading");s!==void 0&&(i.headingDegrees=s);let l=y(e,"speedMetersPerSecond","speed");return l!==void 0&&(i.speedMetersPerSecond=l),i},Ce=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}),Ae=e=>e?{enableHighAccuracy:e.enableHighAccuracy,timeout:e.timeoutMs,maximumAge:e.maximumAgeMs}:void 0,lt=e=>!a()||!navigator.geolocation?Promise.resolve(null):new Promise(t=>{navigator.geolocation.getCurrentPosition(n=>t(Ce(n)),()=>t(null),Ae(e));}),De={isSupported(){return a()?!!navigator.geolocation:false},getCurrentPosition(e){return g({native:()=>d({feature:"geolocation",command:te,endpointPrefix:C,payload:e??{},parseResult:Ie,timeoutMs:e?.timeoutMs}),web:()=>lt(e),safeDefault:null})},watchPosition(e,t){let n=false,o=null,i=null;return O({feature:"geolocation",startCommand:ne,stopCommand:oe,endpointPrefix:C,payload:t??{},parseEvent:Ie,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(Ce(a));},()=>{},Ae(t));}catch{}}),()=>{if(n=true,o&&o(),i!==null&&a()&&navigator.geolocation)try{navigator.geolocation.clearWatch(i);}catch{}}}};var ct={light:10,medium:20,heavy:40,soft:15,rigid:30},ut={success:[20,60,20],warning:[40,40,40],error:[50,30,100]},dt=e=>{if(typeof navigator>"u"||typeof navigator.vibrate!="function")return false;try{return navigator.vibrate(e)}catch{return false}},_=(e,t,n)=>g({native:()=>d({feature:"haptics",command:e,endpointPrefix:G,payload:t,parseResult:()=>true}),web:async()=>dt(n),safeDefault:false}),Ne={isSupported(){return a()?typeof navigator<"u"&&"vibrate"in navigator:false},async impact(e="medium"){await _($,{strength:e},ct[e]);},async selection(){await _(X,{},8);},async notification(e){await _(Z,{kind:e},ut[e]);},async vibrate(e){await _(ee,{pattern:e},e);}};var F=()=>{if(!a())return null;let e=window.MediaRecorder;return typeof e=="function"?e:null},Fe=()=>a()&&!!navigator.mediaDevices?.getUserMedia&&F()!==null,mt=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 l=0;l<a.length;l++)s[l]=a.charCodeAt(l);return new Blob([s],{type:r})}return new Blob([decodeURIComponent(o)],{type:r})}catch{return null}},pt=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=mt(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}},ft=e=>{let t=F();if(!(!e||!t?.isTypeSupported))return t.isTypeSupported(e)?e:void 0},xe=async e=>{if(!Fe())return null;let t=F();if(!t)return null;let n;try{n=await navigator.mediaDevices.getUserMedia({audio:!0});}catch{return null}let o=()=>{for(let p of n.getTracks())p.stop();},i;try{i=new t(n,{mimeType:ft(e?.mimeType),audioBitsPerSecond:e?.audioBitsPerSecond});}catch{return o(),null}let r=[],a=Date.now(),s=false,l=false,u=null,f,h=new Promise(p=>{f=p;}),E=()=>{u!==null&&(clearTimeout(u),u=null);};i.ondataavailable=p=>{p.data&&p.data.size>0&&r.push(p.data);},i.onstop=()=>{if(E(),o(),s){f(null);return}let p=i.mimeType||e?.mimeType||"audio/webm",S=new Blob(r,{type:p});f(S.size===0?null:{blob:S,mimeType:S.type||p,durationMs:Date.now()-a,size:S.size});},i.onerror=()=>{E(),o(),f(null);};let R=()=>{if(!l){if(l=true,i.state==="inactive"){E(),o(),f(null);return}try{i.stop();}catch{E(),o(),f(null);}}};try{i.start();}catch{return o(),null}return e?.maxDurationMs&&e.maxDurationMs>0&&(u=setTimeout(R,e.maxDurationMs)),{get active(){return !l&&!s&&i.state==="recording"},stop(){return R(),h},cancel(){l||s||(s=true,R());}}},ke={isSupported(){return Fe()},async requestPermission(){if(!a())return false;let e=await d({feature:"microphone",command:pe,endpointPrefix:Y,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 g({native:()=>d({feature:"microphone",command:me,endpointPrefix:J,payload:e??{},parseResult:pt,timeoutMs:6e4}),web:async()=>{let n=await xe(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()?xe(e):Promise.resolve(null)}};var Le=()=>typeof DeviceMotionEvent>"u"?false:typeof DeviceMotionEvent.requestPermission=="function",Ue=async()=>{if(!Le())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}},k=(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}},gt=e=>{if(typeof e!="object"||e===null)return null;let t=k(e,"accelerationWithGravity")??k(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=k(e,"acceleration");r&&(i.acceleration=r);let a=Reflect.get(e,"attitude");if(typeof a=="object"&&a!==null){let s=m(a,"yaw"),l=m(a,"pitch"),u=m(a,"roll");s!==void 0&&l!==void 0&&u!==void 0&&(i.attitude={yaw:s,pitch:l,roll:u});}return i},yt=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()}},Et=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},vt=e=>({alpha:e.alpha??0,beta:e.beta??0,gamma:e.gamma??0,timestamp:e.timeStamp||Date.now()}),He={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:le,endpointPrefix:Q,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:Ue()},watchMotion(e,t){let n=false,o=null,i=null,r=()=>{if(n||!a())return;let s=l=>{n||e(Et(l));};window.addEventListener("devicemotion",s),i=s;};return (async()=>{let s=await O({feature:"sensors",startCommand:ie,stopCommand:re,endpointPrefix:j,payload:t??{},parseEvent:gt,onEvent:l=>{n||e(l);}});if(n){s?.();return}if(s){o=s;return}t?.autoRequestPermission&&Le()&&(!await Ue()||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(vt(s));};window.addEventListener("deviceorientation",a$1),o=a$1;};return (async()=>{let a=await O({feature:"sensors",startCommand:ae,stopCommand:se,endpointPrefix:z,payload:{},parseEvent:yt,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);}}};T();var mn={haptics:Ne,geolocation:De,sensors:He,camera:Te,files:we,microphone:ke};export{Te as camera,mn as device,we as files,De as geolocation,Ne as haptics,D as meetsFeatureMinVersion,ke as microphone,He as sensors};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/feed_app-runtime-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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).",