ShadowHunter222 commited on
Commit
3d1cfb6
·
verified ·
1 Parent(s): 014fa5e

Upload native_features.py

Browse files
Files changed (1) hide show
  1. native_features.py +131 -16
native_features.py CHANGED
@@ -1091,13 +1091,30 @@ _NATIVE_BRIDGE_JS = r"""// capacitor-native-bridge.js — Auto-injected by APK C
1091
  // Starts progress UI immediately and streams network chunks in real time!
1092
  // ═══════════════════════════════════════════════
1093
  var origFetch = window.fetch;
 
 
 
 
1094
  window.fetch = async function(resource, init) {
 
 
 
 
 
 
 
 
1095
  var url = '';
1096
  if (typeof resource === 'string') {
1097
  url = resource;
1098
  } else if (resource && resource.url) {
1099
  url = resource.url;
1100
  }
 
 
 
 
 
1101
 
1102
  var isDownloadCandidate = false;
1103
  var now = Date.now();
@@ -1252,7 +1269,9 @@ _NATIVE_BRIDGE_JS = r"""// capacitor-native-bridge.js — Auto-injected by APK C
1252
  if (!blob && tf.localPath && window.Capacitor && window.Capacitor.convertFileSrc) {
1253
  try {
1254
  var webUrl = window.Capacitor.convertFileSrc(tf.localPath);
1255
- var resp = await fetch(webUrl);
 
 
1256
  if (resp.ok) {
1257
  blob = await resp.blob();
1258
  }
@@ -1520,9 +1539,23 @@ _NATIVE_BRIDGE_JS = r"""// capacitor-native-bridge.js — Auto-injected by APK C
1520
  // ═══════════════════════════════════════════════
1521
  // 2. ROBUST CHUNKED DOWNLOAD & SAVE TO DEVICE
1522
  // Intercepts <a download> and blob:/data: URLs.
1523
- // Streams in 512KB chunks to MediaStore.Downloads.
1524
- // Zero-OOM, auto-renames duplicates, and shows real-time progress UI!
 
 
 
 
1525
  // ═══════════════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
1526
  async function saveDownloadToDevice(url, suggestedFilename) {
1527
  console.log('[NativeBridge] Intercepted download:', suggestedFilename, (url || '').slice(0, 50));
1528
  var filename = (suggestedFilename || '').trim();
@@ -1549,28 +1582,110 @@ _NATIVE_BRIDGE_JS = r"""// capacitor-native-bridge.js — Auto-injected by APK C
1549
 
1550
  try {
1551
  // 2. Fetch if not in blobStore (handles data: or remote http/https)
 
 
 
 
1552
  if (!blob) {
1553
- var resp = await fetch(url);
 
 
 
1554
  if (!resp.ok) throw new Error('Fetch failed with status ' + resp.status);
1555
  var totalBytes = parseInt(resp.headers.get('content-length') || '0', 10);
1556
- var mimeType = resp.headers.get('content-type') || 'application/octet-stream';
1557
  ui.update(0, totalBytes);
1558
 
1559
  if (resp.body && typeof resp.body.getReader === 'function') {
1560
- var reader = resp.body.getReader();
1561
- var netChunks = [];
1562
- var netLoaded = 0;
1563
- while (true) {
1564
- var nr = await reader.read();
1565
- if (nr.done) break;
1566
- if (nr.value) {
1567
- netChunks.push(nr.value);
1568
- netLoaded += nr.value.length;
1569
- ui.update(netLoaded, totalBytes || netLoaded);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1570
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1571
  }
1572
- blob = new Blob(netChunks, { type: mimeType });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1573
  } else {
 
 
1574
  blob = await resp.blob();
1575
  ui.update(blob.size, blob.size);
1576
  }
 
1091
  // Starts progress UI immediately and streams network chunks in real time!
1092
  // ═══════════════════════════════════════════════
1093
  var origFetch = window.fetch;
1094
+ // Bridge-internal bypass: saveDownloadToDevice / nativeEntryToFile must use
1095
+ // origFetch directly so their network reads never re-enter this wrapper
1096
+ // (prevents double DownloadUI cards). Exposed for the onCapReady closure.
1097
+ window.__nativeOrigFetch = origFetch;
1098
  window.fetch = async function(resource, init) {
1099
+ // 0.3.0 INTERNAL BYPASS — never treat the bridge's own reads as downloads.
1100
+ if (init && init.__nativeBridgeInternal) {
1101
+ var cleanInit = {};
1102
+ for (var _k in init) {
1103
+ if (Object.prototype.hasOwnProperty.call(init, _k) && _k !== '__nativeBridgeInternal') cleanInit[_k] = init[_k];
1104
+ }
1105
+ return origFetch.call(this, resource, cleanInit);
1106
+ }
1107
  var url = '';
1108
  if (typeof resource === 'string') {
1109
  url = resource;
1110
  } else if (resource && resource.url) {
1111
  url = resource.url;
1112
  }
1113
+ // Internal Capacitor cache reads are never user downloads — skip wrapper entirely.
1114
+ // (convertFileSrc() URLs look like http://localhost/_capacitor_file_/...)
1115
+ if (url && (url.indexOf('/_capacitor_file_') !== -1 || url.indexOf('capacitor://') === 0)) {
1116
+ return origFetch.apply(this, arguments);
1117
+ }
1118
 
1119
  var isDownloadCandidate = false;
1120
  var now = Date.now();
 
1269
  if (!blob && tf.localPath && window.Capacitor && window.Capacitor.convertFileSrc) {
1270
  try {
1271
  var webUrl = window.Capacitor.convertFileSrc(tf.localPath);
1272
+ // Bridge-internal read: bypass download-detection wrapper (no spurious DownloadUI).
1273
+ var _of = window.__nativeOrigFetch || origFetch || window.fetch;
1274
+ var resp = await _of.call(window, webUrl);
1275
  if (resp.ok) {
1276
  blob = await resp.blob();
1277
  }
 
1539
  // ═══════════════════════════════════════════════
1540
  // 2. ROBUST CHUNKED DOWNLOAD & SAVE TO DEVICE
1541
  // Intercepts <a download> and blob:/data: URLs.
1542
+ // PUMP-AND-FORWARD: network bytes stream straight to native in
1543
+ // 512KB stages (never materialize a full Blob for http(s)).
1544
+ // Binder limit 1MB: 512KB raw -> ~683KB base64 + JSON < 800KB, safe.
1545
+ // Chunks are awaited sequentially (Java has no seq num, appends in
1546
+ // arrival order — parallel sends would corrupt file order).
1547
+ // Auto-renames duplicates, real-time progress UI, single card per download.
1548
  // ═══════════════════════════════════════════════
1549
+ // Binder-safe Uint8Array -> base64 without Blob/FileReader.
1550
+ // Chunked fromCharCode avoids arg-stack overflow on 512KB arrays.
1551
+ function uint8ToBase64(u8) {
1552
+ var s = '';
1553
+ var CH = 0x8000;
1554
+ for (var i = 0; i < u8.length; i += CH) {
1555
+ s += String.fromCharCode.apply(null, u8.subarray(i, i + CH));
1556
+ }
1557
+ return btoa(s);
1558
+ }
1559
  async function saveDownloadToDevice(url, suggestedFilename) {
1560
  console.log('[NativeBridge] Intercepted download:', suggestedFilename, (url || '').slice(0, 50));
1561
  var filename = (suggestedFilename || '').trim();
 
1582
 
1583
  try {
1584
  // 2. Fetch if not in blobStore (handles data: or remote http/https)
1585
+ // PUMP-AND-FORWARD: http(s) bytes go network -> native directly in
1586
+ // 512KB stages. Max JS accumulation = 512KB (plus one net read,
1587
+ // typically 16-64KB). Never builds a full Blob for remote URLs,
1588
+ // so a 30/100MB file peaks <1MB in JS instead of 2x file size.
1589
  if (!blob) {
1590
+ // Bridge-internal fetch: bypasses the window.fetch download wrapper
1591
+ // above, so this read creates NO second DownloadUI card.
1592
+ var _of2 = window.__nativeOrigFetch || origFetch || window.fetch;
1593
+ var resp = await _of2.call(window, url);
1594
  if (!resp.ok) throw new Error('Fetch failed with status ' + resp.status);
1595
  var totalBytes = parseInt(resp.headers.get('content-length') || '0', 10);
1596
+ var streamMime = resp.headers.get('content-type') || 'application/octet-stream';
1597
  ui.update(0, totalBytes);
1598
 
1599
  if (resp.body && typeof resp.body.getReader === 'function') {
1600
+ // Fail-fast on permission before pulling bytes over the network.
1601
+ if (window.Capacitor && window.Capacitor.isNativePlatform && window.Capacitor.isNativePlatform()) {
1602
+ try {
1603
+ var _perm = await callNative('NativeFolderHelper', 'checkStoragePermission', {});
1604
+ if (_perm && _perm.granted === false) {
1605
+ ui.error({ message: 'File was not saved: Storage permission denied' });
1606
+ return null;
1607
+ }
1608
+ } catch (_pe) {}
1609
+ }
1610
+ var streamDownloadId = 'dl_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7);
1611
+ var STAGE_MAX = 512 * 1024; // 512KB raw -> ~683KB b64, well under 1MB binder
1612
+ var staging = [];
1613
+ var stagedLen = 0;
1614
+ var streamedTotal = 0;
1615
+ var streamFirst = true;
1616
+ var streamLastRes = null;
1617
+ var netReader = resp.body.getReader();
1618
+
1619
+ async function flushStage(isLast) {
1620
+ if (stagedLen === 0) {
1621
+ if (!isLast || !streamFirst) return;
1622
+ // Empty file: still create the MediaStore entry.
1623
+ streamLastRes = await callNative('NativeFolderHelper', 'saveFileChunk', {
1624
+ downloadId: streamDownloadId,
1625
+ filename: filename,
1626
+ mimeType: streamMime,
1627
+ chunk: '',
1628
+ isFirst: true,
1629
+ isLast: true,
1630
+ totalSize: totalBytes || 0
1631
+ });
1632
+ streamFirst = false;
1633
+ return;
1634
+ }
1635
+ var combined = new Uint8Array(stagedLen);
1636
+ var _pos = 0;
1637
+ for (var _si = 0; _si < staging.length; _si++) {
1638
+ combined.set(staging[_si], _pos);
1639
+ _pos += staging[_si].length;
1640
  }
1641
+ staging = [];
1642
+ stagedLen = 0;
1643
+ var b64 = uint8ToBase64(combined);
1644
+ // `combined`/ staging refs dropped here so GC can reclaim before next await.
1645
+ streamLastRes = await callNative('NativeFolderHelper', 'saveFileChunk', {
1646
+ downloadId: streamDownloadId,
1647
+ filename: filename,
1648
+ mimeType: streamMime,
1649
+ chunk: b64,
1650
+ isFirst: streamFirst,
1651
+ isLast: !!isLast,
1652
+ totalSize: totalBytes || 0
1653
+ });
1654
+ streamFirst = false;
1655
  }
1656
+
1657
+ try {
1658
+ while (true) {
1659
+ var nr = await netReader.read();
1660
+ if (nr.done) break;
1661
+ if (nr.value) {
1662
+ staging.push(nr.value);
1663
+ stagedLen += nr.value.length;
1664
+ streamedTotal += nr.value.length;
1665
+ ui.update(streamedTotal, totalBytes || streamedTotal);
1666
+ // Flush as soon as we hit 512KB — data passes through immediately,
1667
+ // accumulation never exceeds STAGE_MAX + one net read.
1668
+ if (stagedLen >= STAGE_MAX) {
1669
+ await flushStage(false);
1670
+ }
1671
+ }
1672
+ }
1673
+ await flushStage(true);
1674
+ } catch (streamErr) {
1675
+ // Partial MediaStore entry (IS_PENDING=1) may remain open in Java;
1676
+ // there is currently no abortDownload() to delete it (see Part B).
1677
+ try { if (netReader && netReader.cancel) await netReader.cancel(); } catch (_c) {}
1678
+ throw streamErr;
1679
+ }
1680
+
1681
+ var _sUri = (streamLastRes && streamLastRes.uri) || '';
1682
+ var _sName = (streamLastRes && streamLastRes.filename) || filename;
1683
+ var _sFolder = (streamLastRes && streamLastRes.folder) || 'Downloads';
1684
+ ui.success(_sName, _sFolder, _sUri);
1685
+ return streamLastRes;
1686
  } else {
1687
+ // No ReadableStream (blob:/data: in old WebViews): must materialize.
1688
+ // Uses bypassed fetch's raw .blob() so no second DownloadUI card.
1689
  blob = await resp.blob();
1690
  ui.update(blob.size, blob.size);
1691
  }