sandboxedjs 0.1.91 → 0.1.93

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/dist/index.js CHANGED
@@ -20014,6 +20014,67 @@ var wheels_default = {
20014
20014
  ]
20015
20015
  }
20016
20016
  },
20017
+ {
20018
+ name: "argon2-cffi-bindings",
20019
+ version: "21.2.0",
20020
+ filename: "argon2_cffi_bindings-21.2.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20021
+ sha256: "16a0bb095ad8c966509752c2f0498bea965839deead4d84ad4badf6465b5948b",
20022
+ abiId: "sbxabi1-c2637d04695ad927",
20023
+ requires: [
20024
+ "cffi>=1.0.1",
20025
+ 'pytest; extra == "tests"',
20026
+ 'pytest; extra == "dev"',
20027
+ 'cogapp; extra == "dev"',
20028
+ 'pre-commit; extra == "dev"',
20029
+ 'wheel; extra == "dev"'
20030
+ ],
20031
+ provenance: {
20032
+ sourceTreeDigest: "8093e19a848187cbd5a92c541c1dab627f0bacb7ac8d09216d0226a313d6f9ab",
20033
+ recipeSha256: "3744603cabcf8107c497d8e6d106614135ef0dfeec5d32ac24039b7e8e4d9009",
20034
+ buildTools: [
20035
+ "setuptools==84.0.0",
20036
+ "cffi==2.1.1",
20037
+ "setuptools-scm==10.2.3"
20038
+ ],
20039
+ nativeDependencies: [],
20040
+ patches: []
20041
+ }
20042
+ },
20043
+ {
20044
+ name: "bcrypt",
20045
+ version: "5.0.0",
20046
+ filename: "bcrypt-5.0.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20047
+ sha256: "a8fdcaccccf931b2fb3e327f94e8dc9a8e7c53bf17bc9fb85aae25211cbac61f",
20048
+ abiId: "sbxabi1-c2637d04695ad927",
20049
+ requires: [],
20050
+ provenance: {
20051
+ sourceTreeDigest: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
20052
+ recipeSha256: "3a1af92dc00b0d1896cf1483399c002df4b36289de7dea2324035407e78e9f9b",
20053
+ buildTools: [
20054
+ "setuptools==84.0.0"
20055
+ ],
20056
+ nativeDependencies: [],
20057
+ patches: []
20058
+ }
20059
+ },
20060
+ {
20061
+ name: "brotli",
20062
+ version: "1.2.0",
20063
+ filename: "brotli-1.2.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20064
+ sha256: "b67d1b9169ba3e0790d04b8020adb1a58f0b89f8961aca360dca5f020de07a21",
20065
+ abiId: "sbxabi1-c2637d04695ad927",
20066
+ requires: [],
20067
+ provenance: {
20068
+ sourceTreeDigest: "7b563fa2e7f20edac204cc80004cae35c844b36a83758e5957184383332d1209",
20069
+ recipeSha256: "09bf4f6072d637866a6b3c3deaec419dc3ff8a32a6f3b0813f57e06316bfcf39",
20070
+ buildTools: [
20071
+ "setuptools==84.0.0",
20072
+ "pkgconfig==1.6.0"
20073
+ ],
20074
+ nativeDependencies: [],
20075
+ patches: []
20076
+ }
20077
+ },
20017
20078
  {
20018
20079
  name: "cffi",
20019
20080
  version: "2.1.1",
@@ -20035,6 +20096,26 @@ var wheels_default = {
20035
20096
  patches: []
20036
20097
  }
20037
20098
  },
20099
+ {
20100
+ name: "cryptography",
20101
+ version: "50.0.1",
20102
+ filename: "cryptography-50.0.1-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20103
+ sha256: "10bb6568bf4368a593ea3a990ea870e57d4eeb1d3278af89bf24a8713473238e",
20104
+ abiId: "sbxabi1-c2637d04695ad927",
20105
+ requires: [
20106
+ 'cffi>=2.0.0; platform_python_implementation != "PyPy"'
20107
+ ],
20108
+ provenance: {
20109
+ sourceTreeDigest: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
20110
+ recipeSha256: "082169d1d317ba001e37ccfb7a40a0dafd8d3d34e42034da8590f0207ae73ea5",
20111
+ buildTools: [
20112
+ "setuptools==84.0.0",
20113
+ "cffi==2.1.1"
20114
+ ],
20115
+ nativeDependencies: [],
20116
+ patches: []
20117
+ }
20118
+ },
20038
20119
  {
20039
20120
  name: "fastar",
20040
20121
  version: "0.12.0",
@@ -20088,6 +20169,32 @@ var wheels_default = {
20088
20169
  patches: []
20089
20170
  }
20090
20171
  },
