Skip to content

Fix WEB proxy sending from the iOS Share Extension - #2392

Open
niggin wants to merge 1 commit into
TelegramMessenger:masterfrom
niggin:fix/web-proxy-share-extension
Open

niggin wants to merge 1 commit into
TelegramMessenger:masterfrom
niggin:fix/web-proxy-share-extension

Conversation

@niggin

@niggin niggin commented Oct 24, 2026 •

Copy link
Copy Markdown

Fix WEB proxy sending from the iOS Share Extension

Fixes #2380

Problem and linked reports

With an active WEB proxy, sharing a Safari URL, photo or file reaches the recipient picker but stalls after Send. In-app sending works.

  • Original report: #2380 — WEB Proxy prevents sending files from the iOS Share Extension.
  • Independent confirmation: server-side observations in #2380: a ~19 MB video sends in-app, but Share remains at 0% with no new WEB session, upload request or TLS connection observed at the proxy. The same report says Share works with MTProto/FakeTLS. These are the reporter's observations, separate from the device validation below.
  • Our baseline reproduction: an unmodified, separately signed build at fb0e62ba8a147f0821da6aa63c09da7d79a10111 reproduced Safari → Share → recipient → Send → indefinite waiting on the same physical iPhone/proxy later used to validate the fix.

Root cause: three missing pieces

The source links below are pinned to the tested base commit rather than moving master.

  1. Configuration is discarded while the WEB factory is still selected. Initial setup in Network.swift, lines 601–609 applies nil for every .appex, then routes WEB connections to WebProxyTransport. Proxy-setting updates repeat the mismatch.
  2. Carrier demand is also suppressed. The demand subscription forces demand off in extensions even when WEB is active and the account needs a connection. Changing configuration alone therefore does not enable the carrier.
  3. Share needs its own view host. The main app already embeds the carrier WKWebView in its root hierarchy; its comment documents off-screen WebKit throttling that can stall long polling. The extension is a separate process and cannot reuse the app's window host. Removing the .appex guards alone does not provide a Share UI host or a bounded lifecycle.

This is a client initialization/lifecycle fix, not a proxy-server timeout or upload-size change. It keeps the existing WEB transport instead of adding a silent direct-TCP fallback.

Changes

  • Allow WEB carrier configuration and demand for com.apple.share-services only; keep notification, widget, Intents and unknown extensions disabled.
  • Host the existing hardened carrier WKWebView in the Share root hierarchy using the existing WebProxyCarrierViewHost protocol. It is noninteractive and hidden from accessibility, with the same 1pt/0.01-alpha approach as the main app.
  • Require a view host for extension carrier activation. Detaching it stops the carrier and streams and cancels retries, even while an account retains a demand lease.
  • Preserve main-app bootstrapping before a window is available.
  • Add four process-policy regression tests, picked up by the existing test-source glob.

No direct-connection fallback, new proxy protocol, WebKit hardening changes, background mode, account migration or user-facing setting is introduced. Uploads are not promised to continue after dismissing Share.

Lifecycle and review points

  • Process policy opts in only com.apple.share-services. Other extension points, including unknown/missing identifiers, remain disabled; the main app can still bootstrap before a view host exists.
  • ShareRootController retains and attaches the host on appearance, then detaches it on disappearance/deinitialization. The hosted view uses the existing noninteractive, accessibility-hidden 1pt / 0.01-alpha approach.
  • Transport activation requires a host in the extension as well as configuration and demand. Detaching the host stops the carrier even if an account still retains its demand lease. Existing stop logic cancels retries and closes streams; retry/start guards use the same activation policy.
  • No new background-upload guarantee or error UI is added. Dismissing Share ends this carrier lifecycle; the patch does not promise continued sending after dismissal.

Reproduction

  1. Configure and enable a working WEB proxy; verify in-app sending works.
  2. In Safari, open a URL and choose Share → Telegram.
  3. Select Saved Messages and tap Send.
  4. Before: sending stalls. After: the extension finishes and the URL is delivered.
  5. Repeat from Photos and Files, including multiple attachments and cancellation/retry.

Validation

Tested against fb0e62ba8a147f0821da6aa63c09da7d79a10111 (13.1.1), which still matches upstream master as checked on 2026-10-24. The isolated development build was fully built, signed, installed and launched; delivery was checked separately on iPhone 15 Pro Max / iOS 27.0 (24A5370h), Xcode 27.1 (27A9275), macOS 26.6.2.

12 targeted scenarios passed: Safari URL; one photo; two photos with the main app terminated; 25 MiB document; mixed 16 KiB + 25 MiB documents; cancel before Send; cancel during upload; retry; downloaded-document integrity; proxy-off control; MTProto control; WEB over Network Link Conditioner 3G.

The received 26,214,400-byte document was freshly downloaded and exported; its size and SHA-256 matched the original. Relay counters rose by 33,095,257 uploaded bytes / 7 streams during the controlled large-upload window, with no new rejects, dial failures or limit hits. These are aggregate server counters, not a per-process packet capture.

