@sogni-ai/expo-client 1.0.0-alpha.13 → 1.0.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.0.0-alpha.14](https://github.com/Sogni-AI/expo-client/compare/v1.0.0-alpha.13...v1.0.0-alpha.14) (2026-05-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **media:** read upload bytes via arrayBuffer, not Blob ([90f99c5](https://github.com/Sogni-AI/expo-client/commit/90f99c532f888b0ec50a2bb9cca9a2fc84418cae))
7
+
1
8
  # [1.0.0-alpha.13](https://github.com/Sogni-AI/expo-client/compare/v1.0.0-alpha.12...v1.0.0-alpha.13) (2026-05-12)
2
9
 
3
10
 
@@ -22,12 +22,22 @@ export interface MediaInputBody {
22
22
  /**
23
23
  * Convert a MediaInput into a fetch body for upload to a presigned PUT URL.
24
24
  *
25
- * For URI inputs: reads the file via fetch(uri).blob(), so it works with
26
- * `file://`, `content://`, `ph://`, and `http(s)://` URIs.
25
+ * Reads the URI via `fetch(uri).arrayBuffer()` and hands back a `Uint8Array`.
26
+ * We deliberately avoid `res.blob()` here for two reasons:
27
27
  *
28
- * For Uint8Array inputs: passes the bytes through. Caller must provide
29
- * the correct Content-Type via the `contentType` parameter; we cannot
30
- * reliably detect MIME from raw bytes in all cases.
28
+ * 1. React Native's `Blob` is a minimal polyfill without `.text()` or
29
+ * `.arrayBuffer()` (see `Libraries/Blob/Blob.js`), so working with the
30
+ * raw bytes is more portable than rolling with a Blob whose surface
31
+ * depends on platform version.
32
+ * 2. On iOS PhotoKit (`ph://`) URIs, `res.blob()` has been observed to
33
+ * silently return a 0-byte blob — we'd PUT an empty body and the server
34
+ * would store a corrupted image. Reading as an ArrayBuffer makes the
35
+ * byte count visible and lets us throw on empty input.
36
+ *
37
+ * For very large files (videos, raw audio), this buffers the whole payload
38
+ * in JS memory. Acceptable for typical image / short audio reference
39
+ * uploads. If you have raw bytes already (e.g. from `expo-file-system`),
40
+ * pass `Uint8Array` directly to skip the fetch.
31
41
  */
32
42
  export declare function toFetchBody(input: MediaInput, contentTypeOverride?: string): Promise<MediaInputBody>;
33
43
  //# sourceMappingURL=mediaInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mediaInput.d.ts","sourceRoot":"","sources":["../../../src/lib/mediaInput.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,UAAU,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,IAAI,aAAa,CAEzE;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,UAAU,EACjB,mBAAmB,CAAC,EAAE,MAAM,GAC3B,OAAO,CAAC,cAAc,CAAC,CAazB"}
1
+ {"version":3,"file":"mediaInput.d.ts","sourceRoot":"","sources":["../../../src/lib/mediaInput.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,UAAU,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,IAAI,aAAa,CAEzE;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,UAAU,EACjB,mBAAmB,CAAC,EAAE,MAAM,GAC3B,OAAO,CAAC,cAAc,CAAC,CAoBzB"}
@@ -8,12 +8,22 @@ function isMediaInputUri(input) {
8
8
  /**
9
9
  * Convert a MediaInput into a fetch body for upload to a presigned PUT URL.
10
10
  *
11
- * For URI inputs: reads the file via fetch(uri).blob(), so it works with
12
- * `file://`, `content://`, `ph://`, and `http(s)://` URIs.
11
+ * Reads the URI via `fetch(uri).arrayBuffer()` and hands back a `Uint8Array`.
12
+ * We deliberately avoid `res.blob()` here for two reasons:
13
13
  *
14
- * For Uint8Array inputs: passes the bytes through. Caller must provide
15
- * the correct Content-Type via the `contentType` parameter; we cannot
16
- * reliably detect MIME from raw bytes in all cases.
14
+ * 1. React Native's `Blob` is a minimal polyfill without `.text()` or
15
+ * `.arrayBuffer()` (see `Libraries/Blob/Blob.js`), so working with the
16
+ * raw bytes is more portable than rolling with a Blob whose surface
17
+ * depends on platform version.
18
+ * 2. On iOS PhotoKit (`ph://`) URIs, `res.blob()` has been observed to
19
+ * silently return a 0-byte blob — we'd PUT an empty body and the server
20
+ * would store a corrupted image. Reading as an ArrayBuffer makes the
21
+ * byte count visible and lets us throw on empty input.
22
+ *
23
+ * For very large files (videos, raw audio), this buffers the whole payload
24
+ * in JS memory. Acceptable for typical image / short audio reference
25
+ * uploads. If you have raw bytes already (e.g. from `expo-file-system`),
26
+ * pass `Uint8Array` directly to skip the fetch.
17
27
  */
18
28
  async function toFetchBody(input, contentTypeOverride) {
19
29
  if (input instanceof Uint8Array) {
@@ -23,10 +33,15 @@ async function toFetchBody(input, contentTypeOverride) {
23
33
  if (!res.ok) {
24
34
  throw new Error(`Failed to read media at ${input.uri}: HTTP ${res.status}`);
25
35
  }
26
- const blob = await res.blob();
36
+ const arrayBuffer = await res.arrayBuffer();
37
+ if (arrayBuffer.byteLength === 0) {
38
+ throw new Error(`Read 0 bytes from ${input.uri}. On iOS, ph:// PhotoKit URIs sometimes return empty bodies — copy the asset to a file:// path first (e.g. via expo-image-picker with allowsEditing or expo-image-manipulator).`);
39
+ }
40
+ const bytes = new Uint8Array(arrayBuffer);
41
+ const responseContentType = res.headers.get('content-type') ?? undefined;
27
42
  return {
28
- body: blob,
29
- contentType: contentTypeOverride ?? input.type ?? blob.type ?? undefined
43
+ body: bytes,
44
+ contentType: contentTypeOverride ?? input.type ?? responseContentType
30
45
  };
31
46
  }
32
47
  //# sourceMappingURL=mediaInput.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mediaInput.js","sourceRoot":"","sources":["../../../src/lib/mediaInput.ts"],"names":[],"mappings":";;AAkBA,0CAEC;AAiBD,kCAgBC;AAnCD,SAAgB,eAAe,CAAC,KAAiB;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC;AACrD,CAAC;AAOD;;;;;;;;;GASG;AACI,KAAK,UAAU,WAAW,CAC/B,KAAiB,EACjB,mBAA4B;IAE5B,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,KAA4B,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC;IAClF,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,GAAG,UAAU,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,OAAO;QACL,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,mBAAmB,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,SAAS;KACzE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"mediaInput.js","sourceRoot":"","sources":["../../../src/lib/mediaInput.ts"],"names":[],"mappings":";;AAkBA,0CAEC;AA2BD,kCAuBC;AApDD,SAAgB,eAAe,CAAC,KAAiB;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC;AACrD,CAAC;AAOD;;;;;;;;;;;;;;;;;;;GAmBG;AACI,KAAK,UAAU,WAAW,CAC/B,KAAiB,EACjB,mBAA4B;IAE5B,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,KAA4B,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC;IAClF,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,GAAG,UAAU,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,WAAW,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,CAAC,GAAG,iLAAiL,CAChN,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAC;IACzE,OAAO;QACL,IAAI,EAAE,KAA4B;QAClC,WAAW,EAAE,mBAAmB,IAAI,KAAK,CAAC,IAAI,IAAI,mBAAmB;KACtE,CAAC;AACJ,CAAC"}
@@ -4,12 +4,22 @@ export function isMediaInputUri(input) {
4
4
  /**
5
5
  * Convert a MediaInput into a fetch body for upload to a presigned PUT URL.
6
6
  *
7
- * For URI inputs: reads the file via fetch(uri).blob(), so it works with
8
- * `file://`, `content://`, `ph://`, and `http(s)://` URIs.
7
+ * Reads the URI via `fetch(uri).arrayBuffer()` and hands back a `Uint8Array`.
8
+ * We deliberately avoid `res.blob()` here for two reasons:
9
9
  *
10
- * For Uint8Array inputs: passes the bytes through. Caller must provide
11
- * the correct Content-Type via the `contentType` parameter; we cannot
12
- * reliably detect MIME from raw bytes in all cases.
10
+ * 1. React Native's `Blob` is a minimal polyfill without `.text()` or
11
+ * `.arrayBuffer()` (see `Libraries/Blob/Blob.js`), so working with the
12
+ * raw bytes is more portable than rolling with a Blob whose surface
13
+ * depends on platform version.
14
+ * 2. On iOS PhotoKit (`ph://`) URIs, `res.blob()` has been observed to
15
+ * silently return a 0-byte blob — we'd PUT an empty body and the server
16
+ * would store a corrupted image. Reading as an ArrayBuffer makes the
17
+ * byte count visible and lets us throw on empty input.
18
+ *
19
+ * For very large files (videos, raw audio), this buffers the whole payload
20
+ * in JS memory. Acceptable for typical image / short audio reference
21
+ * uploads. If you have raw bytes already (e.g. from `expo-file-system`),
22
+ * pass `Uint8Array` directly to skip the fetch.
13
23
  */
14
24
  export async function toFetchBody(input, contentTypeOverride) {
15
25
  if (input instanceof Uint8Array) {
@@ -19,10 +29,15 @@ export async function toFetchBody(input, contentTypeOverride) {
19
29
  if (!res.ok) {
20
30
  throw new Error(`Failed to read media at ${input.uri}: HTTP ${res.status}`);
21
31
  }
22
- const blob = await res.blob();
32
+ const arrayBuffer = await res.arrayBuffer();
33
+ if (arrayBuffer.byteLength === 0) {
34
+ throw new Error(`Read 0 bytes from ${input.uri}. On iOS, ph:// PhotoKit URIs sometimes return empty bodies — copy the asset to a file:// path first (e.g. via expo-image-picker with allowsEditing or expo-image-manipulator).`);
35
+ }
36
+ const bytes = new Uint8Array(arrayBuffer);
37
+ const responseContentType = res.headers.get('content-type') ?? undefined;
23
38
  return {
24
- body: blob,
25
- contentType: contentTypeOverride ?? input.type ?? blob.type ?? undefined
39
+ body: bytes,
40
+ contentType: contentTypeOverride ?? input.type ?? responseContentType
26
41
  };
27
42
  }
28
43
  //# sourceMappingURL=mediaInput.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mediaInput.js","sourceRoot":"","sources":["../../../src/lib/mediaInput.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC;AACrD,CAAC;AAOD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAiB,EACjB,mBAA4B;IAE5B,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,KAA4B,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC;IAClF,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,GAAG,UAAU,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,OAAO;QACL,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,mBAAmB,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,SAAS;KACzE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"mediaInput.js","sourceRoot":"","sources":["../../../src/lib/mediaInput.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC;AACrD,CAAC;AAOD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAiB,EACjB,mBAA4B;IAE5B,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,KAA4B,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC;IAClF,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,GAAG,UAAU,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,WAAW,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,CAAC,GAAG,iLAAiL,CAChN,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAC;IACzE,OAAO;QACL,IAAI,EAAE,KAA4B;QAClC,WAAW,EAAE,mBAAmB,IAAI,KAAK,CAAC,IAAI,IAAI,mBAAmB;KACtE,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sogni-ai/expo-client",
3
- "version": "1.0.0-alpha.13",
3
+ "version": "1.0.0-alpha.14",
4
4
  "description": "Sogni SDK for Expo/React Native - AI image, video & audio generation plus LLM chat via the Sogni Supernet",
5
5
  "keywords": [
6
6
  "sogni",
@@ -28,12 +28,22 @@ export interface MediaInputBody {
28
28
  /**
29
29
  * Convert a MediaInput into a fetch body for upload to a presigned PUT URL.
30
30
  *
31
- * For URI inputs: reads the file via fetch(uri).blob(), so it works with
32
- * `file://`, `content://`, `ph://`, and `http(s)://` URIs.
31
+ * Reads the URI via `fetch(uri).arrayBuffer()` and hands back a `Uint8Array`.
32
+ * We deliberately avoid `res.blob()` here for two reasons:
33
33
  *
34
- * For Uint8Array inputs: passes the bytes through. Caller must provide
35
- * the correct Content-Type via the `contentType` parameter; we cannot
36
- * reliably detect MIME from raw bytes in all cases.
34
+ * 1. React Native's `Blob` is a minimal polyfill without `.text()` or
35
+ * `.arrayBuffer()` (see `Libraries/Blob/Blob.js`), so working with the
36
+ * raw bytes is more portable than rolling with a Blob whose surface
37
+ * depends on platform version.
38
+ * 2. On iOS PhotoKit (`ph://`) URIs, `res.blob()` has been observed to
39
+ * silently return a 0-byte blob — we'd PUT an empty body and the server
40
+ * would store a corrupted image. Reading as an ArrayBuffer makes the
41
+ * byte count visible and lets us throw on empty input.
42
+ *
43
+ * For very large files (videos, raw audio), this buffers the whole payload
44
+ * in JS memory. Acceptable for typical image / short audio reference
45
+ * uploads. If you have raw bytes already (e.g. from `expo-file-system`),
46
+ * pass `Uint8Array` directly to skip the fetch.
37
47
  */
38
48
  export async function toFetchBody(
39
49
  input: MediaInput,
@@ -46,9 +56,16 @@ export async function toFetchBody(
46
56
  if (!res.ok) {
47
57
  throw new Error(`Failed to read media at ${input.uri}: HTTP ${res.status}`);
48
58
  }
49
- const blob = await res.blob();
59
+ const arrayBuffer = await res.arrayBuffer();
60
+ if (arrayBuffer.byteLength === 0) {
61
+ throw new Error(
62
+ `Read 0 bytes from ${input.uri}. On iOS, ph:// PhotoKit URIs sometimes return empty bodies — copy the asset to a file:// path first (e.g. via expo-image-picker with allowsEditing or expo-image-manipulator).`
63
+ );
64
+ }
65
+ const bytes = new Uint8Array(arrayBuffer);
66
+ const responseContentType = res.headers.get('content-type') ?? undefined;
50
67
  return {
51
- body: blob,
52
- contentType: contentTypeOverride ?? input.type ?? blob.type ?? undefined
68
+ body: bytes as unknown as BodyInit,
69
+ contentType: contentTypeOverride ?? input.type ?? responseContentType
53
70
  };
54
71
  }