20172
+ {
20173
+ name: "lz4",
20174
+ version: "4.4.5",
20175
+ filename: "lz4-4.4.5-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20176
+ sha256: "f94c0ef7d84c9bf700cc86f69a28382699262e442e29d9763736afdb61141ffe",
20177
+ abiId: "sbxabi1-c2637d04695ad927",
20178
+ requires: [
20179
+ 'pytest!=3.3.0; extra == "tests"',
20180
+ 'psutil; extra == "tests"',
20181
+ 'pytest-cov; extra == "tests"',
20182
+ 'sphinx>=1.6.0; extra == "docs"',
20183
+ 'sphinx_bootstrap_theme; extra == "docs"',
20184
+ 'flake8; extra == "flake8"'
20185
+ ],
20186
+ provenance: {
20187
+ sourceTreeDigest: "5f7bda9bfb026d0a30bb5e3383a4b25ae6b7f44fd6dc74950653d57f84c9d88e",
20188
+ recipeSha256: "80250de0ae159cd9f977bdfad059de0d192f28555225b084b42b5a180f684e0c",
20189
+ buildTools: [
20190
+ "setuptools==84.0.0",
20191
+ "pkgconfig==1.6.0",
20192
+ "setuptools-scm==10.2.3"
20193
+ ],
20194
+ nativeDependencies: [],
20195
+ patches: []
20196
+ }
20197
+ },
20091
20198
  {
20092
20199
  name: "msgpack",
20093
20200
  version: "1.1.0",
@@ -20127,20 +20234,86 @@ var wheels_default = {
20127
20234
  patches: []
20128
20235
  }
20129
20236
  },
20237
+ {
20238
+ name: "orjson",
20239
+ version: "3.12.0",
20240
+ filename: "orjson-3.12.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20241
+ sha256: "1a8b9b3ab496f6d8b1ef73d0ae282262d8399f55243bf01f2dbfac46aff99a35",
20242
+ abiId: "sbxabi1-c2637d04695ad927",
20243
+ requires: [],
20244
+ provenance: {
20245
+ sourceTreeDigest: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
20246
+ recipeSha256: "cda75f871a972e759361a12baeaa3531dc9c68c958556674fd30f3d639fb6b45",
20247
+ buildTools: [
20248
+ "setuptools==84.0.0"
20249
+ ],
20250
+ nativeDependencies: [],
20251
+ patches: []
20252
+ }
20253
+ },
20130
20254
  {
20131
20255
  name: "ormsgpack",
20132
20256
  version: "1.12.2",
20133
20257
  filename: "ormsgpack-1.12.2-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20134
- sha256: "42ca38d1948a36d43294c7843fd9398f1ae88bddd9e56b26ad80a644f9bdec52",
20258
+ sha256: "7fa4463a69aad2fe057c111642cce71c8f72af8465c22769d839415dbc15ac79",
20135
20259
  abiId: "sbxabi1-c2637d04695ad927",
20136
20260
  requires: [],
20137
20261
  provenance: {
20138
- sourceTreeDigest: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
20139
- recipeSha256: "6ab618fe6bc9dee1fa76f57f298c55732fe2241e122240f44007a2ce0025e1b2",
20262
+ sourceTreeDigest: "eff3a7be3bc6de2a15e479c0e3e94905c35203fd83369124073f2df5cce46409",
20263
+ recipeSha256: "bd398762801fce51d35b33439f5e57037e1d4d274f25bd795683e2ad17d34b62",
20140
20264
  buildTools: [
20141
20265
  "setuptools==84.0.0"
20142
20266
  ],
20143
20267
  nativeDependencies: [],
20268
+ patches: [
20269
+ "0001-pylong-asbytearray-313-signature.patch"
20270
+ ]
20271
+ }
20272
+ },
20273
+ {
20274
+ name: "pillow",
20275
+ version: "12.3.0",
20276
+ filename: "pillow-12.3.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20277
+ sha256: "7e585c71bf943029d8201283308a1b43f2be5f166d9beb36697d1f6bfba865e9",
20278
+ abiId: "sbxabi1-c2637d04695ad927",
20279
+ requires: [
20280
+ 'furo; extra == "docs"',
20281
+ 'olefile; extra == "docs"',
20282
+ 'sphinx>=8.2; extra == "docs"',
20283
+ 'sphinx-autobuild; extra == "docs"',
20284
+ 'sphinx-copybutton; extra == "docs"',
20285
+ 'sphinx-inline-tabs; extra == "docs"',
20286
+ 'sphinxext-opengraph; extra == "docs"',
20287
+ 'olefile; extra == "fpx"',
20288
+ 'olefile; extra == "mic"',
20289
+ 'arro3-compute; extra == "test-arrow"',
20290
+ 'arro3-core; extra == "test-arrow"',
20291
+ 'nanoarrow; extra == "test-arrow"',
20292
+ 'pyarrow; extra == "test-arrow"',
20293
+ 'coverage>=7.4.2; extra == "tests"',
20294
+ 'defusedxml; extra == "tests"',
20295
+ 'markdown2; extra == "tests"',
20296
+ 'olefile; extra == "tests"',
20297
+ 'packaging; extra == "tests"',
20298
+ 'pytest; extra == "tests"',
20299
+ 'pytest-cov; extra == "tests"',
20300
+ 'pytest-timeout; extra == "tests"',
20301
+ 'pytest-xdist; extra == "tests"',
20302
+ 'setuptools; extra == "tests"',
20303
+ 'trove-classifiers>=2024.10.12; extra == "tests"',
20304
+ 'defusedxml; extra == "xmp"'
20305
+ ],
20306
+ provenance: {
20307
+ sourceTreeDigest: "5427eff55a6a0e5f667d70527c2c3749f2c0786598903d7bbe7aaf16c4e95e31",
20308
+ recipeSha256: "77418b7e139e4779f2a422a06abbea27c941174b1f86597a2f3c4483bb236b7f",
20309
+ buildTools: [
20310
+ "setuptools==84.0.0",
20311
+ "pybind11==3.1.0"
20312
+ ],
20313
+ nativeDependencies: [
20314
+ "z==1.3.1",
20315
+ "jpeg==9f"
20316
+ ],
20144
20317
  patches: []
20145
20318
  }
20146
20319
  },
@@ -20201,6 +20374,23 @@ var wheels_default = {
20201
20374
  patches: []
20202
20375
  }
20203
20376
  },
20377
+ {
20378
+ name: "regex",
20379
+ version: "2026.9.10",
20380
+ filename: "regex-2026.9.10-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20381
+ sha256: "44995aafa6294c4c817ff658755b931d936b0c00db6e4b086b6469a76938f7c8",
20382
+ abiId: "sbxabi1-c2637d04695ad927",
20383
+ requires: [],
20384
+ provenance: {
20385
+ sourceTreeDigest: "db71e2c60e74953662c35d3ce3f75893ac7a9df8190afecc916f1a73981c50f5",
20386
+ recipeSha256: "5fb970f2bdc3dfe0be8f98b04caa083c6a9088351d1415d4f9f49d154c7df90d",
20387
+ buildTools: [
20388
+ "setuptools==84.0.0"
20389
+ ],
20390
+ nativeDependencies: [],
20391
+ patches: []
20392
+ }
20393
+ },
20204
20394
  {
20205
20395
  name: "rignore",
20206
20396
  version: "0.8.1",
@@ -20389,6 +20579,23 @@ var wheels_default = {
20389
20579
  patches: []
20390
20580
  }
20391
20581
  },
20582
+ {
20583
+ name: "simplejson",
20584
+ version: "4.1.2",
20585
+ filename: "simplejson-4.1.2-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20586
+ sha256: "80671c2b02beb0d429108df8cefb6f27857b34e032bb5a5f1e0f957e7bf3cab6",
20587
+ abiId: "sbxabi1-c2637d04695ad927",
20588
+ requires: [],
20589
+ provenance: {
20590
+ sourceTreeDigest: "055af71755ff30d71076fdebc6f605e66f5876e6f01350a7d6075e8679006111",
20591
+ recipeSha256: "e163a1f2e78f32265c3ffdbf743dab27bc9e0829f21c553cabf0bafafd7d69fd",
20592
+ buildTools: [
20593
+ "setuptools==84.0.0"
20594
+ ],
20595
+ nativeDependencies: [],
20596
+ patches: []
20597
+ }
20598
+ },
20392
20599
  {
20393
20600
  name: "siphash24",
20394
20601
  version: "1.9",
@@ -20412,6 +20619,44 @@ var wheels_default = {
20412
20619
  ]
20413
20620
  }
20414
20621
  },
