@mentra/crust 3.4.0-dev.1011 → 3.4.0-dev.1017

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.
@@ -54,6 +54,10 @@ class FetchBridgeTest {
54
54
  private val utf8Bytes = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) +
55
55
  "{\"a\":\"é😀\"}".toByteArray(Charsets.UTF_8) + byteArrayOf(0x00, 0x7F)
56
56
 
57
+ /** Two MB of valid UTF-8 text: arrayBuffer() re-encodes it inside the 32 MB miniapp heap. */
58
+ private val largeUtf8Text: ByteArray =
59
+ "héllo 😀 中文 chunk of reply text ".repeat(52_000).toByteArray(Charsets.UTF_8)
60
+
57
61
  // crust's build.gradle ships this file as the runtime's startup.js asset.
58
62
  private val polyfill: String by lazy { File("../../jspolyfill/assets/startup.js").readText() }
59
63
 
@@ -63,6 +67,7 @@ class FetchBridgeTest {
63
67
  override fun dispatch(request: RecordedRequest): MockResponse = when (request.path) {
64
68
  "/reply.pcm" -> response("application/octet-stream").setBody(Buffer().write(pcm))
65
69
  "/utf8.bin" -> response("application/octet-stream").setBody(Buffer().write(utf8Bytes))
70
+ "/large.txt" -> response("text/plain").setBody(Buffer().write(largeUtf8Text))
66
71
  "/data.json" -> response("application/json").setBody("{\"message\":\"héllo 😀 中文\"}")
67
72
  "/upload" -> response("text/plain").setBody("ok")
68
73
  else -> MockResponse().setResponseCode(404)
@@ -108,6 +113,21 @@ class FetchBridgeTest {
108
113
  assertArrayEquals(utf8Bytes, bytes(result["bytes"]))
109
114
  }
110
115
 
116
+ @Test fun `arrayBuffer re-encodes a large UTF-8 body within the miniapp heap`() {
117
+ val result = runMiniapp(
118
+ """
119
+ const bytes = new Uint8Array(await (await fetch(BASE + "/large.txt")).arrayBuffer())
120
+ let sum = 0
121
+ for (let i = 0; i < bytes.length; i++) sum = (sum * 31 + bytes[i]) >>> 0
122
+ return {length: bytes.length, sum}
123
+ """,
124
+ )
125
+ var sum = 0L
126
+ for (byte in largeUtf8Text) sum = (sum * 31 + (byte.toInt() and 0xFF)) and 0xFFFFFFFFL
127
+ assertEquals(largeUtf8Text.size, (result["length"] as Number).toInt())
128
+ assertEquals(sum, (result["sum"] as Number).toLong())
129
+ }
130
+
111
131
  @Test fun `responses are sent as text only when that text re-encodes to the same bytes`() {
112
132
  val text = JSCPolyfillBridge.fetchResult(responseWith(utf8Bytes))
113
133
  assertEquals(String(utf8Bytes, Charsets.UTF_8), text.getString("body"))
@@ -571,38 +571,48 @@
571
571
  return parts.join("");
572
572
  }
573
573
  if (typeof g.TextEncoder !== "function") {
574
+ const codePointAt = (str, i) => {
575
+ const code = str.charCodeAt(i);
576
+ if (code >= 55296 && code <= 56319 && i + 1 < str.length) {
577
+ const next = str.charCodeAt(i + 1);
578
+ if (next >= 56320 && next <= 57343) return (code - 55296 << 10) + (next - 56320) + 65536;
579
+ }
580
+ return code;
581
+ };
574
582
  class TextEncoderPolyfill {
575
583
  constructor() {
576
584
  this.encoding = "utf-8";
577
585
  }
578
586
  encode(input) {
579
587
  const str = input ?? "";
580
- const out = [];
588
+ let length = 0;
581
589
  for (let i = 0; i < str.length; i++) {
582
- let code = str.charCodeAt(i);
583
- if (code >= 55296 && code <= 56319 && i + 1 < str.length) {
584
- const next = str.charCodeAt(i + 1);
585
- if (next >= 56320 && next <= 57343) {
586
- code = (code - 55296 << 10) + (next - 56320) + 65536;
587
- i++;
588
- }
589
- }
590
+ const code = codePointAt(str, i);
591
+ if (code > 65535) i++;
592
+ length += code < 128 ? 1 : code < 2048 ? 2 : code < 65536 ? 3 : 4;
593
+ }
594
+ const out = new Uint8Array(length);
595
+ let at = 0;
596
+ for (let i = 0; i < str.length; i++) {
597
+ const code = codePointAt(str, i);
598
+ if (code > 65535) i++;
590
599
  if (code < 128) {
591
- out.push(code);
600
+ out[at++] = code;
592
601
  } else if (code < 2048) {
593
- out.push(192 | code >> 6, 128 | code & 63);
602
+ out[at++] = 192 | code >> 6;
603
+ out[at++] = 128 | code & 63;
594
604
  } else if (code < 65536) {
595
- out.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
605
+ out[at++] = 224 | code >> 12;
606
+ out[at++] = 128 | code >> 6 & 63;
607
+ out[at++] = 128 | code & 63;
596
608
  } else {
597
- out.push(
598
- 240 | code >> 18,
599
- 128 | code >> 12 & 63,
600
- 128 | code >> 6 & 63,
601
- 128 | code & 63
602
- );
609
+ out[at++] = 240 | code >> 18;
610
+ out[at++] = 128 | code >> 12 & 63;
611
+ out[at++] = 128 | code >> 6 & 63;
612
+ out[at++] = 128 | code & 63;
603
613
  }
604
614
  }
605
- return new Uint8Array(out);
615
+ return out;
606
616
  }
607
617
  }
608
618
  ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/crust",
3
- "version": "3.4.0-dev.1011",
3
+ "version": "3.4.0-dev.1017",
4
4
  "description": "Mentra Native Module",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  "license": "Apache-2.0",
36
36
  "homepage": "https://github.com/Mentra-Community/MentraOS/tree/dev/mobile/modules/crust#readme",
37
37
  "dependencies": {
38
- "@mentra/jspolyfill": "3.4.0-dev.1011"
38
+ "@mentra/jspolyfill": "3.4.0-dev.1017"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@expo/config-plugins": ">=8.0.0",