Repository navigation
Expand file tree
/
Copy pathRemoteLoopbackRuntimeBridge.swift
More file actions
141 lines (131 loc) · 5.85 KB
/
Copy pathRemoteLoopbackRuntimeBridge.swift
File metadata and controls
141 lines (131 loc) · 5.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import Foundation
enum RemoteLoopbackRuntimeBridge {
static let runtimeBridgeScriptSource: String = {
let exactLoopbackHostLiterals = RemoteLoopbackProxyAlias.exactLoopbackHosts
.sorted()
.map(javaScriptStringLiteral)
.joined(separator: ", ")
return """
(() => {
const aliasHost = \(javaScriptStringLiteral(RemoteLoopbackProxyAlias.aliasHost));
const canonicalLoopbackHost = \(javaScriptStringLiteral(RemoteLoopbackProxyAlias.canonicalLoopbackHost));
const exactLoopbackHosts = new Set([\(exactLoopbackHostLiterals)]);
const normalizeHost = (host) => {
let value = String(host || '').trim().toLowerCase();
if (!value) return '';
if (value.endsWith('.')) value = value.slice(0, -1);
if (value.startsWith('[') && value.endsWith(']')) {
value = value.slice(1, -1);
}
return value;
};
const normalizedAliasHost = normalizeHost(aliasHost);
const currentHost = normalizeHost(window.location.hostname);
let effectiveHost = currentHost;
if (!effectiveHost && window.location.protocol === 'about:') {
try {
effectiveHost = normalizeHost(new URL(document.baseURI).hostname);
} catch (_) {}
}
if (effectiveHost !== normalizedAliasHost && !effectiveHost.endsWith(`.${normalizedAliasHost}`)) {
return true;
}
if (window.__cmuxRemoteLoopbackRuntimeBridgeInstalled) return true;
window.__cmuxRemoteLoopbackRuntimeBridgeInstalled = true;
const loopbackAliasHost = (host) => {
const normalizedHost = normalizeHost(host);
if (exactLoopbackHosts.has(normalizedHost)) {
return aliasHost;
}
const suffix = `.${canonicalLoopbackHost}`;
if (normalizedHost.endsWith(suffix) && normalizedHost.length > suffix.length) {
return `${normalizedHost.slice(0, -suffix.length)}.${aliasHost}`;
}
return null;
};
const rewriteLoopbackURL = (input) => {
if (typeof input !== 'string' && !(input instanceof URL)) {
return input;
}
const original = input instanceof URL ? input.href : input;
let parsed;
try {
parsed = new URL(original, document.baseURI);
} catch {
return input;
}
// Only rewrite cleartext HTTP/WebSocket requests. TLS-bearing `https:` and
// `wss:` validate certificates against the URL hostname, so aliasing them
// would change SNI/certificate expectations for localhost dev servers.
if (parsed.protocol !== 'http:' && parsed.protocol !== 'ws:') {
return input;
}
const rewrittenHost = loopbackAliasHost(parsed.hostname);
if (!rewrittenHost) {
return input;
}
parsed.hostname = rewrittenHost;
return parsed.href;
};
Object.defineProperty(window, '__cmuxRewriteRemoteLoopbackURL', {
value: rewriteLoopbackURL,
configurable: true,
});
const nativeFetch = window.fetch ? window.fetch.bind(window) : null;
if (nativeFetch) {
window.fetch = (input, init) => {
if (typeof Request !== 'undefined' && input instanceof Request) {
const rewrittenURL = rewriteLoopbackURL(input.url);
if (rewrittenURL !== input.url) {
return nativeFetch(new Request(rewrittenURL, input), init);
}
return nativeFetch(input, init);
}
return nativeFetch(rewriteLoopbackURL(input), init);
};
}
const nativeXHROpen = window.XMLHttpRequest && window.XMLHttpRequest.prototype.open;
if (nativeXHROpen) {
window.XMLHttpRequest.prototype.open = function(method, url, ...rest) {
return nativeXHROpen.call(this, method, rewriteLoopbackURL(url), ...rest);
};
}
const NativeWebSocket = window.WebSocket;
if (typeof NativeWebSocket === 'function') {
const CmuxWebSocket = function(url, protocols) {
const rewrittenURL = rewriteLoopbackURL(url);
if (protocols === undefined) {
return new NativeWebSocket(rewrittenURL);
}
return new NativeWebSocket(rewrittenURL, protocols);
};
CmuxWebSocket.prototype = NativeWebSocket.prototype;
Object.setPrototypeOf(CmuxWebSocket, NativeWebSocket);
window.WebSocket = CmuxWebSocket;
}
const NativeEventSource = window.EventSource;
if (typeof NativeEventSource === 'function') {
const CmuxEventSource = function(url, eventSourceInitDict) {
const rewrittenURL = rewriteLoopbackURL(url);
if (eventSourceInitDict === undefined) {
return new NativeEventSource(rewrittenURL);
}
return new NativeEventSource(rewrittenURL, eventSourceInitDict);
};
CmuxEventSource.prototype = NativeEventSource.prototype;
Object.setPrototypeOf(CmuxEventSource, NativeEventSource);
window.EventSource = CmuxEventSource;
}
return true;
})();
"""
}()
private static func javaScriptStringLiteral(_ value: String) -> String {
let escaped = value
.replacingOccurrences(of: "\\", with: "\\\\")
.replacingOccurrences(of: "'", with: "\\'")
.replacingOccurrences(of: "\n", with: "\\n")
.replacingOccurrences(of: "\r", with: "\\r")
return "'\(escaped)'"
}
}