20622
+ {
20623
+ name: "tiktoken",
20624
+ version: "0.14.0",
20625
+ filename: "tiktoken-0.14.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20626
+ sha256: "a006e3c7d5281b05af21d11692d9be00bb2d4ca554589889fe93c5616e94d02f",
20627
+ abiId: "sbxabi1-c2637d04695ad927",
20628
+ requires: [
20629
+ "regex>=2022.1.18",
20630
+ "requests>=2.26.0"
20631
+ ],
20632
+ provenance: {
20633
+ sourceTreeDigest: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
20634
+ recipeSha256: "99b8a588584b366576b343a3eeeff8cae87f9ba917b26460b050b2fc462239ed",
20635
+ buildTools: [
20636
+ "setuptools==84.0.0"
20637
+ ],
20638
+ nativeDependencies: [],
20639
+ patches: []
20640
+ }
20641
+ },
20642
+ {
20643
+ name: "ujson",
20644
+ version: "6.0.0",
20645
+ filename: "ujson-6.0.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20646
+ sha256: "3d4fd009acb9fad7a2070b0e2e498f6257e9c0cc28f806950d8c04cbb6849c24",
20647
+ abiId: "sbxabi1-c2637d04695ad927",
20648
+ requires: [],
20649
+ provenance: {
20650
+ sourceTreeDigest: "2a0c67ecb71628cb89b46090372d11dc4cf6e91c2406260a4e544c17331a31da",
20651
+ recipeSha256: "e9f3fe48e14241bd38d1d20dc691413d8c646984d67ae3898f4b27d10af4bf80",
20652
+ buildTools: [
20653
+ "setuptools==84.0.0",
20654
+ "setuptools-scm==10.2.3"
20655
+ ],
20656
+ nativeDependencies: [],
20657
+ patches: []
20658
+ }
20659
+ },
20415
20660
  {
20416
20661
  name: "uuid-utils",
20417
20662
  version: "0.17.1",
@@ -20494,7 +20739,7 @@ var wheels_default = {
20494
20739
 
20495
20740
  // package.json
20496
20741
  var package_default = {
20497
- version: "0.1.91"};
20742
+ version: "0.1.93"};
20498
20743
 
20499
20744
  // src/python/extension-abi.ts
20500
20745
  var EXTENSION_ABI = {
@@ -22599,7 +22844,13 @@ async function serve(ctx, connection) {
22599
22844
  headers: forwarded,
22600
22845
  ...request.body.length > 0 ? { body: request.body } : {}
22601
22846
  });
22602
- writeResponse(connection, result.status, result.statusText, result.headers, result.body);
22847
+ const {
22848
+ "content-encoding": _encoding,
22849
+ "content-length": _length,
22850
+ "transfer-encoding": _chunked,
22851
+ ...responseHeaders
22852
+ } = result.headers;
22853
+ writeResponse(connection, result.status, result.statusText, responseHeaders, result.body);
22603
22854
  } catch (error) {
22604
22855
  const message = error instanceof Error ? error.message : String(error);
22605
22856
  try {
@@ -22898,16 +23149,33 @@ def run(main, *, loop_factory=new_event_loop, debug=None, **run_kwargs):
22898
23149
  with __asyncio.Runner(loop_factory=loop_factory, debug=debug) as runner:
22899
23150
  return runner.run(main)
22900
23151
  `;
22901
- function builtinDistribution(name, version, summary, files) {
23152
+ var HTTPX2_JSFETCH_VERSION = "1.0";
23153
+ var HTTPX2_JSFETCH_SOURCE = `"""SandboxedJs stand-in for httpx2-jsfetch.
23154
+
23155
+ The real package drives the browser fetch API through Pyodide's \`js\` module.
23156
+ This runtime has no \`js\` module but does have sockets, so httpx2's regular
23157
+ httpcore2 transports are used under the names httpx2 imports on Emscripten.
23158
+ """
23159
+
23160
+ from httpx2._transports.default import (
23161
+ AsyncHTTPTransport as AsyncJavascriptFetchTransport,
23162
+ HTTPTransport as JavascriptFetchTransport,
23163
+ )
23164
+
23165
+ __all__ = ["AsyncJavascriptFetchTransport", "JavascriptFetchTransport"]
23166
+ `;
23167
+ function builtinDistribution(name, version, summary, files, requires = []) {
22902
23168
  return {
22903
23169
  version,
23170
+ requires,
22904
23171
  files: {
22905
23172
  ...files,
22906
23173
  [`${name}-${version}.dist-info/METADATA`]: `Metadata-Version: 2.1
22907
23174
  Name: ${name}
22908
23175
  Version: ${version}
22909
23176
  Summary: ${summary}
22910
- `,
23177
+ ` + requires.map((r) => `Requires-Dist: ${r}
23178
+ `).join(""),
22911
23179
  [`${name}-${version}.dist-info/WHEEL`]: "Wheel-Version: 1.0\nGenerator: sandboxedjs\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
22912
23180
  [`${name}-${version}.dist-info/INSTALLER`]: "sandboxedjs\n",
22913
23181
  [`${name}-${version}.dist-info/RECORD`]: ""
@@ -22915,6 +23183,13 @@ Summary: ${summary}
22915
23183
  };
22916
23184
  }