Observed Share Extension physical-footprint peaks: ~57 MiB for two photos and ~72 MiB for the file pair. WebKit child processes are separate and not included. No Share Extension crash reports were found in the available inventory; this is not a long-running leak or memory-limit proof.

Host-only Swift/XCTest: 4 new process-policy tests + 6 existing demand-lease tests, 10 passed, 0 failures. Patch whitespace check passed. These host tests do not exercise real WebKit, transport teardown or UIKit lifecycle; device scenarios cover the tested paths.

Not run: full application regression, full iOS/WebKit unit suite (no iOS simulator runtime installed), long-running soak, actual cellular network. Cellular testing is explicitly outside the agreed scope; NLC 3G is a slow-link simulation, not a cellular-radio test.

Reproduce the host-only tests

Requires macOS and a full Xcode installation. Check out this PR's commit (93e3286bdac82330fb6f927b0e23ae91dbdffd60), save the script below as run-host-tests.sh outside the source checkout, and run:

bash /path/to/run-host-tests.sh /path/to/Telegram-iOS

If Xcode is installed somewhere else, set DEVELOPER_DIR to its Contents/Developer directory when invoking the command.

Expected result: PASS: 10 policy/lease tests; not a WebKit or device test and exit status 0. This compiles only the Foundation-based process policy and demand-set sources plus their XCTest cases. It does not rebuild Telegram, require signing or an iOS simulator, or exercise real WebKit, UIKit lifecycle or delivery. Temporary binaries and results.log are kept in the printed /var/tmp/webproxy-host-tests.* directory.

Standalone macOS test script (the exact host-only runner used for the 10/10 result)
#!/bin/bash
set -euo pipefail
# Standalone macOS subset, not the Bazel/WebKit suite. Requires Xcode.
source_root="${1:?Usage: run-host-tests.sh /path/to/Telegram-iOS}"
test_scratch="$(mktemp -d /var/tmp/webproxy-host-tests.XXXXXX)"
export DEVELOPER_DIR="${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}"
mac_sdk="$(xcrun --sdk macosx --show-sdk-path)"
xctest_frameworks="$DEVELOPER_DIR/Platforms/MacOSX.platform/Developer/Library/Frameworks"
xctest_libraries="$DEVELOPER_DIR/Platforms/MacOSX.platform/Developer/usr/lib"
xcrun swiftc -sdk "$mac_sdk" -enable-testing -emit-library -emit-module \
  -module-name WebProxyTransport \
  "$source_root/submodules/WebProxyTransport/Sources/WebProxyProcessPolicy.swift" \
  "$source_root/submodules/WebProxyTransport/Sources/WebProxyDemandSet.swift" \
  -emit-module-path "$test_scratch/WebProxyTransport.swiftmodule" \
  -o "$test_scratch/libWebProxyTransport.dylib"
cat > "$test_scratch/main.swift" <<'SWIFT'
import XCTest
import Darwin
let suite = XCTestSuite(name: "WEB carrier policy and leases (host-only)")
suite.addTest(XCTestSuite(forTestCaseClass: WebProxyProcessPolicyTests.self))
suite.addTest(XCTestSuite(forTestCaseClass: WebProxyDemandSetTests.self))
suite.run()
guard let result = suite.testRun, result.executionCount == 10, result.totalFailureCount == 0 else { exit(1) }
print("PASS: \(result.executionCount) policy/lease tests; not a WebKit or device test")
SWIFT
xcrun swiftc -sdk "$mac_sdk" -F "$xctest_frameworks" \
  -Xlinker -rpath -Xlinker "$xctest_frameworks" \
  -I "$xctest_libraries" -L "$xctest_libraries" \
  -Xlinker -rpath -Xlinker "$xctest_libraries" \
  -I "$test_scratch" -L "$test_scratch" \
  -lWebProxyTransport -Xlinker -rpath -Xlinker "$test_scratch" \
  "$source_root/submodules/WebProxyTransport/Tests/WebProxyProcessPolicyTests.swift" \
  "$source_root/submodules/WebProxyTransport/Tests/WebProxyDemandSetTests.swift" \
  "$test_scratch/main.swift" -o "$test_scratch/run-tests"
"$test_scratch/run-tests" | tee "$test_scratch/results.log"
printf 'Host-only artifacts: %s\n' "$test_scratch"

The new test file is also included automatically by the existing Tests/**/*.swift glob in //submodules/WebProxyTransport:WebProxyTransportTests. That full iOS/WebKit suite is distinct from this host-only run and was not run for this validation.

Only synthetic attachments and the test account's Saved Messages were used. Lab naming/signing/API credentials, local build workarounds, private screenshots, chat data and proxy credentials are not part of this patch.

The Share Extension selected the WEB connection factory while suppressing
its configuration and carrier demand, leaving sends waiting indefinitely.
Allow the share-services extension to run the existing hardened carrier
and provide a view host tied to its visible root controller lifecycle.

Keep headless extensions disabled and main-app bootstrap behavior intact.
Require the Share host for activation and retries; do not add a direct
fallback or background-upload behavior. Cover the process activation policy.

Fixes TelegramMessenger#2380
@CLAassistant

CLAassistant commented Oct 24, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants