@bty/feed_app-runtime-sdk 0.1.4 → 0.1.5

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
@@ -20,15 +20,16 @@ Two failure styles, split by sub-entry — know which you're calling:
20
20
  | Sub-entry | On failure | Why |
21
21
  |---|---|---|
22
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 |
23
+ | `/user` | **returns an empty value** (never throws) | no host user context is a normal runtime state |
24
+ | `/device` | **returns `DeviceResult<T>`** (never throws) | denied / cancelled / unsupported native capability is normal control flow — branch on `result.ok` |
24
25
 
25
26
  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.
27
+ for `getUserInfoAsync`. Device APIs use one envelope instead:
28
+ `{ ok: true, source, value }` or
29
+ `{ ok: false, source, reason, errorCode?, errorMessage? }`.
29
30
 
30
31
  Rule of thumb: wrap `/ai` calls in `try/catch`; branch on the return value for
31
- `/user` and `/device`.
32
+ `/user`; branch on `result.ok` for `/device`.
32
33
 
33
34
  ## User
34
35
 
@@ -152,7 +153,7 @@ entry do not need to install React.
152
153
  ## Device
153
154
 
154
155
  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
+ → iframe parent relay → browser Web API → SDK failure. Import the `device`
156
157
  namespace, or the individual capabilities for tree-shaking.
157
158
 
158
159
  ```ts
@@ -166,26 +167,38 @@ import {
166
167
  } from '@bty/feed_app-runtime-sdk/device'
167
168
 
168
169
  await haptics.impact('medium')
169
- const pos = await geolocation.getCurrentPosition() // PositionSample | null
170
- const stop = sensors.watchMotion((s) => console.log(s.accelerationWithGravity))
171
- const photo = await camera.capturePhoto({ camera: 'back' }) // CapturedPhoto | null
170
+ const pos = await geolocation.getCurrentPosition() // DeviceResult<PositionSample>
171
+ const motion = await sensors.watchMotion((s) => console.log(s.accelerationWithGravity))
172
+ const photo = await camera.capturePhoto({ camera: 'back' }) // DeviceResult<CapturedPhoto>
172
173
  const picked = await files.pickFiles({ accept: ['image/*'], multiple: true })
173
- const result = await files.saveFile(blob, 'export.json') // "saved" | "cancelled" | "failed"
174
- const clip = await microphone.recordAudio({ maxDurationMs: 5000 }) // AudioRecording | null
175
- stop() // dispose the motion subscription
174
+ const saved = await files.saveFile(blob, 'export.json') // DeviceResult<{ path?: string }>
175
+ const clip = await microphone.recordAudio({ maxDurationMs: 5000 }) // DeviceResult<AudioRecording>
176
+
177
+ if (pos.ok) console.log(pos.value.latitude, pos.value.longitude)
178
+ if (motion.ok) motion.value() // dispose the motion subscription
179
+ if (!saved.ok) console.warn(saved.reason, saved.errorCode, saved.errorMessage)
176
180
  ```
177
181
 
178
- Every call returns the safe default (`null`, or `false` for `haptics`) on
179
- denial / cancel / unavailability none of them throw. `pickFiles` resolves to
180
- `{ files: { file, path? }[] }` (path present only on the native bridge),
181
- `saveFile` to a `FileSaveResult`.
182
+ Every device call returns `DeviceResult<T>` and never throws for normal runtime
183
+ states. On success, read `result.value`; on failure, branch on `result.reason`
184
+ (`"cancelled"`, `"permission_denied"`, `"not_supported"`, `"timeout"`,
185
+ `"invalid_response"`, or `"failed"`). `source` is `"native"`, `"iframe"`,
186
+ `"web"`, or `"sdk"` and is useful for diagnostics.
187
+
188
+ `pickFiles` succeeds with `{ files: { file, path? }[] }` (path present only on
189
+ the native bridge). `watchPosition`, `watchMotion`, and `watchOrientation`
190
+ succeed with a cleanup function in `value`. `haptics.*` and `camera.stopStream`
191
+ succeed with `value: undefined`.
182
192
 
183
193
  For native/App saves, `saveFile` sends the Blob bytes through the bridge as
184
194
  base64 with `{ filename, mime, size, base64 }`. The host App must return an
185
195
  `ok: true` envelope with `result.saved === true`; older metadata-only success
186
- responses are ignored. `USER_CANCELLED` maps to `"cancelled"`; other native
187
- failure responses map to `"failed"` instead of trusting browser download
188
- fallbacks, because WebView downloads can report success without writing a file.
196
+ responses are ignored. `USER_CANCELLED` maps to `reason: "cancelled"`; native
197
+ failure responses keep `errorCode` / `errorMessage` on the returned
198
+ `FileSaveResult`. Top-level HostApp WebViews do not trust browser download
199
+ fallbacks after a native save failure, because WebView downloads can report
200
+ success without writing a file. Diagnostics are part of the normal `saveFile`
201
+ Result envelope.
189
202
 
190
203
  Capability detection (synchronous, cheap — gate UI ahead of a call):
191
204
 