22917
23185
  var BUILTINS3 = {
23186
+ "httpx2-jsfetch": builtinDistribution(
23187
+ "httpx2_jsfetch",
23188
+ HTTPX2_JSFETCH_VERSION,
23189
+ "SandboxedJs stand-in: httpx2's socket transports under the jsfetch names",
23190
+ { "httpx2_jsfetch/__init__.py": HTTPX2_JSFETCH_SOURCE },
23191
+ ["httpcore2[asyncio]", "anyio>=4.10", "truststore>=0.10"]
23192
+ ),
22918
23193
  greenlet: builtinDistribution(
22919
23194
  "greenlet",
22920
23195
  GREENLET_VERSION,
@@ -22939,7 +23214,7 @@ function builtinCandidates(name) {
22939
23214
  filename: `${name}-${builtin.version}-py3-none-any.whl`,
22940
23215
  sha256: "",
22941
23216
  metadataUrl: null,
22942
- indexedRequires: []
23217
+ indexedRequires: builtin.requires
22943
23218
  }];
22944
23219
  }
22945
23220
  function isBuiltinOnly(name) {
@@ -23835,6 +24110,36 @@ if sys.platform in ("emscripten", "wasi"):
23835
24110
 
23836
24111
  sys.meta_path.insert(0, _SbxAsyncioWatcherHook())
23837
24112
 
24113
+ class _SbxPyodideFetchHook:
24114
+ """Libraries read \`sys.platform == "emscripten"\` as "running in Pyodide"
24115
+ and switch to a browser-fetch backend that imports Pyodide's \`js\`
24116
+ module. This runtime has no \`js\` but does have sockets, so those
24117
+ backends are replaced by modules that leave the socket path in place.
24118
+ urllib3 (and so requests) imports its backend at package import, which
24119
+ made anything using requests fail with "No module named 'js'"."""
24120
+
24121
+ _STANDINS = {
24122
+ # urllib3/__init__.py calls inject_into_urllib3() on emscripten.
24123
+ "urllib3.contrib.emscripten": "def inject_into_urllib3():\\n pass\\n",
24124
+ }
24125
+
24126
+ def find_spec(self, name, path=None, target=None):
24127
+ source = self._STANDINS.get(name)
24128
+ if source is None:
24129
+ return None
24130
+ import importlib.util
24131
+
24132
+ class _Loader:
24133
+ def create_module(self, spec):
24134
+ return None
24135
+
24136
+ def exec_module(self, module):
24137
+ exec(compile(source, f"<sandboxedjs {name}>", "exec"), module.__dict__)
24138
+
24139
+ return importlib.util.spec_from_loader(name, _Loader(), is_package=False)
24140
+
24141
+ sys.meta_path.insert(0, _SbxPyodideFetchHook())
24142
+
23838
24143
 
23839
24144
  _egress = os.environ.get("SBX_HTTP_EGRESS")
23840
24145
  if _egress:
@@ -23916,6 +24221,255 @@ if _egress:
23916
24221
  http.client.HTTPConnection = _SbxEgressConnection
23917
24222
  http.client.HTTPSConnection = _SbxEgressTlsConnection
23918
24223
 
24224
+ # Everything above routes code that reaches for the standard library. The
24225
+ # popular HTTP clients do not: urllib3 (so requests), httpcore (so httpx)
24226
+ # and httpcore2 (so httpx2) open sockets themselves, and raw sockets do not
24227
+ # leave this container -- connect() to anything but loopback is
24228
+ # ENETUNREACH, and CPython's ssl cannot wrap these sockets anyway. So each
24229
+ # client's connection layer is replaced by one that speaks plain HTTP to
24230
+ # the egress endpoint with the absolute URL in the request line, which is
24231
+ # what the standard-library shims above already do.
24232
+
24233
+ def _sbx_egress_request(method, url, headers, body, timeout=None):
24234
+ """One request through the egress. Returns (status, reason, headers, body)."""
24235
+ connection = _RealHTTPConnection(_egress_host, _egress_port, timeout=timeout)
24236
+ try:
24237
+ connection.request(method, url, body, {k: v for k, v in headers if k.lower() != "host"})
24238
+ response = connection.getresponse()
24239
+ payload = response.read()
24240
+ if response.getheader("x-sbx-error"):
24241
+ raise OSError(payload.decode("utf-8", "replace").strip())
24242
+ return response.status, response.reason, response.getheaders(), payload
24243
+ finally:
24244
+ connection.close()
24245
+
24246
+ async def _sbx_egress_request_async(method, url, headers, body):
24247
+ """The same hop as _sbx_egress_request, over asyncio streams."""
24248
+ import asyncio
24249
+ reader, writer = await asyncio.open_connection(_egress_host, _egress_port)
24250
+ try:
24251
+ payload = body or b""
24252
+ if isinstance(payload, str):
24253
+ payload = payload.encode()
24254
+ lines = [f"{method} {url} HTTP/1.1", f"Host: {_egress_host}:{_egress_port}"]
24255
+ for key, value in headers:
24256
+ if key.lower() in ("host", "connection", "content-length", "transfer-encoding"):
24257
+ continue
24258
+ lines.append(f"{key}: {value}")
24259
+ lines.append(f"Content-Length: {len(payload)}")
24260
+ # Read to EOF rather than tracking keep-alive framing: one request
24261
+ # per connection, which is what this hop is for.
24262
+ lines.append("Connection: close")
24263
+ writer.write(("\\r\\n".join(lines) + "\\r\\n\\r\\n").encode("latin-1") + payload)
24264
+ await writer.drain()
24265
+
24266
+ head = await reader.readuntil(b"\\r\\n\\r\\n")
24267
+ start, _, rest = head.decode("latin-1").partition("\\r\\n")
24268
+ _, _, status_text = start.partition(" ")
24269
+ code, _, reason = status_text.partition(" ")
24270
+ received = []
24271
+ for line in rest.split("\\r\\n"):
24272
+ key, sep, value = line.partition(":")
24273
+ if sep:
24274
+ received.append((key.strip(), value.strip()))
24275
+ content = dict((k.lower(), v) for k, v in received)
24276
+ if "content-length" in content:
24277
+ data = await reader.readexactly(int(content["content-length"]))
24278
+ else:
24279
+ data = await reader.read()
24280
+ if content.get("x-sbx-error"):
24281
+ raise OSError(data.decode("utf-8", "replace").strip())
24282
+ return int(code), reason, received, data
24283
+ finally:
24284
+ writer.close()
24285
+
24286
+ class _SbxUrllib3Hook:
24287
+ """urllib3's emscripten backend is Pyodide's \`js\` fetch, which this
24288
+ runtime has no module for; its socket backend cannot leave the
24289
+ container. Both connection classes become egress ones."""
24290
+
24291
+ def find_spec(self, name, path=None, target=None):
24292
+ if name != "urllib3.contrib.emscripten":
24293
+ return None
24294
+ import importlib.util
24295
+
24296
+ def inject_into_urllib3():
24297
+ import urllib3.connection
24298
+ from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool
24299
+
24300
+ class _Plain(urllib3.connection.HTTPConnection):
24301
+ """urllib3's own connection, dialled at the egress.
24302
+
24303
+ urllib3.connection.HTTPConnection still derives from
24304
+ http.client.HTTPConnection, so request building is
24305
+ inherited; only where it connects and what goes in the
24306
+ request line change.
24307
+ """
24308
+
24309
+ _sbx_scheme = "http"
24310
+ default_port = 80
24311
+ is_verified = True
24312
+ proxy_is_verified = None
24313
+
24314
+ def __init__(self, host, port=None, **kwargs):
24315
+ # A distinct name from the http.client shim's
24316
+ # _sbx_origin: that class is further up this MRO and
24317
+ # its __init__ runs after this one, so sharing the name
24318
+ # overwrote the real destination with the egress and
24319
+ # every request went out addressed to the egress itself.
24320
+ self._sbx_target = (host, port or self.default_port)
24321
+ for unsupported in ("ssl_context", "ssl_version", "ssl_minimum_version",
24322
+ "ssl_maximum_version", "assert_hostname",
24323
+ "assert_fingerprint", "server_hostname", "ca_certs",
24324
+ "ca_cert_dir", "ca_cert_data", "cert_file", "key_file",
24325
+ "key_password", "cert_reqs"):
24326
+ kwargs.pop(unsupported, None)
24327
+ super().__init__(_egress_host, port=_egress_port, **kwargs)
24328
+
24329
+ def _sbx_absolute(self, url):
24330
+ host, port = self._sbx_target
24331
+ if url.startswith("http://") or url.startswith("https://"):
24332
+ return url
24333
+ netloc = host if port == self.default_port else f"{host}:{port}"
24334
+ return f"{self._sbx_scheme}://{netloc}{url}"
24335
+
24336
+ # urllib3 v2 writes the request line in its own request()
24337
+ # rather than through http.client's putrequest, so both
24338
+ # are absolutised; the egress refuses an origin-form target.
24339
+ def request(self, method, url, *args, **kwargs):
24340
+ return super().request(method, self._sbx_absolute(url), *args, **kwargs)
24341
+
24342
+ def putrequest(self, method, url, *args, **kwargs):
24343
+ return super().putrequest(method, self._sbx_absolute(url), *args, **kwargs)
24344
+
24345
+ def set_tunnel(self, *args, **kwargs):
24346
+ raise OSError("this container reaches the network through its egress, not CONNECT")
24347
+
24348
+ class _Tls(_Plain):
24349
+ _sbx_scheme = "https"
24350
+ default_port = 443
24351
+
24352
+ HTTPConnectionPool.ConnectionCls = _Plain
24353
+ HTTPSConnectionPool.ConnectionCls = _Tls
24354
+ urllib3.connection.HTTPConnection = _Plain
24355
+ urllib3.connection.HTTPSConnection = _Tls
24356
+
24357
+ class _Loader:
24358
+ def create_module(self, spec):
24359
+ return None
24360
+
24361
+ def exec_module(self, module):
24362
+ module.inject_into_urllib3 = inject_into_urllib3
24363
+
24364
+ return importlib.util.spec_from_loader(name, _Loader(), is_package=True)
24365
+
24366
+ sys.meta_path.insert(0, _SbxUrllib3Hook())
24367
+
24368
+ def _sbx_httpx_transports(httpx_module):
24369
+ """Sync and async httpx transports that send through the egress."""
24370
+
24371
+ class _Transport(httpx_module.BaseTransport):
24372
+ def __init__(self, *args, **kwargs):
24373
+ pass
24374
+
24375
+ def handle_request(self, request):
24376
+ body = request.read()
24377
+ sent = [(k.decode("latin-1"), v.decode("latin-1")) for k, v in request.headers.raw]
24378
+ status, reason, headers, payload = _sbx_egress_request(
24379
+ request.method, str(request.url), sent, body,
24380
+ )
24381
+ # Content-Length/Transfer-Encoding describe the egress hop's
24382
+ # framing, and httpx re-derives them from the body it is given.
24383
+ keep = [(k, v) for k, v in headers
24384
+ if k.lower() not in ("content-length", "transfer-encoding", "content-encoding")]
24385
+ return httpx_module.Response(
24386
+ status, headers=keep, content=payload, request=request,
24387
+ extensions={"reason_phrase": (reason or "").encode()},
24388
+ )
24389
+
24390
+ def close(self):
24391
+ pass
24392
+
24393
+ def __enter__(self):
24394
+ return self
24395
+
24396
+ def __exit__(self, *args):
24397
+ pass
24398
+
24399
+ class _AsyncTransport(httpx_module.AsyncBaseTransport):
24400
+ def __init__(self, *args, **kwargs):
24401
+ pass
24402
+
24403
+ async def handle_async_request(self, request):
24404
+ # Not the blocking client on the loop thread: this image
24405
+ # proxies every syscall to one thread, so a blocking recv
24406
+ # inside a coroutine stops the loop that must service it. The
24407
+ # egress is loopback, which asyncio streams reach.
24408
+ import asyncio
24409
+ body = await request.aread()
24410
+ sent = [(k.decode("latin-1"), v.decode("latin-1")) for k, v in request.headers.raw]
24411
+ status, reason, headers, payload = await _sbx_egress_request_async(
24412
+ request.method, str(request.url), sent, body,
24413
+ )
24414
+ keep = [(k, v) for k, v in headers
24415
+ if k.lower() not in ("content-length", "transfer-encoding", "content-encoding")]
24416
+ return httpx_module.Response(
24417
+ status, headers=keep, content=payload, request=request,
24418
+ extensions={"reason_phrase": (reason or "").encode()},
24419
+ )
24420
+
24421
+ async def aclose(self):
24422
+ pass
24423
+
24424
+ async def __aenter__(self):
24425
+ return self
24426
+
24427
+ async def __aexit__(self, *args):
24428
+ pass
24429
+
24430
+ return _Transport, _AsyncTransport
24431
+
24432
+ class _SbxHttpxHook:
24433
+ """httpx and httpx2 build their default transports on httpcore, which
24434
+ opens sockets. Their default transport classes are replaced as the
24435
+ module loads, so \`httpx.get(...)\` and every client built from the
24436
+ defaults leave through the egress."""
24437
+
24438
+ _PACKAGES = {"httpx": "httpx", "httpx2": "httpx2"}
24439
+
24440
+ def find_spec(self, name, path=None, target=None):
24441
+ package = self._PACKAGES.get(name)
24442
+ if package is None or getattr(self, "_loading", None) == name:
24443
+ return None
24444
+ import importlib.machinery
24445
+ spec = importlib.machinery.PathFinder.find_spec(name, path)
24446
+ if spec is None or spec.loader is None:
24447
+ return spec
24448
+ load = spec.loader.exec_module
24449
+
24450
+ def exec_module(module):
24451
+ self._loading = name
24452
+ try:
24453
+ load(module)
24454
+ finally:
24455
+ self._loading = None
24456
+ transport, async_transport = _sbx_httpx_transports(module)
24457
+ module.HTTPTransport = transport
24458
+ module.AsyncHTTPTransport = async_transport
24459
+ defaults = sys.modules.get(f"{package}._transports.default")
24460
+ if defaults is not None:
24461
+ defaults.HTTPTransport = transport
24462
+ defaults.AsyncHTTPTransport = async_transport
24463
+ client = sys.modules.get(f"{package}._client")
24464
+ if client is not None:
24465
+ client.HTTPTransport = transport
24466
+ client.AsyncHTTPTransport = async_transport
24467
+
24468
+ spec.loader.exec_module = exec_module
24469
+ return spec
24470
+
24471
+ sys.meta_path.insert(0, _SbxHttpxHook())
24472
+
23919
24473
 
23920
24474
  if os.environ.get("SBX_SERIAL_HOST_CALLS") == "1":
23921
24475
  import asyncio
@@ -23944,6 +24498,47 @@ if os.environ.get("SBX_SERIAL_HOST_CALLS") == "1":
23944
24498
  )