package/dist/ai/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import {d as d$1,c}from'../chunk-V73IHZAF.js';import {a as a$1}from'../chunk-H42VJJAC.js';import {createParser}from'eventsource-parser';var D={version:"0.1.4"};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}};
1
+ import {d as d$1,c}from'../chunk-V73IHZAF.js';import {a as a$1}from'../chunk-H42VJJAC.js';import {createParser}from'eventsource-parser';var D={version:"0.1.5"};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
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};
@@ -77,7 +77,10 @@ interface PickedFile {
77
77
  interface PickedFiles {
78
78
  files: PickedFile[];
79
79
  }
80
- type FileSaveResult = "saved" | "cancelled" | "failed";
80
+ interface SavedFile {
81
+ path?: string;
82
+ }
83
+ type FileSaveResult = DeviceResult<SavedFile>;
81
84
  interface AudioRecordingOptions {
82
85
  maxDurationMs?: number;
83
86
  mimeType?: string;
@@ -90,10 +93,23 @@ interface AudioRecording {
90
93
  size: number;
91
94
  }
92
95
  interface AudioRecorder {
93
- stop(): Promise<AudioRecording | null>;
96
+ stop(): Promise<DeviceResult<AudioRecording>>;
94
97
  cancel(): void;
95
98
  readonly active: boolean;
96
99
  }
100
+ type DeviceResultSource = "native" | "iframe" | "web" | "sdk";
101
+ type DeviceErrorReason = "cancelled" | "permission_denied" | "not_supported" | "timeout" | "invalid_response" | "failed";
102
+ type DeviceResult<T> = {
103
+ ok: true;
104
+ source: DeviceResultSource;
105
+ value: T;
106
+ } | {
107
+ ok: false;
108
+ source: DeviceResultSource;
109
+ reason: DeviceErrorReason;
110
+ errorCode?: string;
111
+ errorMessage?: string;
112
+ };
97
113
 
98
114
  declare const camera: {
99
115
  isSupported(): boolean;
@@ -101,13 +117,13 @@ declare const camera: {
101
117
  * Open the camera, capture one photo, close the stream. The native bridge
102
118
  * variant runs the host App's full-screen capture UI (better quality, save
103
119
  * to gallery, etc.); the web fallback does a headless `getUserMedia +
104
- * canvas` shot. Returns null on permission denial, hardware absence, or
105
- * any failure on either path.
120
+ * canvas` shot. Permission denial, hardware absence, or any failure returns
121
+ * a DeviceResult failure.
106
122
  *
107
123
  * Must be called inside a user-gesture handler to satisfy autoplay /
108
124
  * permission requirements on iOS Safari and modern Android browsers.
109
125
  */
110
- capturePhoto(opts?: PhotoCaptureOptions): Promise<CapturedPhoto | null>;
126
+ capturePhoto(opts?: PhotoCaptureOptions): Promise<DeviceResult<CapturedPhoto>>;
111
127
  /**
112
128
  * Open a live MediaStream — for QR scanning, AR overlays, or any case
113
129
  * where a single still photo is not enough. Web-only by design: streaming
@@ -115,11 +131,12 @@ declare const camera: {
115
131
  * need camera access usually push it through capturePhoto's full-screen UI
116
132
  * instead.
117
133
  *
118
- * Returns null when getUserMedia is unavailable or the user denies access.
119
- * Caller must invoke `camera.stopStream(stream)` when done.
134
+ * Returns a DeviceResult failure when getUserMedia is unavailable or the
135
+ * user denies access. Caller must invoke `camera.stopStream(result.value)`
136
+ * when done.
120
137
  */
121
- openStream(opts?: StreamOptions): Promise<MediaStream | null>;
122
- stopStream(stream: MediaStream): void;
138
+ openStream(opts?: StreamOptions): Promise<DeviceResult<MediaStream>>;
139
+ stopStream(stream: MediaStream): DeviceResult<void>;
123
140
  };
124
141
 
125
142
  declare const files: {
@@ -131,24 +148,22 @@ declare const files: {
131
148
  */
132
149
  isPickerSupported(): boolean;
133
150
  /**
134
- * Pick one or more files. Returns null on user cancel or unavailability.
135
- * Each entry is a `{ file, path? }` — `path` is set only when the native
136
- * bridge serves the request; web fallbacks have no path concept and omit
137
- * it. Pairing path with its file avoids the index drift a separate
138
- * `paths[]` array invites.
151
+ * Pick one or more files. User cancel or unavailability returns a
152
+ * DeviceResult failure. On success, each entry is a `{ file, path? }` —
153
+ * `path` is set only when the native bridge serves the request; web
154
+ * fallbacks have no path concept and omit it. Pairing path with its file
155
+ * avoids the index drift a separate `paths[]` array invites.
139
156
  */
140
- pickFiles(opts?: PickFilesOptions): Promise<PickedFiles | null>;
157
+ pickFiles(opts?: PickFilesOptions): Promise<DeviceResult<PickedFiles>>;
141
158
  /**
142
159
  * Save a Blob to a file the user nominates. Native handler can prompt for
143
160
  * a directory and write in place; web fallback uses the File System Access
144
161
  * save dialog when available, otherwise triggers an anchor download.
145
162
  *
146
- * Resolves to a `FileSaveResult` `"saved"` on success, `"cancelled"` when
147
- * the user dismisses the dialog, `"failed"` on any error or unavailable
148
- * environment. Never throws, so a "Export" button can branch on the outcome
149
- * (toast on saved, stay quiet on cancelled, error hint on failed) instead of
150
- * assuming success. There is no progress callback — web Blobs write
151
- * atomically.
163
+ * Resolves to a `DeviceResult<SavedFile>`. Never throws, so an "Export"
164
+ * button can branch on `ok` and inspect `reason` / `errorCode` /
165
+ * `errorMessage` for UI or diagnostics. There is no progress callback
166
+ * web Blobs write atomically.
152
167
  */
153
168
  saveFile(blob: Blob, filename: string): Promise<FileSaveResult>;
154
169
  /**
@@ -156,29 +171,29 @@ declare const files: {
156
171
  * not consult the native bridge. The blob already lives in the page's
157
172
  * memory by the time this is called.
158
173
  */
159
- readAsText(file: File | Blob): Promise<string>;
174
+ readAsText(file: File | Blob): Promise<DeviceResult<string>>;
160
175
  /**
161
176
  * Read the contents of a File / Blob as a base64 data URL — useful for
162
177
  * `<img src>` previews. Pure web.
163
178
  */
164
- readAsDataUrl(file: File | Blob): Promise<string>;
179
+ readAsDataUrl(file: File | Blob): Promise<DeviceResult<string>>;
165
180
  };
166
181
 
167
182
  declare const geolocation: {
168
183
  isSupported(): boolean;
169
184
  /**
170
- * Single position read. Returns null on permission denial, timeout, or
171
- * absence of geolocation hardware. Default timeout is the browser/native
172
- * default; pass `timeoutMs` to override.
185
+ * Single position read. Permission denial, timeout, or absence of
186
+ * geolocation hardware returns a DeviceResult failure. Default timeout is
187
+ * the browser/native default; pass `timeoutMs` to override.
173
188
  */
174
- getCurrentPosition(opts?: PositionRequestOptions): Promise<PositionSample | null>;
189
+ getCurrentPosition(opts?: PositionRequestOptions): Promise<DeviceResult<PositionSample>>;
175
190
  /**
176
- * Continuous position updates. Returns a synchronous cleanup function
177
- * native subscription may be still warming up; cleanup is safe to call
191
+ * Continuous position updates. On success, `value` is a cleanup function.
192
+ * Native subscription may be still warming up; cleanup is safe to call
178
193
  * before that resolves, the subscription will be torn down as soon as it
179
194
  * is installed.
180
195
  */
181
- watchPosition(onSample: (sample: PositionSample) => void, opts?: PositionRequestOptions): () => void;
196
+ watchPosition(onSample: (sample: PositionSample) => void, opts?: PositionRequestOptions): Promise<DeviceResult<() => void>>;
182
197
  };
183
198
 
184
199
  declare const haptics: {
@@ -194,25 +209,25 @@ declare const haptics: {
194
209
  * VibrationEffect.EFFECT_CLICK / EFFECT_HEAVY_CLICK on Android (native
195
210
  * bridge); a short navigator.vibrate pulse on the web. Default `medium`.
196
211
  */
197
- impact(strength?: HapticImpactStrength): Promise<void>;
212
+ impact(strength?: HapticImpactStrength): Promise<DeviceResult<void>>;
198
213
  /**
199
214
  * "Tick" feedback for changing a selected item (segmented control, picker).
200
215
  * Maps to UISelectionFeedbackGenerator on iOS; a very short pulse on web.
201
216
  */
202
- selection(): Promise<void>;
217
+ selection(): Promise<DeviceResult<void>>;
203
218
  /**
204
219
  * Status feedback after a discrete action (form submit, transaction).
205
220
  * Maps to UINotificationFeedbackGenerator on iOS; a small multi-segment
206
221
  * pattern on web.
207
222
  */
208
- notification(kind: HapticNotificationKind): Promise<void>;
223
+ notification(kind: HapticNotificationKind): Promise<DeviceResult<void>>;
209
224
  /**
210
225
  * Low-level Android-style pattern. Number = single pulse in ms; array =
211
226
  * alternating vibrate/pause durations. Native bridge forwards verbatim;
212
227
  * iOS handler may approximate by mapping pattern length/intensity to its
213
228
  * Impact generator.
214
229
  */
215
- vibrate(pattern: number | number[]): Promise<void>;
230
+ vibrate(pattern: number | number[]): Promise<DeviceResult<void>>;
216
231
  };
217
232
 
218
233
  declare const microphone: {
@@ -225,9 +240,10 @@ declare const microphone: {
225
240
  * camera/sensors. The native-bridge variant routes through the App's own
226
241
  * permission prompt and has no gesture requirement.
227
242
  *
228
- * Returns true if recording is permitted, false on denial / no hardware.
243
+ * Returns a successful DeviceResult with `value: true` if recording is
244
+ * permitted. Denial / no hardware returns a DeviceResult failure.
229
245
  */
230
- requestPermission(): Promise<boolean>;
246
+ requestPermission(): Promise<DeviceResult<boolean>>;
231
247
  /**
232
248
  * Record one audio clip and resolve when capture finishes. The native
233
249
  * bridge runs the host App's recording UI (record button, waveform, native
@@ -236,13 +252,13 @@ declare const microphone: {
236
252
  * since the headless path has no UI for the user to tap "done".
237
253
  *
238
254
  * Must be called inside a user-gesture handler to satisfy mic-permission /
239
- * autoplay rules on iOS Safari and modern Android browsers. Returns null on
240
- * permission denial, hardware absence, or any failure on either path.
255
+ * autoplay rules on iOS Safari and modern Android browsers. Permission
256
+ * denial, hardware absence, or any failure returns a DeviceResult failure.
241
257
  *
242
258
  * For press-and-hold or tap-to-stop UIs where the caller decides when to
243
259
  * stop, use `startRecording` instead.
244
260
  */
245
- recordAudio(opts?: AudioRecordingOptions): Promise<AudioRecording | null>;
261
+ recordAudio(opts?: AudioRecordingOptions): Promise<DeviceResult<AudioRecording>>;
246
262
  /**
247
263
  * Begin caller-controlled recording and return a handle to stop / cancel on
248
264
  * your own schedule — for push-to-talk, tap-to-start/tap-to-stop, or any UI
@@ -250,11 +266,11 @@ declare const microphone: {
250
266
  * audio through a JS bridge is impractical, and host Apps that need mic
251
267
  * access route it through `recordAudio`'s full-screen UI instead.
252
268
  *
253
- * Must be called inside a user-gesture handler. Returns null when the mic is
254
- * unavailable or the user denies access. The caller MUST eventually call
255
- * `.stop()` or `.cancel()` to release the microphone.
269
+ * Must be called inside a user-gesture handler. Returns a DeviceResult
270
+ * failure when the mic is unavailable or the user denies access. The caller
271
+ * MUST eventually call `.stop()` or `.cancel()` to release the microphone.
256
272
  */
257
- startRecording(opts?: AudioRecordingOptions): Promise<AudioRecorder | null>;
273
+ startRecording(opts?: AudioRecordingOptions): Promise<DeviceResult<AudioRecorder>>;
258
274
  };
259
275
 
260
276
  declare const sensors: {
@@ -267,7 +283,7 @@ declare const sensors: {
267
283
  * variant has no such gesture requirement because the App's own iOS
268
284
  * permission prompt is independent of the web view's gesture stack.
269
285
  */
270
- requestMotionPermission(): Promise<boolean>;
286
+ requestMotionPermission(): Promise<DeviceResult<boolean>>;
271
287
  /**
272
288
  * Subscribe to device motion samples. Native handler streams sensor-rate
273
289
  * data through the bridge; web fallback uses the `devicemotion` event.
@@ -276,12 +292,12 @@ declare const sensors: {
276
292
  * `autoRequestPermission: true` from inside a click handler if you want
277
293
  * the subscription to also request permission as part of setup.
278
294
  */
279
- watchMotion(onSample: (s: MotionSample) => void, opts?: MotionWatchOptions): () => void;
295
+ watchMotion(onSample: (s: MotionSample) => void, opts?: MotionWatchOptions): Promise<DeviceResult<() => void>>;
280
296
  /**
281
297
  * Subscribe to device orientation samples. Mirrors watchMotion but uses
282
298
  * the `deviceorientation` event on the web fallback path.
283
299
  */
284
- watchOrientation(onSample: (s: OrientationSample) => void): () => void;
300
+ watchOrientation(onSample: (s: OrientationSample) => void): Promise<DeviceResult<() => void>>;
285
301
  };
286
302
 
287
303
  type DeviceFeature = "haptics" | "geolocation" | "sensors" | "camera" | "files" | "microphone";
@@ -289,42 +305,42 @@ type DeviceFeature = "haptics" | "geolocation" | "sensors" | "camera" | "files"
289
305
  declare const device: {
290
306
  readonly haptics: {
291
307
  isSupported(): boolean;
292
- impact(strength?: HapticImpactStrength): Promise<void>;
293
- selection(): Promise<void>;
294
- notification(kind: HapticNotificationKind): Promise<void>;
295
- vibrate(pattern: number | number[]): Promise<void>;
308
+ impact(strength?: HapticImpactStrength): Promise<DeviceResult<void>>;
309
+ selection(): Promise<DeviceResult<void>>;
310
+ notification(kind: HapticNotificationKind): Promise<DeviceResult<void>>;
311
+ vibrate(pattern: number | number[]): Promise<DeviceResult<void>>;
296
312
  };
297
313
  readonly geolocation: {
298
314
  isSupported(): boolean;
299
- getCurrentPosition(opts?: PositionRequestOptions): Promise<PositionSample | null>;
300
- watchPosition(onSample: (sample: PositionSample) => void, opts?: PositionRequestOptions): () => void;
315
+ getCurrentPosition(opts?: PositionRequestOptions): Promise<DeviceResult<PositionSample>>;
316
+ watchPosition(onSample: (sample: PositionSample) => void, opts?: PositionRequestOptions): Promise<DeviceResult<() => void>>;
301
317
  };
302
318
  readonly sensors: {
303
319
  isMotionSupported(): boolean;
304
320
  isOrientationSupported(): boolean;
305
- requestMotionPermission(): Promise<boolean>;
306
- watchMotion(onSample: (s: MotionSample) => void, opts?: MotionWatchOptions): () => void;
307
- watchOrientation(onSample: (s: OrientationSample) => void): () => void;
321
+ requestMotionPermission(): Promise<DeviceResult<boolean>>;
322
+ watchMotion(onSample: (s: MotionSample) => void, opts?: MotionWatchOptions): Promise<DeviceResult<() => void>>;
323
+ watchOrientation(onSample: (s: OrientationSample) => void): Promise<DeviceResult<() => void>>;
308
324
  };
309
325
  readonly camera: {
310
326
  isSupported(): boolean;
311
- capturePhoto(opts?: PhotoCaptureOptions): Promise<CapturedPhoto | null>;
312
- openStream(opts?: StreamOptions): Promise<MediaStream | null>;
313
- stopStream(stream: MediaStream): void;
327
+ capturePhoto(opts?: PhotoCaptureOptions): Promise<DeviceResult<CapturedPhoto>>;
328
+ openStream(opts?: StreamOptions): Promise<DeviceResult<MediaStream>>;
329
+ stopStream(stream: MediaStream): DeviceResult<void>;
314
330
  };
315
331
  readonly files: {
316
332
  isPickerSupported(): boolean;
317
- pickFiles(opts?: PickFilesOptions): Promise<PickedFiles | null>;
333
+ pickFiles(opts?: PickFilesOptions): Promise<DeviceResult<PickedFiles>>;
318
334
  saveFile(blob: Blob, filename: string): Promise<FileSaveResult>;
319
- readAsText(file: File | Blob): Promise<string>;
320
- readAsDataUrl(file: File | Blob): Promise<string>;
335
+ readAsText(file: File | Blob): Promise<DeviceResult<string>>;
336
+ readAsDataUrl(file: File | Blob): Promise<DeviceResult<string>>;
321
337
  };
322
338
  readonly microphone: {
323
339
  isSupported(): boolean;
324
- requestPermission(): Promise<boolean>;
325
- recordAudio(opts?: AudioRecordingOptions): Promise<AudioRecording | null>;
326
- startRecording(opts?: AudioRecordingOptions): Promise<AudioRecorder | null>;
340
+ requestPermission(): Promise<DeviceResult<boolean>>;
341
+ recordAudio(opts?: AudioRecordingOptions): Promise<DeviceResult<AudioRecording>>;
342
+ startRecording(opts?: AudioRecordingOptions): Promise<DeviceResult<AudioRecorder>>;
327
343
  };
328
344
  };
329
345
 
330
- 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, microphone, sensors };
346
+ export { type AudioRecorder, type AudioRecording, type AudioRecordingOptions, type CameraFacing, type CapturedPhoto, type DeviceErrorReason, type DeviceFeature, type DeviceResult, type DeviceResultSource, type FileSaveResult, type HapticImpactStrength, type HapticNotificationKind, type MotionSample, type MotionWatchOptions, type OrientationSample, type PhotoCaptureOptions, type PhotoFormat, type PhotoQuality, type PickFilesOptions, type PickedFile, type PickedFiles, type PositionRequestOptions, type PositionSample, type SavedFile, type StreamOptions, type Vector3, camera, device, files, geolocation, haptics, microphone, sensors };
@@ -1 +1 @@
1
- import {a,d as d$1,b,p as p$1,c,r,e,q as q$1}from'../chunk-H42VJJAC.js';import {fileSave,directoryOpen,fileOpen}from'browser-fs-access';var B="device.haptics",I="device.geolocation",H="device.sensors.motion",V="device.sensors.orientation",q="device.sensors.permission",W="device.camera",A="device.files",j="device.microphone",G="device.microphone.permission",z="device.haptics.impact",Q="device.haptics.selection",K="device.haptics.notification",Y="device.haptics.vibrate",J="device.geolocation.get",X="device.geolocation.watch.start",$="device.geolocation.watch.stop",Z="device.sensors.motion.start",ee="device.sensors.motion.stop",te="device.sensors.orientation.start",ne="device.sensors.orientation.stop",oe="device.sensors.requestPermission",ie="device.camera.capture",re="device.files.pick",ae="device.files.save",se="device.microphone.record",le="device.microphone.requestPermission",ce="device-request",ue="device-response";var de="NOT_SUPPORTED",me="USER_CANCELLED";var pe=false,He=e=>typeof e!="object"||e===null?false:Reflect.get(e,"type")===ue&&typeof Reflect.get(e,"endpoint")=="string",T=()=>{!a()||pe||(pe=true,d$1(),window.addEventListener("message",e=>{let n=e.data;He(n)&&b.emit(n.endpoint,n.data??n);}));};var w=e=>JSON.stringify(e),We=()=>a()&&window.parent!==window,je=(e,n)=>{let t=N(e,n);return t?.ok===true?t.result:null},N=(e,n)=>{if(typeof e!="object"||e===null)return null;let t=Reflect.get(e,"ok");if(t===true){let o=n(Reflect.get(e,"result"));return o===null?{ok:false}:{ok:true,result:o}}if(t===false){let o=Reflect.get(e,"errorCode"),i=Reflect.get(e,"errorMessage");return {ok:false,errorCode:typeof o=="string"?o:void 0,errorMessage:typeof i=="string"?i:void 0}}return null},ge=async e$1=>e$1.platform==="Android"?r(e,{pollIntervalMs:50,timeoutMs:1500}):q$1(e),Ge=async e=>{let n=await ve(e);return n?.ok===true?n.result:null},ve=async e=>{let n=p$1();if(n.type!=="native_app")return null;let t=await ge(n);if(!t)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.on(o,f=>{let h=N(f,e.parseResult);s(h);}),u=setTimeout(()=>s(null),i);try{t.postMessage({command:e.command,parameters:w({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{s(null);}})},ze=async e=>{let n=await ye(e);return n?.ok===true?n.result:null},ye=async e=>{if(!We())return null;let n=c(e.endpointPrefix),t=e.timeoutMs??3e3;return new Promise(o=>{let i=false,r=l=>{i||(i=true,a(),clearTimeout(s),o(l));},a=b.on(n,l=>{let u=N(l,e.parseResult);r(u);}),s=setTimeout(()=>r(null),t);try{window.parent.postMessage({type:ce,command:e.command,parameters:w({endpoint:n,timestamp:Date.now(),payload:e.payload})},"*");}catch{r(null);}})},p=async e=>{if(!a())return null;T();let n=await Ge(e);return n!==null?n:ze(e)},Ee=async e=>{if(!a())return null;T();let n=await ve(e);return n!==null?n:ye(e)},O=async e=>{if(!a())return null;T();let n=p$1();if(n.type!=="native_app")return null;let t=await ge(n);if(!t)return null;let o=c(e.endpointPrefix),i=b.on(o,r=>{let a=je(r,e.parseEvent);a&&e.onEvent(a);});try{t.postMessage({command:e.startCommand,parameters:w({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{return i(),null}return ()=>{i();try{t.postMessage({command:e.stopCommand,parameters:w({endpoint:o,timestamp:Date.now(),payload:null})});}catch{}}};var g=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 he=e=>e==="front"?"user":"environment",Qe={low:.5,medium:.8,high:.95},Ke=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 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}},Ye=e=>new Promise(n=>{let t=new FileReader;t.onload=()=>n(typeof t.result=="string"?t.result:null),t.onerror=()=>n(null),t.readAsDataURL(e);}),Je=e=>new Promise(n=>{let t=new Image;t.onload=()=>{n({width:t.naturalWidth,height:t.naturalHeight}),t.remove();},t.onerror=()=>{n(null),t.remove();},t.src=e;}),Xe=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=Ke(n);return i?{blob:i,dataUrl:n,width:t,height:o}:null},$e=async(e,n)=>{if(n?.format!==void 0||n?.quality!==void 0||n?.width!==void 0||n?.height!==void 0)return null;let t=Reflect.get(window,"ImageCapture");if(typeof t!="function")return null;let o=e.getVideoTracks()[0];if(!o)return null;try{let r=await new t(o).takePhoto(),a=await Ye(r);if(!a)return null;let s=await Je(a);return s?{blob:r,dataUrl:a,width:s.width,height:s.height}:null}catch{return null}},Pe=()=>new Promise(e=>requestAnimationFrame(()=>e())),D=(e,n,t)=>new Promise(o=>{let i=false,r=()=>{i||(i=true,window.clearTimeout(a),e.removeEventListener(n,r),o());},a=window.setTimeout(r,t);e.addEventListener(n,r,{once:true});}),Se=(e,n)=>new Promise(t=>{let o=e.requestVideoFrameCallback;if(!o){requestAnimationFrame(()=>requestAnimationFrame(()=>t()));return}let i=false,r=()=>{i||(i=true,window.clearTimeout(a),t());},a=window.setTimeout(r,n);o.call(e,r);}),Ze=async e=>{let n=()=>e.videoWidth>0&&e.videoHeight>0;return n()||await D(e,"loadedmetadata",2e3),e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA&&await D(e,"loadeddata",2e3),e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA&&await D(e,"canplay",2e3),await Se(e,1e3),await Pe(),n()},et=e=>{if(e.videoWidth<=0||e.videoHeight<=0)return true;let n=document.createElement("canvas");n.width=24,n.height=24;let t=n.getContext("2d");if(!t)return false;try{t.drawImage(e,0,0,n.width,n.height);let o=t.getImageData(0,0,n.width,n.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}},tt=async(e,n)=>{let t=performance.now();for(;performance.now()-t<n;)if(await Se(e,700),await Pe(),!et(e))return},nt=async e=>{if(!a()||!navigator.mediaDevices?.getUserMedia)return null;let n=null,t=null;try{n=await navigator.mediaDevices.getUserMedia({video:{facingMode:he(e?.camera),width:e?.width,height:e?.height}});let o=await $e(n,e);if(o)return o;if(t=document.createElement("video"),t.srcObject=n,t.autoplay=!0,t.muted=!0,t.playsInline=!0,t.style.position="fixed",t.style.left="0",t.style.top="0",t.style.width="2px",t.style.height="2px",t.style.opacity="0.01",t.style.pointerEvents="none",t.style.zIndex="-1",(document.body??document.documentElement).appendChild(t),await t.play(),!await Ze(t))return null;await tt(t,3e3);let a=e?.width??t.videoWidth??640,s=e?.height??t.videoHeight??480,l=document.createElement("canvas");l.width=a,l.height=s;let u=l.getContext("2d");if(!u)return null;u.drawImage(t,0,0,a,s);let h=`image/${e?.format??"jpeg"}`,y=Qe[e?.quality??"medium"],M=l.toDataURL(h,y),m=await new Promise(P=>{l.toBlob(Be=>P(Be),h,y);});return m?{blob:m,dataUrl:M,width:a,height:s}:null}catch{return null}finally{if(t&&(t.pause(),t.srcObject=null,t.remove()),n)for(let o of n.getTracks())o.stop();}},Re={isSupported(){return a()?!!navigator.mediaDevices?.getUserMedia:false},capturePhoto(e){return g({native:()=>p({feature:"camera",command:ie,endpointPrefix:W,payload:e??{},parseResult:Xe,timeoutMs:6e4}),web:()=>nt(e),safeDefault:null})},async openStream(e){if(!a()||!navigator.mediaDevices?.getUserMedia)return null;try{return await navigator.mediaDevices.getUserMedia({video:{facingMode:he(e?.camera),width:e?.width,height:e?.height}})}catch{return null}},stopStream(e){try{for(let n of e.getTracks())n.stop();}catch{}}};var rt=8*1024*1024,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})},at=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},st=e=>{if(typeof e!="object"||e===null)return null;let n=Reflect.get(e,"files");if(!Array.isArray(n))return null;let t=Reflect.get(e,"paths"),o=r=>{if(!Array.isArray(t))return;let a=t[r];return typeof a=="string"?a:void 0},i=[];for(let r=0;r<n.length;r++){let a=n[r];if(typeof a!="object"||a===null)continue;let s=a,l=at(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}},lt=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}},be=e=>e instanceof DOMException&&e.name==="AbortError",Oe=e=>e.length>0?{files:e.map(n=>({file:n}))}:null,ct=async e=>{if(!a())return null;try{if(e?.directory){let i=await directoryOpen({recursive:!0});return Oe(i)}let{mimeTypes:n,extensions:t}=lt(e?.accept);if(e?.multiple){let i=await fileOpen({mimeTypes:n,extensions:t,multiple:!0});return Oe(i)}return {files:[{file:await fileOpen({mimeTypes:n,extensions:t})}]}}catch(n){return be(n),null}},ut=async(e,n)=>{if(!a())return "failed";try{return await fileSave(e,{fileName:n}),"saved"}catch(t){return be(t)?"cancelled":"failed"}},dt=e=>new Promise(n=>{let t=new FileReader;t.onload=()=>{let o=typeof t.result=="string"?t.result:"",i=o.indexOf(",");n(i>=0?o.slice(i+1):null);},t.onerror=()=>n(null),t.readAsDataURL(e);}),mt=e=>e===true?true:typeof e!="object"||e===null?null:Reflect.get(e,"saved")===true?true:null,we={isPickerSupported(){return a()?typeof window.showOpenFilePicker=="function":false},pickFiles(e){return g({native:()=>p({feature:"files",command:re,endpointPrefix:A,payload:e??{},parseResult:st,timeoutMs:6e4}),web:()=>ct(e),safeDefault:null})},async saveFile(e,n){if(!a())return "failed";if(e.size<=rt){let t=await dt(e);if(t!==null){let o=await Ee({feature:"files",command:ae,endpointPrefix:A,payload:{filename:n,mime:e.type||"application/octet-stream",size:e.size,base64:t},parseResult:mt,timeoutMs:6e4});if(o?.ok===true)return "saved";if(o?.ok===false){if(o.errorCode===me)return "cancelled";if(o.errorCode!==de||p$1().type==="native_app")return "failed"}}}return p$1().type==="native_app"?"failed":ut(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 v=(e,...n)=>{for(let t of n){let o=Reflect.get(e,t);if(typeof o=="number"&&!Number.isNaN(o))return o}},_e=e=>{if(typeof e!="object"||e===null)return null;let n=v(e,"latitude"),t=v(e,"longitude"),o=v(e,"accuracyMeters","accuracy");if(n===void 0||t===void 0||o===void 0)return null;let i={latitude:n,longitude:t,accuracyMeters:o,timestamp:v(e,"timestamp")??Date.now()},r=v(e,"altitudeMeters","altitude");r!==void 0&&(i.altitudeMeters=r);let a=v(e,"altitudeAccuracyMeters","altitudeAccuracy");a!==void 0&&(i.altitudeAccuracyMeters=a);let s=v(e,"headingDegrees","heading");s!==void 0&&(i.headingDegrees=s);let l=v(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}),Ie=e=>e?{enableHighAccuracy:e.enableHighAccuracy,timeout:e.timeoutMs,maximumAge:e.maximumAgeMs}:void 0,pt=e=>!a()||!navigator.geolocation?Promise.resolve(null):new Promise(n=>{navigator.geolocation.getCurrentPosition(t=>n(Ce(t)),()=>n(null),Ie(e));}),Ae={isSupported(){return a()?!!navigator.geolocation:false},getCurrentPosition(e){return g({native:()=>p({feature:"geolocation",command:J,endpointPrefix:I,payload:e??{},parseResult:_e,timeoutMs:e?.timeoutMs}),web:()=>pt(e),safeDefault:null})},watchPosition(e,n){let t=false,o=null,i=null;return O({feature:"geolocation",startCommand:X,stopCommand:$,endpointPrefix:I,payload:n??{},parseEvent:_e,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(Ce(a));},()=>{},Ie(n));}catch{}}),()=>{if(t=true,o&&o(),i!==null&&a()&&navigator.geolocation)try{navigator.geolocation.clearWatch(i);}catch{}}}};var ft={light:10,medium:20,heavy:40,soft:15,rigid:30},gt={success:[20,60,20],warning:[40,40,40],error:[50,30,100]},vt=e=>{if(typeof navigator>"u"||typeof navigator.vibrate!="function")return false;try{return navigator.vibrate(e)}catch{return false}},_=(e,n,t)=>g({native:()=>p({feature:"haptics",command:e,endpointPrefix:B,payload:n,parseResult:()=>true}),web:async()=>vt(t),safeDefault:false}),Ne={isSupported(){return a()?typeof navigator<"u"&&"vibrate"in navigator:false},async impact(e="medium"){await _(z,{strength:e},ft[e]);},async selection(){await _(Q,{},8);},async notification(e){await _(K,{kind:e},gt[e]);},async vibrate(e){await _(Y,{pattern:e},e);}};var x=()=>{if(!a())return null;let e=window.MediaRecorder;return typeof e=="function"?e:null},xe=()=>a()&&!!navigator.mediaDevices?.getUserMedia&&x()!==null,yt=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 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}},Et=e=>{if(typeof e!="object"||e===null)return null;let n=Reflect.get(e,"dataUrl");if(typeof n!="string"||n.length===0)return null;let t=yt(n);if(!t)return null;let o=Reflect.get(e,"mimeType"),i=Reflect.get(e,"durationMs"),r=typeof o=="string"&&o.length>0?o:t.type||"audio/octet-stream",a=typeof i=="number"&&i>=0?i:0;return {blob:t,mimeType:r,durationMs:a,size:t.size}},ht=e=>{let n=x();if(!(!e||!n?.isTypeSupported))return n.isTypeSupported(e)?e:void 0},De=async e=>{if(!xe())return null;let n=x();if(!n)return null;let t;try{t=await navigator.mediaDevices.getUserMedia({audio:!0});}catch{return null}let o=()=>{for(let m of t.getTracks())m.stop();},i;try{i=new n(t,{mimeType:ht(e?.mimeType),audioBitsPerSecond:e?.audioBitsPerSecond});}catch{return o(),null}let r=[],a=Date.now(),s=false,l=false,u=null,f,h=new Promise(m=>{f=m;}),y=()=>{u!==null&&(clearTimeout(u),u=null);};i.ondataavailable=m=>{m.data&&m.data.size>0&&r.push(m.data);},i.onstop=()=>{if(y(),o(),s){f(null);return}let m=i.mimeType||e?.mimeType||"audio/webm",P=new Blob(r,{type:m});f(P.size===0?null:{blob:P,mimeType:P.type||m,durationMs:Date.now()-a,size:P.size});},i.onerror=()=>{y(),o(),f(null);};let M=()=>{if(!l){if(l=true,i.state==="inactive"){y(),o(),f(null);return}try{i.stop();}catch{y(),o(),f(null);}}};try{i.start();}catch{return o(),null}return e?.maxDurationMs&&e.maxDurationMs>0&&(u=setTimeout(M,e.maxDurationMs)),{get active(){return !l&&!s&&i.state==="recording"},stop(){return M(),h},cancel(){l||s||(s=true,M());}}},ke={isSupported(){return xe()},async requestPermission(){if(!a())return false;let e=await p({feature:"microphone",command:le,endpointPrefix:G,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}});if(e!==null)return e;if(!a()||!navigator.mediaDevices?.getUserMedia)return false;try{let n=await navigator.mediaDevices.getUserMedia({audio:!0});for(let t of n.getTracks())t.stop();return !0}catch{return false}},async recordAudio(e){return g({native:()=>p({feature:"microphone",command:se,endpointPrefix:j,payload:e??{},parseResult:Et,timeoutMs:6e4}),web:async()=>{let t=await De(e);if(!t)return null;let o=e?.maxDurationMs??6e4;return new Promise(i=>{setTimeout(()=>{t.stop().then(i);},o);})},safeDefault:null})},startRecording(e){return a()?De(e):Promise.resolve(null)}};var Ue=()=>typeof DeviceMotionEvent>"u"?false:typeof DeviceMotionEvent.requestPermission=="function",Fe=async()=>{if(!Ue())return true;try{let e=DeviceMotionEvent.requestPermission;return e?await e()==="granted":!0}catch{return false}},d=(e,...n)=>{for(let t of n){let o=Reflect.get(e,t);if(typeof o=="number"&&!Number.isNaN(o))return o}},k=(e,n)=>{let t=Reflect.get(e,n);if(typeof t!="object"||t===null)return;let o=d(t,"x"),i=d(t,"y"),r=d(t,"z");if(!(o===void 0||i===void 0||r===void 0))return {x:o,y:i,z:r}},Pt=e=>{if(typeof e!="object"||e===null)return null;let n=k(e,"accelerationWithGravity")??k(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:d(t,"alpha")??0,beta:d(t,"beta")??0,gamma:d(t,"gamma")??0});let i={accelerationWithGravity:n,rotationRate:o,timestamp:d(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=d(a,"yaw"),l=d(a,"pitch"),u=d(a,"roll");s!==void 0&&l!==void 0&&u!==void 0&&(i.attitude={yaw:s,pitch:l,roll:u});}return i},St=e=>{if(typeof e!="object"||e===null)return null;let n=d(e,"alpha"),t=d(e,"beta"),o=d(e,"gamma");return n===void 0||t===void 0||o===void 0?null:{alpha:n,beta:t,gamma:o,timestamp:d(e,"timestamp")??Date.now()}},Rt=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},Tt=e=>({alpha:e.alpha??0,beta:e.beta??0,gamma:e.gamma??0,timestamp:e.timeStamp||Date.now()}),Le={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:oe,endpointPrefix:q,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:Fe()},watchMotion(e,n){let t=false,o=null,i=null,r=()=>{if(t||!a())return;let s=l=>{t||e(Rt(l));};window.addEventListener("devicemotion",s),i=s;};return (async()=>{let s=await O({feature:"sensors",startCommand:Z,stopCommand:ee,endpointPrefix:H,payload:n??{},parseEvent:Pt,onEvent:l=>{t||e(l);}});if(t){s?.();return}if(s){o=s;return}n?.autoRequestPermission&&Ue()&&(!await Fe()||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=s=>{n||e(Tt(s));};window.addEventListener("deviceorientation",a$1),o=a$1;};return (async()=>{let a=await O({feature:"sensors",startCommand:te,stopCommand:ne,endpointPrefix:V,payload:{},parseEvent:St,onEvent:s=>{n||e(s);}});if(n){a?.();return}if(a){t=a;return}i();})(),()=>{n=true,t&&t(),o&&a()&&(window.removeEventListener("deviceorientation",o),o=null);}}};T();var yn={haptics:Ne,geolocation:Ae,sensors:Le,camera:Re,files:we,microphone:ke};export{Re as camera,yn as device,we as files,Ae as geolocation,Ne as haptics,ke as microphone,Le as sensors};
1
+ import {a as a$1,d as d$1,b,p,c,r,e,q}from'../chunk-H42VJJAC.js';import {fileSave,directoryOpen,fileOpen}from'browser-fs-access';var j="device.haptics",F="device.geolocation",z="device.sensors.motion",Q="device.sensors.orientation",K="device.sensors.permission",Y="device.camera",x="device.files",$="device.microphone",J="device.microphone.permission",X="device.haptics.impact",Z="device.haptics.selection",ee="device.haptics.notification",te="device.haptics.vibrate",re="device.geolocation.get",oe="device.geolocation.watch.start",ne="device.geolocation.watch.stop",ie="device.sensors.motion.start",se="device.sensors.motion.stop",ae="device.sensors.orientation.start",ce="device.sensors.orientation.stop",le="device.sensors.requestPermission",ue="device.camera.capture",de="device.files.pick",me="device.files.save",pe="device.microphone.record",fe="device.microphone.requestPermission",ve="device-request",Ee="device-response";var ge="PERMISSION_DENIED",T="NOT_SUPPORTED",h="USER_CANCELLED";var Re="TIMEOUT";var Se=false,je=e=>typeof e!="object"||e===null?false:Reflect.get(e,"type")===Ee&&typeof Reflect.get(e,"endpoint")=="string",D=()=>{!a$1()||Se||(Se=true,d$1(),window.addEventListener("message",e=>{let r=e.data;je(r)&&b.emit(r.endpoint,r.data??r);}));};var d=(e,r)=>({ok:true,source:r,value:e}),ze=e=>{switch(e){case h:return "cancelled";case ge:return "permission_denied";case T:return "not_supported";case Re:return "timeout";case "INVALID_RESPONSE":return "invalid_response";default:return "failed"}},a=e=>({ok:false,source:e.source,reason:e.reason??ze(e.errorCode),...e.errorCode?{errorCode:e.errorCode}:{},...e.errorMessage?{errorMessage:e.errorMessage}:{}}),E=e=>{if(e instanceof Error)return e.message;if(typeof e=="string")return e};var A=e=>JSON.stringify(e),Ye=()=>a$1()&&window.parent!==window,$e=(e,r)=>{let t=L(e,r);return t?.ok===true?t.result:null},L=(e,r)=>{if(typeof e!="object"||e===null)return null;let t=Reflect.get(e,"ok");if(t===true){let o=r(Reflect.get(e,"result"));return o===null?{ok:false,errorCode:"INVALID_RESPONSE",errorMessage:"Native bridge returned an invalid response."}:{ok:true,result:o}}if(t===false){let o=Reflect.get(e,"errorCode"),n=Reflect.get(e,"errorMessage");return {ok:false,errorCode:typeof o=="string"?o:void 0,errorMessage:typeof n=="string"?n:void 0}}return null},Me=(e,r)=>e.ok?{...e,source:r}:{...e,source:r},ye=async e$1=>e$1.platform==="Android"?r(e,{pollIntervalMs:50,timeoutMs:1500}):q(e);var Je=async e=>{let r=p();if(r.type!=="native_app")return null;let t=await ye(r);if(!t)return null;let o=c(e.endpointPrefix),n=e.timeoutMs??3e3;return new Promise(i=>{let s=false,c=g=>{s||(s=true,l(),clearTimeout(m),i(g));},l=b.on(o,g=>{let M=L(g,e.parseResult);c(M?Me(M,"native"):null);}),m=setTimeout(()=>c(null),n);try{t.postMessage({command:e.command,parameters:A({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{c(null);}})};var Xe=async e=>{if(!Ye())return null;let r=c(e.endpointPrefix),t=e.timeoutMs??3e3;return new Promise(o=>{let n=false,i=l=>{n||(n=true,s(),clearTimeout(c),o(l));},s=b.on(r,l=>{let m=L(l,e.parseResult);i(m?Me(m,"iframe"):null);}),c=setTimeout(()=>i(null),t);try{window.parent.postMessage({type:ve,command:e.command,parameters:A({endpoint:r,timestamp:Date.now(),payload:e.payload})},"*");}catch{i(null);}})};var U=async e=>{if(!a$1())return null;D();let r=await Je(e);return r!==null?r:Xe(e)},v=async e=>{let r=await U(e);return r?r.ok?d(r.result,r.source):a({source:r.source,errorCode:r.errorCode,errorMessage:r.errorMessage}):null},C=async e=>{if(!a$1())return null;D();let r=p();if(r.type!=="native_app")return null;let t=await ye(r);if(!t)return null;let o=c(e.endpointPrefix),n=b.on(o,i=>{let s=$e(i,e.parseEvent);s&&e.onEvent(s);});try{t.postMessage({command:e.startCommand,parameters:A({endpoint:o,timestamp:Date.now(),payload:e.payload})});}catch{return n(),null}return ()=>{n();try{t.postMessage({command:e.stopCommand,parameters:A({endpoint:o,timestamp:Date.now(),payload:null})});}catch{}}};var R=async e=>{if(!a$1())return e.unavailable??a({source:"sdk",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Device API is not available outside the browser."});let r=await e.native();if(r?.ok)return r;let t=null;try{if(t=await e.web(),t.ok)return t}catch(o){t=a({source:"web",errorCode:"WEB_ERROR",errorMessage:o instanceof Error?o.message:String(o)});}return r??t??e.unavailable??a({source:"sdk",reason:"not_supported",errorCode:"NOT_SUPPORTED"})};var Te=e=>e==="front"?"user":"environment",Ze={low:.5,medium:.8,high:.95},et=e=>{let r=e.indexOf(",");if(r<0)return null;let t=e.slice(5,r),o=e.slice(r+1),i=/^([^;]+)/.exec(t)?.[1]??"application/octet-stream";try{if(/;base64/.test(t)){let c=atob(o),l=new Uint8Array(c.length);for(let m=0;m<c.length;m++)l[m]=c.charCodeAt(m);return new Blob([l],{type:i})}return new Blob([decodeURIComponent(o)],{type:i})}catch{return null}},tt=e=>new Promise(r=>{let t=new FileReader;t.onload=()=>r(typeof t.result=="string"?t.result:null),t.onerror=()=>r(null),t.readAsDataURL(e);}),rt=e=>new Promise(r=>{let t=new Image;t.onload=()=>{r({width:t.naturalWidth,height:t.naturalHeight}),t.remove();},t.onerror=()=>{r(null),t.remove();},t.src=e;}),ot=e=>{if(typeof e!="object"||e===null)return null;let r=Reflect.get(e,"dataUrl"),t=Reflect.get(e,"width"),o=Reflect.get(e,"height");if(typeof r!="string"||r.length===0||typeof t!="number"||typeof o!="number")return null;let n=et(r);return n?{blob:n,dataUrl:r,width:t,height:o}:null},nt=async(e,r)=>{if(r?.format!==void 0||r?.quality!==void 0||r?.width!==void 0||r?.height!==void 0)return null;let t=Reflect.get(window,"ImageCapture");if(typeof t!="function")return null;let o=e.getVideoTracks()[0];if(!o)return null;try{let i=await new t(o).takePhoto(),s=await tt(i);if(!s)return null;let c=await rt(s);return c?{blob:i,dataUrl:s,width:c.width,height:c.height}:null}catch{return null}},he=()=>new Promise(e=>requestAnimationFrame(()=>e())),B=(e,r,t)=>new Promise(o=>{let n=false,i=()=>{n||(n=true,window.clearTimeout(s),e.removeEventListener(r,i),o());},s=window.setTimeout(i,t);e.addEventListener(r,i,{once:true});}),_e=(e,r)=>new Promise(t=>{let o=e.requestVideoFrameCallback;if(!o){requestAnimationFrame(()=>requestAnimationFrame(()=>t()));return}let n=false,i=()=>{n||(n=true,window.clearTimeout(s),t());},s=window.setTimeout(i,r);o.call(e,i);}),it=async e=>{let r=()=>e.videoWidth>0&&e.videoHeight>0;return r()||await B(e,"loadedmetadata",2e3),e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA&&await B(e,"loadeddata",2e3),e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA&&await B(e,"canplay",2e3),await _e(e,1e3),await he(),r()},st=e=>{if(e.videoWidth<=0||e.videoHeight<=0)return true;let r=document.createElement("canvas");r.width=24,r.height=24;let t=r.getContext("2d");if(!t)return false;try{t.drawImage(e,0,0,r.width,r.height);let o=t.getImageData(0,0,r.width,r.height).data,n=0,i=0;for(let c=0;c<o.length;c+=4){let l=o[c]*.2126+o[c+1]*.7152+o[c+2]*.0722;n=Math.max(n,l),i+=l;}let s=i/(o.length/4);return n<18&&s<6}catch{return false}},at=async(e,r)=>{let t=performance.now();for(;performance.now()-t<r;)if(await _e(e,700),await he(),!st(e))return},ct=async e=>{if(!a$1()||!navigator.mediaDevices?.getUserMedia)return null;let r=null,t=null;try{r=await navigator.mediaDevices.getUserMedia({video:{facingMode:Te(e?.camera),width:e?.width,height:e?.height}});let o=await nt(r,e);if(o)return o;if(t=document.createElement("video"),t.srcObject=r,t.autoplay=!0,t.muted=!0,t.playsInline=!0,t.style.position="fixed",t.style.left="0",t.style.top="0",t.style.width="2px",t.style.height="2px",t.style.opacity="0.01",t.style.pointerEvents="none",t.style.zIndex="-1",(document.body??document.documentElement).appendChild(t),await t.play(),!await it(t))return null;await at(t,3e3);let s=e?.width??t.videoWidth??640,c=e?.height??t.videoHeight??480,l=document.createElement("canvas");l.width=s,l.height=c;let m=l.getContext("2d");if(!m)return null;m.drawImage(t,0,0,s,c);let M=`image/${e?.format??"jpeg"}`,y=Ze[e?.quality??"medium"],w=l.toDataURL(M,y),p=await new Promise(O=>{l.toBlob(Ge=>O(Ge),M,y);});return p?{blob:p,dataUrl:w,width:s,height:c}:null}catch{return null}finally{if(t&&(t.pause(),t.srcObject=null,t.remove()),r)for(let o of r.getTracks())o.stop();}},lt=async e=>{let r=await ct(e);return r?d(r,"web"):a({source:"web",errorCode:"WEB_CAMERA_FAILED",errorMessage:"Camera capture failed or was denied."})},Oe={isSupported(){return a$1()?!!navigator.mediaDevices?.getUserMedia:false},capturePhoto(e){return R({native:()=>v({feature:"camera",command:ue,endpointPrefix:Y,payload:e??{},parseResult:ot,timeoutMs:6e4}),web:()=>lt(e)})},async openStream(e){if(!a$1()||!navigator.mediaDevices?.getUserMedia)return a({source:"web",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"getUserMedia is not available."});try{return d(await navigator.mediaDevices.getUserMedia({video:{facingMode:Te(e?.camera),width:e?.width,height:e?.height}}),"web")}catch(r){return a({source:"web",errorCode:"WEB_CAMERA_STREAM_FAILED",errorMessage:E(r)})}},stopStream(e){try{for(let r of e.getTracks())r.stop();return d(void 0,"web")}catch(r){return a({source:"web",errorCode:"WEB_CAMERA_STOP_FAILED",errorMessage:E(r)})}}};var we=8*1024*1024,De=(e,r)=>{let t=atob(e),o=new Uint8Array(t.length);for(let n=0;n<t.length;n++)o[n]=t.charCodeAt(n);return new Blob([o],{type:r})},mt=e=>{let r=null;if(e.dataUrl){let t=e.dataUrl.indexOf(",");if(t>0){let o=e.dataUrl.slice(5,t),n=e.dataUrl.slice(t+1),i=/^([^;]+)/.exec(o)?.[1]??"application/octet-stream";try{r=/;base64/.test(o)?De(n,i):new Blob([decodeURIComponent(n)],{type:i});}catch{r=null;}}}else typeof e.base64=="string"&&(r=De(e.base64,e.mime??"application/octet-stream"));return r?new File([r],e.name,{type:e.mime??r.type,lastModified:Date.now()}):null},pt=e=>{if(typeof e!="object"||e===null)return null;let r=Reflect.get(e,"files");if(!Array.isArray(r))return null;let t=Reflect.get(e,"paths"),o=i=>{if(!Array.isArray(t))return;let s=t[i];return typeof s=="string"?s:void 0},n=[];for(let i=0;i<r.length;i++){let s=r[i];if(typeof s!="object"||s===null)continue;let c=s,l=mt(c);if(!l)continue;let m=typeof c.path=="string"?c.path:o(i);n.push(m!==void 0?{file:l,path:m}:{file:l});}return n.length===0?null:{files:n}},ft=e=>{let r=[],t=[];if(!e)return {mimeTypes:r,extensions:t};for(let o of e)for(let n of o.split(",")){let i=n.trim();i&&(i.startsWith(".")?t.push(i):r.push(i));}return {mimeTypes:r,extensions:t}},Ie=e=>e instanceof DOMException&&e.name==="AbortError",Ce=e=>e.length>0?{files:e.map(r=>({file:r}))}:null,vt=async e=>{if(!a$1())return a({source:"sdk",reason:"not_supported",errorCode:T,errorMessage:"File picker is not available outside the browser."});try{if(e?.directory){let n=await directoryOpen({recursive:!0}),i=Ce(n);return i?d(i,"web"):a({source:"web",reason:"cancelled",errorCode:h,errorMessage:"File picking cancelled."})}let{mimeTypes:r,extensions:t}=ft(e?.accept);if(e?.multiple){let n=await fileOpen({mimeTypes:r,extensions:t,multiple:!0}),i=Ce(n);return i?d(i,"web"):a({source:"web",reason:"cancelled",errorCode:h,errorMessage:"File picking cancelled."})}let o=await fileOpen({mimeTypes:r,extensions:t});return d({files:[{file:o}]},"web")}catch(r){return Ie(r)?a({source:"web",reason:"cancelled",errorCode:h,errorMessage:"File picking cancelled."}):a({source:"web",errorCode:"WEB_PICK_FAILED",errorMessage:E(r)})}},Et=async(e,r)=>{if(!a$1())return a({source:"sdk",reason:"not_supported",errorCode:T,errorMessage:"File save is not available outside the browser."});try{return await fileSave(e,{fileName:r}),d({},"web")}catch(t){return Ie(t)?a({source:"web",reason:"cancelled",errorCode:h,errorMessage:"File save cancelled."}):a({source:"web",errorCode:"WEB_SAVE_FAILED",errorMessage:E(t)})}},gt=e=>new Promise(r=>{let t=new FileReader;t.onload=()=>{let o=typeof t.result=="string"?t.result:"",n=o.indexOf(",");r(n>=0?o.slice(n+1):null);},t.onerror=()=>r(null),t.readAsDataURL(e);}),Rt=e=>{if(e===true)return {saved:true};if(typeof e!="object"||e===null||Reflect.get(e,"saved")!==true)return null;let r=Reflect.get(e,"path");return {saved:true,...typeof r=="string"&&r?{path:r}:{}}},Ae={isPickerSupported(){return a$1()?typeof window.showOpenFilePicker=="function":false},pickFiles(e){return R({native:()=>v({feature:"files",command:de,endpointPrefix:x,payload:e??{},parseResult:pt,timeoutMs:6e4}),web:()=>vt(e)})},async saveFile(e,r){if(!a$1())return a({source:"sdk",reason:"not_supported",errorCode:T,errorMessage:"File save is not available outside the browser."});if(e.size<=we){let t=await gt(e);if(t!==null){let o=await U({feature:"files",command:me,endpointPrefix:x,payload:{filename:r,mime:e.type||"application/octet-stream",size:e.size,base64:t},parseResult:Rt,timeoutMs:6e4});if(o?.ok===true)return d(o.result.path?{path:o.result.path}:{},o.source);if(o?.ok===false&&(o.errorCode!==T||p().type==="native_app"))return a({source:o.source,errorCode:o.errorCode??"NATIVE_SAVE_FAILED",errorMessage:o.errorMessage})}else if(p().type==="native_app")return a({source:"sdk",errorCode:"FILE_READ_FAILED",errorMessage:"Failed to read file content for native save."})}else if(p().type==="native_app")return a({source:"sdk",errorCode:"FILE_TOO_LARGE",errorMessage:`File exceeds native bridge limit (${we} bytes).`});return p().type==="native_app"?a({source:"sdk",errorCode:"NATIVE_SAVE_UNAVAILABLE",errorMessage:"Native file save did not return a success envelope."}):Et(e,r)},async readAsText(e){try{return typeof e.text=="function"?d(await e.text(),"web"):await new Promise(r=>{let t=new FileReader;t.onload=()=>{r(d(typeof t.result=="string"?t.result:"","web"));},t.onerror=()=>{r(a({source:"web",errorCode:"READ_FAILED",errorMessage:E(t.error)}));},t.readAsText(e);})}catch(r){return a({source:"web",errorCode:"READ_FAILED",errorMessage:E(r)})}},readAsDataUrl(e){return new Promise(r=>{let t=new FileReader;t.onload=()=>{r(d(typeof t.result=="string"?t.result:"","web"));},t.onerror=()=>{r(a({source:"web",errorCode:"READ_FAILED",errorMessage:E(t.error)}));};try{t.readAsDataURL(e);}catch(o){r(a({source:"web",errorCode:"READ_FAILED",errorMessage:E(o)}));}})}};var P=(e,...r)=>{for(let t of r){let o=Reflect.get(e,t);if(typeof o=="number"&&!Number.isNaN(o))return o}},Ne=e=>{if(typeof e!="object"||e===null)return null;let r=P(e,"latitude"),t=P(e,"longitude"),o=P(e,"accuracyMeters","accuracy");if(r===void 0||t===void 0||o===void 0)return null;let n={latitude:r,longitude:t,accuracyMeters:o,timestamp:P(e,"timestamp")??Date.now()},i=P(e,"altitudeMeters","altitude");i!==void 0&&(n.altitudeMeters=i);let s=P(e,"altitudeAccuracyMeters","altitudeAccuracy");s!==void 0&&(n.altitudeAccuracyMeters=s);let c=P(e,"headingDegrees","heading");c!==void 0&&(n.headingDegrees=c);let l=P(e,"speedMetersPerSecond","speed");return l!==void 0&&(n.speedMetersPerSecond=l),n},ke=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}),Fe=e=>e?{enableHighAccuracy:e.enableHighAccuracy,timeout:e.timeoutMs,maximumAge:e.maximumAgeMs}:void 0,St=e=>!a$1()||!navigator.geolocation?Promise.resolve(a({source:"web",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Geolocation API is not available."})):new Promise(r=>{navigator.geolocation.getCurrentPosition(t=>r(d(ke(t),"web")),t=>r(a({source:"web",reason:t.code===t.PERMISSION_DENIED?"permission_denied":t.code===t.TIMEOUT?"timeout":"failed",errorCode:t.code===t.PERMISSION_DENIED?"PERMISSION_DENIED":t.code===t.TIMEOUT?"TIMEOUT":"POSITION_UNAVAILABLE",errorMessage:t.message})),Fe(e));}),xe={isSupported(){return a$1()?!!navigator.geolocation:false},getCurrentPosition(e){return R({native:()=>v({feature:"geolocation",command:re,endpointPrefix:F,payload:e??{},parseResult:Ne,timeoutMs:e?.timeoutMs}),web:()=>St(e)})},async watchPosition(e,r){let t=false,o=null,n=await C({feature:"geolocation",startCommand:oe,stopCommand:ne,endpointPrefix:F,payload:r??{},parseEvent:Ne,onEvent:i=>{t||e(i);}});if(n)return d(()=>{t=true,n();},"native");if(!a$1()||!navigator.geolocation)return a({source:"web",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Geolocation API is not available."});try{o=navigator.geolocation.watchPosition(i=>{t||e(ke(i));},()=>{},Fe(r));}catch(i){return a({source:"web",errorCode:"WEB_GEOLOCATION_WATCH_FAILED",errorMessage:i instanceof Error?i.message:String(i)})}return d(()=>{if(t=true,o!==null&&a$1()&&navigator.geolocation)try{navigator.geolocation.clearWatch(o);}catch{}},"web")}};var Pt={light:10,medium:20,heavy:40,soft:15,rigid:30},Mt={success:[20,60,20],warning:[40,40,40],error:[50,30,100]},yt=e=>{if(typeof navigator>"u"||typeof navigator.vibrate!="function")return false;try{return navigator.vibrate(e)}catch{return false}},N=(e,r,t)=>R({native:()=>v({feature:"haptics",command:e,endpointPrefix:j,payload:r,parseResult:()=>{}}),web:async()=>yt(t)?d(void 0,"web"):a({source:"web",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Vibration API is not available."})}),Le={isSupported(){return a$1()?typeof navigator<"u"&&"vibrate"in navigator:false},impact(e="medium"){return N(X,{strength:e},Pt[e])},selection(){return N(Z,{},8)},notification(e){return N(ee,{kind:e},Mt[e])},vibrate(e){return N(te,{pattern:e},e)}};var H=()=>{if(!a$1())return null;let e=window.MediaRecorder;return typeof e=="function"?e:null},Be=()=>a$1()&&!!navigator.mediaDevices?.getUserMedia&&H()!==null,Tt=e=>{let r=e.indexOf(",");if(r<0)return null;let t=e.slice(5,r),o=e.slice(r+1),i=/^([^;]+)/.exec(t)?.[1]??"application/octet-stream";try{if(/;base64/.test(t)){let s=atob(o),c=new Uint8Array(s.length);for(let l=0;l<s.length;l++)c[l]=s.charCodeAt(l);return new Blob([c],{type:i})}return new Blob([decodeURIComponent(o)],{type:i})}catch{return null}},ht=e=>{if(typeof e!="object"||e===null)return null;let r=Reflect.get(e,"dataUrl");if(typeof r!="string"||r.length===0)return null;let t=Tt(r);if(!t)return null;let o=Reflect.get(e,"mimeType"),n=Reflect.get(e,"durationMs"),i=typeof o=="string"&&o.length>0?o:t.type||"audio/octet-stream",s=typeof n=="number"&&n>=0?n:0;return {blob:t,mimeType:i,durationMs:s,size:t.size}},_t=e=>{let r=H();if(!(!e||!r?.isTypeSupported))return r.isTypeSupported(e)?e:void 0},Ue=async e=>{if(!Be())return null;let r=H();if(!r)return null;let t;try{t=await navigator.mediaDevices.getUserMedia({audio:!0});}catch{return null}let o=()=>{for(let p of t.getTracks())p.stop();},n;try{n=new r(t,{mimeType:_t(e?.mimeType),audioBitsPerSecond:e?.audioBitsPerSecond});}catch{return o(),null}let i=[],s=Date.now(),c=false,l=false,m=null,g,M=new Promise(p=>{g=p;}),y=()=>{m!==null&&(clearTimeout(m),m=null);};n.ondataavailable=p=>{p.data&&p.data.size>0&&i.push(p.data);},n.onstop=()=>{if(y(),o(),c){g(null);return}let p=n.mimeType||e?.mimeType||"audio/webm",O=new Blob(i,{type:p});g(O.size===0?null:{blob:O,mimeType:O.type||p,durationMs:Date.now()-s,size:O.size});},n.onerror=()=>{y(),o(),g(null);};let w=()=>{if(!l){if(l=true,n.state==="inactive"){y(),o(),g(null);return}try{n.stop();}catch{y(),o(),g(null);}}};try{n.start();}catch{return o(),null}return e?.maxDurationMs&&e.maxDurationMs>0&&(m=setTimeout(w,e.maxDurationMs)),{get active(){return !l&&!c&&n.state==="recording"},async stop(){w();let p=await M;return p?d(p,"web"):a({source:"web",errorCode:"WEB_MICROPHONE_FAILED",errorMessage:"Microphone recording produced no audio."})},cancel(){l||c||(c=true,w());}}},He={isSupported(){return Be()},async requestPermission(){if(!a$1())return a({source:"sdk",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Microphone is not available outside the browser."});let e=await v({feature:"microphone",command:fe,endpointPrefix:J,payload:{},parseResult:r=>{if(typeof r=="boolean")return r;if(typeof r=="object"&&r!==null){let t=Reflect.get(r,"granted");if(typeof t=="boolean")return t}return null}});if(e)return e.ok?e.value?e:a({source:e.source,reason:"permission_denied",errorCode:"PERMISSION_DENIED",errorMessage:"Microphone permission denied."}):e;if(!a$1()||!navigator.mediaDevices?.getUserMedia)return a({source:"web",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"getUserMedia is not available."});try{let r=await navigator.mediaDevices.getUserMedia({audio:!0});for(let t of r.getTracks())t.stop();return d(!0,"web")}catch(r){return a({source:"web",reason:"permission_denied",errorCode:"PERMISSION_DENIED",errorMessage:E(r)})}},async recordAudio(e){return R({native:()=>v({feature:"microphone",command:pe,endpointPrefix:$,payload:e??{},parseResult:ht,timeoutMs:6e4}),web:async()=>{let t=await Ue(e);if(!t)return a({source:"web",errorCode:"WEB_MICROPHONE_FAILED",errorMessage:"Microphone recording could not start."});let o=e?.maxDurationMs??6e4;return new Promise(n=>{setTimeout(()=>{t.stop().then(n);},o);})}})},async startRecording(e){if(!a$1())return a({source:"sdk",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Microphone is not available outside the browser."});let r=await Ue(e);return r?d(r,"web"):a({source:"web",errorCode:"WEB_MICROPHONE_FAILED",errorMessage:"Microphone recording could not start."})}};var We=()=>typeof DeviceMotionEvent>"u"?false:typeof DeviceMotionEvent.requestPermission=="function",Ve=async()=>{if(!We())return true;try{let e=DeviceMotionEvent.requestPermission;return e?await e()==="granted":!0}catch{return false}},f=(e,...r)=>{for(let t of r){let o=Reflect.get(e,t);if(typeof o=="number"&&!Number.isNaN(o))return o}},V=(e,r)=>{let t=Reflect.get(e,r);if(typeof t!="object"||t===null)return;let o=f(t,"x"),n=f(t,"y"),i=f(t,"z");if(!(o===void 0||n===void 0||i===void 0))return {x:o,y:n,z:i}},Ot=e=>{if(typeof e!="object"||e===null)return null;let r=V(e,"accelerationWithGravity")??V(e,"accelerationIncludingGravity");if(!r)return null;let t=Reflect.get(e,"rotationRate"),o={alpha:0,beta:0,gamma:0};typeof t=="object"&&t!==null&&(o={alpha:f(t,"alpha")??0,beta:f(t,"beta")??0,gamma:f(t,"gamma")??0});let n={accelerationWithGravity:r,rotationRate:o,timestamp:f(e,"timestamp")??Date.now()},i=V(e,"acceleration");i&&(n.acceleration=i);let s=Reflect.get(e,"attitude");if(typeof s=="object"&&s!==null){let c=f(s,"yaw"),l=f(s,"pitch"),m=f(s,"roll");c!==void 0&&l!==void 0&&m!==void 0&&(n.attitude={yaw:c,pitch:l,roll:m});}return n},bt=e=>{if(typeof e!="object"||e===null)return null;let r=f(e,"alpha"),t=f(e,"beta"),o=f(e,"gamma");return r===void 0||t===void 0||o===void 0?null:{alpha:r,beta:t,gamma:o,timestamp:f(e,"timestamp")??Date.now()}},wt=e=>{let r=e.accelerationIncludingGravity,t=e.acceleration,o=e.rotationRate,n={accelerationWithGravity:{x:r?.x??0,y:r?.y??0,z:r?.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)&&(n.acceleration={x:t.x??0,y:t.y??0,z:t.z??0}),n},Dt=e=>({alpha:e.alpha??0,beta:e.beta??0,gamma:e.gamma??0,timestamp:e.timeStamp||Date.now()}),qe={isMotionSupported(){return a$1()?typeof DeviceMotionEvent<"u":false},isOrientationSupported(){return a$1()?typeof DeviceOrientationEvent<"u":false},async requestMotionPermission(){if(!a$1())return a({source:"sdk",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Motion sensors are not available outside the browser."});let e=await v({feature:"sensors",command:le,endpointPrefix:K,payload:{},parseResult:t=>{if(typeof t=="boolean")return t;if(typeof t=="object"&&t!==null){let o=Reflect.get(t,"granted");if(typeof o=="boolean")return o}return null}});return e?e.ok?e.value?e:a({source:e.source,reason:"permission_denied",errorCode:"PERMISSION_DENIED",errorMessage:"Motion permission denied."}):e:await Ve()?d(true,"web"):a({source:"web",reason:"permission_denied",errorCode:"PERMISSION_DENIED",errorMessage:"Motion permission denied."})},async watchMotion(e,r){let t=false,o=null,n=()=>{if(t||!a$1()||typeof DeviceMotionEvent>"u")return a({source:"web",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Motion sensors are not available."});let s=c=>{t||e(wt(c));};return window.addEventListener("devicemotion",s),o=s,d(()=>{t=true,o&&a$1()&&(window.removeEventListener("devicemotion",o),o=null);},"web")},i=await C({feature:"sensors",startCommand:ie,stopCommand:se,endpointPrefix:z,payload:r??{},parseEvent:Ot,onEvent:s=>{t||e(s);}});return i?d(()=>{t=true,i();},"native"):r?.autoRequestPermission&&We()&&(!await Ve()||t)?a({source:"web",reason:"permission_denied",errorCode:"PERMISSION_DENIED",errorMessage:"Motion permission denied."}):n()},async watchOrientation(e){let r=false,t=null,o=()=>{if(r||!a$1()||typeof DeviceOrientationEvent>"u")return a({source:"web",reason:"not_supported",errorCode:"NOT_SUPPORTED",errorMessage:"Orientation sensors are not available."});let i=s=>{r||e(Dt(s));};return window.addEventListener("deviceorientation",i),t=i,d(()=>{r=true,t&&a$1()&&(window.removeEventListener("deviceorientation",t),t=null);},"web")},n=await C({feature:"sensors",startCommand:ae,stopCommand:ce,endpointPrefix:Q,payload:{},parseEvent:bt,onEvent:i=>{r||e(i);}});return n?d(()=>{r=true,n();},"native"):o()}};D();var Ir={haptics:Le,geolocation:xe,sensors:qe,camera:Oe,files:Ae,microphone:He};export{Oe as camera,Ir as device,Ae as files,xe as geolocation,Le as haptics,He as microphone,qe as sensors};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/feed_app-runtime-sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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).",