23945
24499
 
23946
24500
  asyncio.to_thread = _to_thread
24501
+
24502
+ class _SbxAnyioThreadHook:
24503
+ """anyio's thread offload, run inline.
24504
+
24505
+ anyio has its own worker threads, so it never reaches asyncio.to_thread
24506
+ and the refusal above -- it deadlocks instead, for the reason described
24507
+ there. Starlette sends every *synchronous* endpoint through
24508
+ anyio.to_thread.run_sync, so a plain def FastAPI route hung forever
24509
+ while an async def one answered.
24510
+
24511
+ Running the function on the loop thread keeps such an endpoint working.
24512
+ The cost is real and worth stating: a synchronous handler blocks the
24513
+ loop for its duration, so it does not overlap with other requests.
24514
+ """
24515
+
24516
+ def find_spec(self, name, path=None, target=None):
24517
+ if name != "anyio.to_thread":
24518
+ return None
24519
+ import importlib.machinery
24520
+ spec = importlib.machinery.PathFinder.find_spec(name, path)
24521
+ if spec is None or spec.loader is None:
24522
+ return spec
24523
+ load = spec.loader.exec_module
24524
+
24525
+ def exec_module(module):
24526
+ load(module)
24527
+
24528
+ async def run_sync(func, *args, abandon_on_cancel=False,
24529
+ cancellable=None, limiter=None):
24530
+ return func(*args)
24531
+
24532
+ async def run_sync_in_worker_thread(func, *args, **kwargs):
24533
+ return func(*args)
24534
+
24535
+ module.run_sync = run_sync
24536
+ module.run_sync_in_worker_thread = run_sync_in_worker_thread
24537
+
24538
+ spec.loader.exec_module = exec_module
24539
+ return spec
24540
+
24541
+ sys.meta_path.insert(0, _SbxAnyioThreadHook())
23947
24542
  `;
23948
24543
  function ensureProcessShims(ctx) {
23949
24544
  const hook = `${SITE_PACKAGES}/sitecustomize.py`;