@limrun/api 0.48.3 → 0.49.0

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.
Files changed (184) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/client.d.mts +6 -3
  3. package/client.d.mts.map +1 -1
  4. package/client.d.ts +6 -3
  5. package/client.d.ts.map +1 -1
  6. package/client.js +4 -4
  7. package/client.js.map +1 -1
  8. package/client.mjs +2 -2
  9. package/client.mjs.map +1 -1
  10. package/destination-tunnel-dialer.d.mts +30 -2
  11. package/destination-tunnel-dialer.d.mts.map +1 -1
  12. package/destination-tunnel-dialer.d.ts +30 -2
  13. package/destination-tunnel-dialer.d.ts.map +1 -1
  14. package/destination-tunnel-dialer.js +349 -65
  15. package/destination-tunnel-dialer.js.map +1 -1
  16. package/destination-tunnel-dialer.mjs +345 -64
  17. package/destination-tunnel-dialer.mjs.map +1 -1
  18. package/destination-tunnel-har.d.mts +23 -0
  19. package/destination-tunnel-har.d.mts.map +1 -0
  20. package/destination-tunnel-har.d.ts +23 -0
  21. package/destination-tunnel-har.d.ts.map +1 -0
  22. package/destination-tunnel-har.js +164 -0
  23. package/destination-tunnel-har.js.map +1 -0
  24. package/destination-tunnel-har.mjs +160 -0
  25. package/destination-tunnel-har.mjs.map +1 -0
  26. package/destination-tunnel-inspection.d.mts +71 -0
  27. package/destination-tunnel-inspection.d.mts.map +1 -0
  28. package/destination-tunnel-inspection.d.ts +71 -0
  29. package/destination-tunnel-inspection.d.ts.map +1 -0
  30. package/destination-tunnel-inspection.js +267 -0
  31. package/destination-tunnel-inspection.js.map +1 -0
  32. package/destination-tunnel-inspection.mjs +261 -0
  33. package/destination-tunnel-inspection.mjs.map +1 -0
  34. package/destination-tunnel.d.mts +119 -14
  35. package/destination-tunnel.d.mts.map +1 -1
  36. package/destination-tunnel.d.ts +119 -14
  37. package/destination-tunnel.d.ts.map +1 -1
  38. package/destination-tunnel.js +407 -98
  39. package/destination-tunnel.js.map +1 -1
  40. package/destination-tunnel.mjs +374 -73
  41. package/destination-tunnel.mjs.map +1 -1
  42. package/exec-client.d.mts +1 -0
  43. package/exec-client.d.mts.map +1 -1
  44. package/exec-client.d.ts +1 -0
  45. package/exec-client.d.ts.map +1 -1
  46. package/exec-client.js.map +1 -1
  47. package/exec-client.mjs.map +1 -1
  48. package/index.d.mts +6 -0
  49. package/index.d.mts.map +1 -1
  50. package/index.d.ts +6 -0
  51. package/index.d.ts.map +1 -1
  52. package/index.js +23 -2
  53. package/index.js.map +1 -1
  54. package/index.mjs +5 -0
  55. package/index.mjs.map +1 -1
  56. package/instance-client.d.mts +105 -1
  57. package/instance-client.d.mts.map +1 -1
  58. package/instance-client.d.ts +105 -1
  59. package/instance-client.d.ts.map +1 -1
  60. package/instance-client.js +83 -1
  61. package/instance-client.js.map +1 -1
  62. package/instance-client.mjs +83 -1
  63. package/instance-client.mjs.map +1 -1
  64. package/internal/destination-tunnel-management.d.mts +4 -3
  65. package/internal/destination-tunnel-management.d.mts.map +1 -1
  66. package/internal/destination-tunnel-management.d.ts +4 -3
  67. package/internal/destination-tunnel-management.d.ts.map +1 -1
  68. package/internal/destination-tunnel-management.js +64 -46
  69. package/internal/destination-tunnel-management.js.map +1 -1
  70. package/internal/destination-tunnel-management.mjs +55 -37
  71. package/internal/destination-tunnel-management.mjs.map +1 -1
  72. package/internal/destination-tunnel-url.d.mts +2 -0
  73. package/internal/destination-tunnel-url.d.mts.map +1 -1
  74. package/internal/destination-tunnel-url.d.ts +2 -0
  75. package/internal/destination-tunnel-url.d.ts.map +1 -1
  76. package/internal/destination-tunnel-url.js +45 -5
  77. package/internal/destination-tunnel-url.js.map +1 -1
  78. package/internal/destination-tunnel-url.mjs +44 -5
  79. package/internal/destination-tunnel-url.mjs.map +1 -1
  80. package/internal/destination-tunnel-wire-reader.d.mts +23 -0
  81. package/internal/destination-tunnel-wire-reader.d.mts.map +1 -0
  82. package/internal/destination-tunnel-wire-reader.d.ts +23 -0
  83. package/internal/destination-tunnel-wire-reader.d.ts.map +1 -0
  84. package/internal/destination-tunnel-wire-reader.js +110 -0
  85. package/internal/destination-tunnel-wire-reader.js.map +1 -0
  86. package/internal/destination-tunnel-wire-reader.mjs +93 -0
  87. package/internal/destination-tunnel-wire-reader.mjs.map +1 -0
  88. package/internal/persist-option.d.mts +14 -0
  89. package/internal/persist-option.d.mts.map +1 -0
  90. package/internal/persist-option.d.ts +14 -0
  91. package/internal/persist-option.d.ts.map +1 -0
  92. package/internal/persist-option.js +18 -0
  93. package/internal/persist-option.js.map +1 -0
  94. package/internal/persist-option.mjs +15 -0
  95. package/internal/persist-option.mjs.map +1 -0
  96. package/internal/proxy-transport.d.mts +27 -0
  97. package/internal/proxy-transport.d.mts.map +1 -1
  98. package/internal/proxy-transport.d.ts +27 -0
  99. package/internal/proxy-transport.d.ts.map +1 -1
  100. package/internal/proxy-transport.js +206 -0
  101. package/internal/proxy-transport.js.map +1 -1
  102. package/internal/proxy-transport.mjs +205 -0
  103. package/internal/proxy-transport.mjs.map +1 -1
  104. package/internal/session-stream.d.mts +39 -0
  105. package/internal/session-stream.d.mts.map +1 -0
  106. package/internal/session-stream.d.ts +39 -0
  107. package/internal/session-stream.d.ts.map +1 -0
  108. package/internal/session-stream.js +144 -0
  109. package/internal/session-stream.js.map +1 -0
  110. package/internal/session-stream.mjs +139 -0
  111. package/internal/session-stream.mjs.map +1 -0
  112. package/internal/tslib.js +4 -4
  113. package/ios-client.d.mts +61 -4
  114. package/ios-client.d.mts.map +1 -1
  115. package/ios-client.d.ts +61 -4
  116. package/ios-client.d.ts.map +1 -1
  117. package/ios-client.js +48 -2
  118. package/ios-client.js.map +1 -1
  119. package/ios-client.mjs +48 -2
  120. package/ios-client.mjs.map +1 -1
  121. package/package.json +22 -1
  122. package/resources/android-instances-helpers.d.mts +25 -0
  123. package/resources/android-instances-helpers.d.mts.map +1 -0
  124. package/resources/android-instances-helpers.d.ts +25 -0
  125. package/resources/android-instances-helpers.d.ts.map +1 -0
  126. package/resources/android-instances-helpers.js +37 -0
  127. package/resources/android-instances-helpers.js.map +1 -0
  128. package/resources/android-instances-helpers.mjs +33 -0
  129. package/resources/android-instances-helpers.mjs.map +1 -0
  130. package/resources/index.d.mts +5 -2
  131. package/resources/index.d.mts.map +1 -1
  132. package/resources/index.d.ts +5 -2
  133. package/resources/index.d.ts.map +1 -1
  134. package/resources/index.js +5 -5
  135. package/resources/index.js.map +1 -1
  136. package/resources/index.mjs +2 -2
  137. package/resources/index.mjs.map +1 -1
  138. package/resources/ios-instances-helpers.d.mts +25 -0
  139. package/resources/ios-instances-helpers.d.mts.map +1 -0
  140. package/resources/ios-instances-helpers.d.ts +25 -0
  141. package/resources/ios-instances-helpers.d.ts.map +1 -0
  142. package/resources/ios-instances-helpers.js +37 -0
  143. package/resources/ios-instances-helpers.js.map +1 -0
  144. package/resources/ios-instances-helpers.mjs +33 -0
  145. package/resources/ios-instances-helpers.mjs.map +1 -0
  146. package/resources/session-artifacts.d.mts +70 -0
  147. package/resources/session-artifacts.d.mts.map +1 -0
  148. package/resources/session-artifacts.d.ts +70 -0
  149. package/resources/session-artifacts.d.ts.map +1 -0
  150. package/resources/session-artifacts.js +8 -0
  151. package/resources/session-artifacts.js.map +1 -0
  152. package/resources/session-artifacts.mjs +7 -0
  153. package/resources/session-artifacts.mjs.map +1 -0
  154. package/resources/xcode-instances-helpers.d.mts +9 -0
  155. package/resources/xcode-instances-helpers.d.mts.map +1 -1
  156. package/resources/xcode-instances-helpers.d.ts +9 -0
  157. package/resources/xcode-instances-helpers.d.ts.map +1 -1
  158. package/resources/xcode-instances-helpers.js.map +1 -1
  159. package/resources/xcode-instances-helpers.mjs.map +1 -1
  160. package/src/client.ts +4 -2
  161. package/src/destination-tunnel-dialer.ts +403 -64
  162. package/src/destination-tunnel-har.ts +200 -0
  163. package/src/destination-tunnel-inspection.ts +367 -0
  164. package/src/destination-tunnel.ts +510 -88
  165. package/src/exec-client.ts +1 -0
  166. package/src/index.ts +52 -0
  167. package/src/instance-client.ts +271 -5
  168. package/src/internal/destination-tunnel-management.ts +67 -43
  169. package/src/internal/destination-tunnel-url.ts +61 -5
  170. package/src/internal/destination-tunnel-wire-reader.ts +109 -0
  171. package/src/internal/persist-option.ts +26 -0
  172. package/src/internal/proxy-transport.ts +248 -0
  173. package/src/internal/session-stream.ts +157 -0
  174. package/src/ios-client.ts +127 -8
  175. package/src/resources/android-instances-helpers.ts +45 -0
  176. package/src/resources/index.ts +9 -2
  177. package/src/resources/ios-instances-helpers.ts +45 -0
  178. package/src/resources/session-artifacts.ts +95 -0
  179. package/src/resources/xcode-instances-helpers.ts +9 -0
  180. package/src/version.ts +1 -1
  181. package/version.d.mts +1 -1
  182. package/version.d.ts +1 -1
  183. package/version.js +1 -1
  184. package/version.mjs +1 -1
@@ -1,37 +1,59 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DestinationTunnelProtocolError = exports.DestinationTunnelRouteError = exports.DESTINATION_TUNNEL_CONN_ID_HEADER_BYTES = exports.DESTINATION_TUNNEL_MAX_ROUTES = exports.DESTINATION_TUNNEL_VERSION = void 0;
3
+ exports.DestinationTunnelSelectorError = exports.DESTINATION_TUNNEL_MAX_TTL_SECONDS = exports.DESTINATION_TUNNEL_DEFAULT_TTL_SECONDS = exports.DESTINATION_TUNNEL_MAX_BODY_BYTES = exports.DESTINATION_TUNNEL_DEFAULT_MAX_BODY_BYTES = exports.DESTINATION_TUNNEL_MAX_WINDOW_INCREMENT = exports.DESTINATION_TUNNEL_DEFAULT_WINDOW = exports.DESTINATION_TUNNEL_MAX_WINDOW = exports.DESTINATION_TUNNEL_FAKE_RANGE = exports.DESTINATION_TUNNEL_CONN_ID_HEADER_BYTES = exports.DESTINATION_TUNNEL_MAX_DOMAINS = exports.DESTINATION_TUNNEL_MAX_ROUTES = exports.DESTINATION_TUNNEL_VERSION = exports.DestinationTunnelProtocolError = void 0;
4
4
  exports.encodeDestinationTunnelDataFrame = encodeDestinationTunnelDataFrame;
5
5
  exports.decodeDestinationTunnelDataFrame = decodeDestinationTunnelDataFrame;
6
6
  exports.validateDestinationTunnelRoutes = validateDestinationTunnelRoutes;
7
+ exports.validateDestinationTunnelSelectors = validateDestinationTunnelSelectors;
8
+ exports.classifyDestinationTunnelSelectors = classifyDestinationTunnelSelectors;
9
+ exports.validateDestinationTunnelDomains = validateDestinationTunnelDomains;
10
+ exports.destinationTunnelConfigHash = destinationTunnelConfigHash;
11
+ exports.normalizeDestinationTunnelInspection = normalizeDestinationTunnelInspection;
12
+ exports.disabledDestinationTunnelInspection = disabledDestinationTunnelInspection;
13
+ exports.destinationTunnelSelectorIds = destinationTunnelSelectorIds;
14
+ exports.destinationTunnelDomainMatches = destinationTunnelDomainMatches;
7
15
  exports.assertDestinationTunnelOpenAllowed = assertDestinationTunnelOpenAllowed;
8
16
  exports.assertDestinationTunnelReady = assertDestinationTunnelReady;
9
17
  exports.encodeDestinationTunnelClientMessage = encodeDestinationTunnelClientMessage;
10
18
  exports.decodeDestinationTunnelServerMessage = decodeDestinationTunnelServerMessage;
11
19
  const tslib_1 = require("./internal/tslib.js");
20
+ const crypto_1 = tslib_1.__importDefault(require("crypto"));
12
21
  const net_1 = tslib_1.__importDefault(require("net"));
22
+ const url_1 = require("url");
23
+ const destination_tunnel_wire_reader_1 = require("./internal/destination-tunnel-wire-reader.js");
24
+ var destination_tunnel_wire_reader_2 = require("./internal/destination-tunnel-wire-reader.js");
25
+ Object.defineProperty(exports, "DestinationTunnelProtocolError", { enumerable: true, get: function () { return destination_tunnel_wire_reader_2.DestinationTunnelProtocolError; } });
13
26
  exports.DESTINATION_TUNNEL_VERSION = 1;
14
27
  exports.DESTINATION_TUNNEL_MAX_ROUTES = 10;
28
+ exports.DESTINATION_TUNNEL_MAX_DOMAINS = 64;
15
29
  exports.DESTINATION_TUNNEL_CONN_ID_HEADER_BYTES = 4;
16
- class DestinationTunnelRouteError extends Error {
30
+ /**
31
+ * IPv4 range used by the device side for synthetic (fake) DNS answers of
32
+ * matched domain selectors.
33
+ */
34
+ exports.DESTINATION_TUNNEL_FAKE_RANGE = '198.18.0.0/15';
35
+ /** Upper bound for per-flow receive windows advertised in open/openOk. */
36
+ exports.DESTINATION_TUNNEL_MAX_WINDOW = 64 * 1024 * 1024;
37
+ /** Default receive window advertised by generic SDK tunnel clients. */
38
+ exports.DESTINATION_TUNNEL_DEFAULT_WINDOW = 1024 * 1024;
39
+ /** Upper bound for a single windowUpdate increment. */
40
+ exports.DESTINATION_TUNNEL_MAX_WINDOW_INCREMENT = 2147483647;
41
+ exports.DESTINATION_TUNNEL_DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
42
+ exports.DESTINATION_TUNNEL_MAX_BODY_BYTES = 64 * 1024 * 1024;
43
+ exports.DESTINATION_TUNNEL_DEFAULT_TTL_SECONDS = 72 * 60 * 60;
44
+ exports.DESTINATION_TUNNEL_MAX_TTL_SECONDS = 30 * 24 * 60 * 60;
45
+ class DestinationTunnelSelectorError extends Error {
17
46
  constructor(code, message) {
18
47
  super(message);
19
48
  this.code = code;
20
- this.name = 'DestinationTunnelRouteError';
49
+ this.name = 'DestinationTunnelSelectorError';
21
50
  }
22
51
  }
23
- exports.DestinationTunnelRouteError = DestinationTunnelRouteError;
24
- class DestinationTunnelProtocolError extends Error {
25
- constructor(message) {
26
- super(message);
27
- this.name = 'DestinationTunnelProtocolError';
28
- }
29
- }
30
- exports.DestinationTunnelProtocolError = DestinationTunnelProtocolError;
52
+ exports.DestinationTunnelSelectorError = DestinationTunnelSelectorError;
31
53
  function encodeDestinationTunnelDataFrame(connId, payload) {
32
54
  validateConnectionId(connId);
33
55
  if (payload.length === 0) {
34
- throw new DestinationTunnelProtocolError('tunnel data frame must include a payload');
56
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError('tunnel data frame must include a payload');
35
57
  }
36
58
  const frame = Buffer.allocUnsafe(exports.DESTINATION_TUNNEL_CONN_ID_HEADER_BYTES + payload.length);
37
59
  frame.writeUInt32BE(connId, 0);
@@ -40,58 +62,220 @@ function encodeDestinationTunnelDataFrame(connId, payload) {
40
62
  }
41
63
  function decodeDestinationTunnelDataFrame(frame) {
42
64
  if (frame.length <= exports.DESTINATION_TUNNEL_CONN_ID_HEADER_BYTES) {
43
- throw new DestinationTunnelProtocolError('tunnel data frame must include a payload');
65
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError('tunnel data frame must include a payload');
44
66
  }
45
67
  return {
46
68
  connId: frame.readUInt32BE(0),
47
69
  payload: frame.subarray(exports.DESTINATION_TUNNEL_CONN_ID_HEADER_BYTES),
48
70
  };
49
71
  }
50
- function validateDestinationTunnelRoutes(routes) {
72
+ function validateDestinationTunnelRoutes(routes, options = {}) {
51
73
  if (routes.length === 0) {
52
- throw new DestinationTunnelRouteError('empty', 'at least one tunnel route is required');
74
+ throw new DestinationTunnelSelectorError('empty', 'at least one tunnel route is required');
53
75
  }
54
76
  if (routes.length > exports.DESTINATION_TUNNEL_MAX_ROUTES) {
55
- throw new DestinationTunnelRouteError('too_many', `at most ${exports.DESTINATION_TUNNEL_MAX_ROUTES} tunnel routes are allowed`);
77
+ throw new DestinationTunnelSelectorError('too_many', `at most ${exports.DESTINATION_TUNNEL_MAX_ROUTES} tunnel routes are allowed`);
56
78
  }
57
79
  const canonicalRoutes = [];
58
80
  const seen = new Set();
59
81
  for (const route of routes) {
60
- const canonical = canonicalizeDestinationTunnelRoute(route);
82
+ const canonical = canonicalizeDestinationTunnelRoute(route, options.minPort);
61
83
  const key = `${canonical.host}\0${canonical.port}`;
62
84
  if (seen.has(key)) {
63
- throw new DestinationTunnelRouteError('duplicate', `duplicate tunnel route ${canonical.host}:${canonical.port}`);
85
+ throw new DestinationTunnelSelectorError('duplicate', `duplicate tunnel route ${canonical.host}:${canonical.port}`);
64
86
  }
65
87
  seen.add(key);
66
88
  canonicalRoutes.push(canonical);
67
89
  }
68
90
  return canonicalRoutes;
69
91
  }
70
- function assertDestinationTunnelOpenAllowed(message, routes) {
71
- const index = parseRouteId(message.routeId);
72
- const route = routes[index];
73
- if (!route || message.host !== route.host || message.port !== route.port || message.proto !== 'tcp') {
74
- throw new DestinationTunnelProtocolError(`server requested undeclared route ${message.routeId} ${message.host}:${message.port}/${message.proto}`);
92
+ /**
93
+ * Validate and canonicalize a full selector set. Selector policy specific to
94
+ * one product (such as the Android bind-listener minimum route port) is
95
+ * expressed via options rather than separate message shapes.
96
+ */
97
+ function validateDestinationTunnelSelectors(selectors, options = {}) {
98
+ if (!Array.isArray(selectors)) {
99
+ throw new DestinationTunnelSelectorError('invalid_host', 'tunnel selectors must be an array');
100
+ }
101
+ if (selectors.length === 0) {
102
+ throw new DestinationTunnelSelectorError('empty', 'at least one tunnel selector is required');
103
+ }
104
+ const canonical = [];
105
+ const seen = new Set();
106
+ let routeCount = 0;
107
+ let domainCount = 0;
108
+ for (const selector of selectors) {
109
+ const parsed = parseDestinationTunnelSelector(selector, options.minRoutePort);
110
+ if (parsed.route)
111
+ routeCount++;
112
+ else
113
+ domainCount++;
114
+ if (routeCount > exports.DESTINATION_TUNNEL_MAX_ROUTES) {
115
+ throw new DestinationTunnelSelectorError('too_many', `at most ${exports.DESTINATION_TUNNEL_MAX_ROUTES} exact tunnel selectors are allowed`);
116
+ }
117
+ if (domainCount > exports.DESTINATION_TUNNEL_MAX_DOMAINS) {
118
+ throw new DestinationTunnelSelectorError('too_many', `at most ${exports.DESTINATION_TUNNEL_MAX_DOMAINS} domain tunnel selectors are allowed`);
119
+ }
120
+ if (seen.has(parsed.value)) {
121
+ throw new DestinationTunnelSelectorError('duplicate', `duplicate tunnel selector ${parsed.value}`);
122
+ }
123
+ seen.add(parsed.value);
124
+ canonical.push(parsed.value);
125
+ }
126
+ return canonical;
127
+ }
128
+ function classifyDestinationTunnelSelectors(selectors, options = {}) {
129
+ const canonical = validateDestinationTunnelSelectors(selectors, options);
130
+ const routes = [];
131
+ const domains = [];
132
+ for (const selector of canonical) {
133
+ const parsed = parseDestinationTunnelSelector(selector, options.minRoutePort);
134
+ if (parsed.route)
135
+ routes.push(parsed.route);
136
+ else
137
+ domains.push(parsed.domain);
138
+ }
139
+ return {
140
+ ...(routes.length > 0 ? { routes } : {}),
141
+ ...(domains.length > 0 ? { domains } : {}),
142
+ };
143
+ }
144
+ function validateDestinationTunnelDomains(domains) {
145
+ if (domains.length > exports.DESTINATION_TUNNEL_MAX_DOMAINS) {
146
+ throw new DestinationTunnelSelectorError('too_many', `at most ${exports.DESTINATION_TUNNEL_MAX_DOMAINS} tunnel domains are allowed`);
147
+ }
148
+ const canonical = [];
149
+ const seen = new Set();
150
+ for (const domain of domains) {
151
+ const normalized = canonicalizeDestinationTunnelDomain(domain);
152
+ if (seen.has(normalized)) {
153
+ throw new DestinationTunnelSelectorError('duplicate', `duplicate tunnel domain ${normalized}`);
154
+ }
155
+ seen.add(normalized);
156
+ canonical.push(normalized);
157
+ }
158
+ return canonical;
159
+ }
160
+ /**
161
+ * Deterministic hash of the canonical selector configuration. Servers echo it
162
+ * in `ready` so clients can detect config mismatches across reconnects. The
163
+ * hashed JSON has fixed key order.
164
+ */
165
+ function destinationTunnelConfigHash(selectors, inspection = disabledDestinationTunnelInspection()) {
166
+ const canonical = validateDestinationTunnelSelectors(selectors);
167
+ const canonicalInspection = normalizeDestinationTunnelInspection(inspection);
168
+ const parts = [
169
+ `"version":${exports.DESTINATION_TUNNEL_VERSION}`,
170
+ `"selectors":[${canonical.map((selector) => JSON.stringify(selector)).join(',')}]`,
171
+ ];
172
+ parts.push(`"inspection":{"enabled":${canonicalInspection.enabled},"captureBodies":${canonicalInspection.captureBodies},"maxBodyBytes":${canonicalInspection.maxBodyBytes},"persist":${canonicalInspection.persist},"ttlSeconds":${canonicalInspection.ttlSeconds}}`);
173
+ return crypto_1.default
174
+ .createHash('sha256')
175
+ .update(`{${parts.join(',')}}`, 'utf8')
176
+ .digest('hex');
177
+ }
178
+ function normalizeDestinationTunnelInspection(inspection) {
179
+ const enabled = inspection.enabled ?? false;
180
+ const captureBodies = inspection.captureBodies ?? false;
181
+ const maxBodyBytes = inspection.maxBodyBytes ?? exports.DESTINATION_TUNNEL_DEFAULT_MAX_BODY_BYTES;
182
+ const persist = inspection.persist ?? false;
183
+ const ttlSeconds = inspection.ttlSeconds ?? exports.DESTINATION_TUNNEL_DEFAULT_TTL_SECONDS;
184
+ if (captureBodies && !enabled) {
185
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError('captureBodies requires inspection to be enabled');
186
+ }
187
+ if (persist && !enabled) {
188
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError('persist requires inspection to be enabled');
189
+ }
190
+ if (!Number.isInteger(maxBodyBytes) ||
191
+ maxBodyBytes < 1 ||
192
+ maxBodyBytes > exports.DESTINATION_TUNNEL_MAX_BODY_BYTES) {
193
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`maxBodyBytes must be an integer between 1 and ${exports.DESTINATION_TUNNEL_MAX_BODY_BYTES}`);
194
+ }
195
+ if (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > exports.DESTINATION_TUNNEL_MAX_TTL_SECONDS) {
196
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`ttlSeconds must be an integer between 1 and ${exports.DESTINATION_TUNNEL_MAX_TTL_SECONDS}`);
197
+ }
198
+ return { enabled, captureBodies, maxBodyBytes, persist, ttlSeconds };
199
+ }
200
+ function disabledDestinationTunnelInspection() {
201
+ return normalizeDestinationTunnelInspection({ enabled: false, captureBodies: false });
202
+ }
203
+ /** Selector IDs are one-based positions in the canonical selector array. */
204
+ function destinationTunnelSelectorIds(selectors) {
205
+ return validateDestinationTunnelSelectors(selectors).map((_, index) => `selector-${index + 1}`);
206
+ }
207
+ function destinationTunnelDomainMatches(pattern, host) {
208
+ const candidate = host.toLowerCase();
209
+ if (pattern.startsWith('*.')) {
210
+ const base = pattern.slice(2);
211
+ return candidate.length > base.length + 1 && candidate.endsWith(`.${base}`);
212
+ }
213
+ return candidate === pattern;
214
+ }
215
+ /**
216
+ * Verify a server OPEN against the negotiated selectors. Every OPEN must name
217
+ * a known selector ID and a target that the selector actually covers.
218
+ */
219
+ function assertDestinationTunnelOpenAllowed(message, selectors) {
220
+ const canonical = validateDestinationTunnelSelectors(selectors);
221
+ const match = /^selector-([1-9]\d*)$/.exec(message.selectorId);
222
+ const index = match?.[1] ? Number(match[1]) - 1 : -1;
223
+ const fail = () => {
224
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`server requested undeclared selector ${message.selectorId} ${message.host}:${message.port}`);
225
+ };
226
+ if (index < 0)
227
+ fail();
228
+ if (message.port === 53)
229
+ fail();
230
+ const selector = canonical[index];
231
+ if (selector === undefined)
232
+ return fail();
233
+ const parsed = parseDestinationTunnelSelector(selector);
234
+ if (parsed.route) {
235
+ if (message.host !== parsed.route.host || message.port !== parsed.route.port)
236
+ fail();
237
+ return 'route';
75
238
  }
239
+ if (!destinationTunnelDomainMatches(parsed.domain, message.host))
240
+ fail();
241
+ return 'domain';
76
242
  }
77
243
  function assertDestinationTunnelReady(message) {
78
244
  if (message.version !== exports.DESTINATION_TUNNEL_VERSION) {
79
- throw new DestinationTunnelProtocolError(`unsupported tunnel version ${message.version}`);
245
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`unsupported tunnel version ${message.version}`);
80
246
  }
81
247
  }
82
248
  function encodeDestinationTunnelClientMessage(message) {
83
- const record = readRecord(message, 'tunnel control message');
84
- const type = readString(record, 'type');
249
+ const record = (0, destination_tunnel_wire_reader_1.readRecord)(message, 'tunnel control message');
250
+ const type = (0, destination_tunnel_wire_reader_1.readString)(record, 'type');
85
251
  switch (type) {
86
252
  case 'start': {
87
- const version = readInteger(record, 'version');
253
+ const version = (0, destination_tunnel_wire_reader_1.readInteger)(record, 'version');
88
254
  if (version !== exports.DESTINATION_TUNNEL_VERSION) {
89
- throw new DestinationTunnelProtocolError(`unsupported tunnel version ${version}`);
255
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`unsupported tunnel version ${version}`);
90
256
  }
91
- const routes = validateDestinationTunnelRoutes(readArray(record, 'routes').map(readRoute));
92
- return JSON.stringify({ type, version, routes });
257
+ const selectors = validateDestinationTunnelSelectors((0, destination_tunnel_wire_reader_1.readArray)(record, 'selectors').map((value) => {
258
+ if (typeof value !== 'string') {
259
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError('tunnel selector must be a string');
260
+ }
261
+ return value;
262
+ }));
263
+ const inspection = readInspectionConfig(record);
264
+ return JSON.stringify({
265
+ type,
266
+ version,
267
+ selectors,
268
+ inspection,
269
+ window: readWindow(record),
270
+ });
93
271
  }
94
272
  case 'openOk':
273
+ return JSON.stringify({
274
+ type,
275
+ connId: readConnectionId(record),
276
+ transport: readTransportResult(record),
277
+ window: readWindow(record),
278
+ });
95
279
  case 'fin':
96
280
  return JSON.stringify({ type, connId: readConnectionId(record) });
97
281
  case 'openFail':
@@ -99,31 +283,40 @@ function encodeDestinationTunnelClientMessage(message) {
99
283
  return JSON.stringify({
100
284
  type,
101
285
  connId: readConnectionId(record),
102
- reason: readString(record, 'reason'),
103
- ...readOptionalString(record, 'osCode'),
286
+ reason: (0, destination_tunnel_wire_reader_1.readString)(record, 'reason'),
287
+ ...(0, destination_tunnel_wire_reader_1.readOptionalString)(record, 'osCode'),
288
+ });
289
+ case 'windowUpdate':
290
+ return JSON.stringify({
291
+ type,
292
+ connId: readConnectionId(record),
293
+ increment: readWindowIncrement(record),
104
294
  });
105
295
  default:
106
- throw new DestinationTunnelProtocolError(`unknown tunnel control message type ${type}`);
296
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`unknown tunnel control message type ${type}`);
107
297
  }
108
298
  }
109
299
  function decodeDestinationTunnelServerMessage(value) {
110
- const message = readRecord(value, 'tunnel control message');
111
- const type = readString(message, 'type');
300
+ const message = (0, destination_tunnel_wire_reader_1.readRecord)(value, 'tunnel control message');
301
+ const type = (0, destination_tunnel_wire_reader_1.readString)(message, 'type');
112
302
  switch (type) {
113
303
  case 'ready':
114
304
  return {
115
305
  type,
116
- version: readInteger(message, 'version'),
117
- tunnelId: readString(message, 'tunnelId'),
306
+ version: (0, destination_tunnel_wire_reader_1.readInteger)(message, 'version'),
307
+ tunnelId: (0, destination_tunnel_wire_reader_1.readString)(message, 'tunnelId'),
308
+ selectors: (0, destination_tunnel_wire_reader_1.readArray)(message, 'selectors').map((value, index) => readSelectorReport(value, `selector-${index + 1}`)),
309
+ configHash: (0, destination_tunnel_wire_reader_1.readString)(message, 'configHash'),
118
310
  };
119
311
  case 'open':
120
312
  return {
121
313
  type,
122
314
  connId: readConnectionId(message),
123
- routeId: readString(message, 'routeId'),
124
- host: readString(message, 'host'),
315
+ selectorId: (0, destination_tunnel_wire_reader_1.readString)(message, 'selectorId'),
316
+ host: (0, destination_tunnel_wire_reader_1.readString)(message, 'host'),
125
317
  port: readPort(message, 'port'),
126
- proto: readTCP(message),
318
+ transport: readTransportRequest(message),
319
+ window: readWindow(message),
127
320
  };
128
321
  case 'fin':
129
322
  return { type, connId: readConnectionId(message) };
@@ -131,18 +324,57 @@ function decodeDestinationTunnelServerMessage(value) {
131
324
  return {
132
325
  type,
133
326
  connId: readConnectionId(message),
134
- reason: readString(message, 'reason'),
135
- ...readOptionalString(message, 'osCode'),
327
+ reason: (0, destination_tunnel_wire_reader_1.readString)(message, 'reason'),
328
+ ...(0, destination_tunnel_wire_reader_1.readOptionalString)(message, 'osCode'),
329
+ };
330
+ case 'windowUpdate':
331
+ return {
332
+ type,
333
+ connId: readConnectionId(message),
334
+ increment: readWindowIncrement(message),
136
335
  };
137
336
  case 'error':
138
- return { type, code: readString(message, 'code') };
337
+ return { type, code: (0, destination_tunnel_wire_reader_1.readString)(message, 'code') };
139
338
  default:
140
- throw new DestinationTunnelProtocolError(`unknown tunnel control message type ${type}`);
339
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`unknown tunnel control message type ${type}`);
340
+ }
341
+ }
342
+ function parseDestinationTunnelSelector(selector, minRoutePort = 1) {
343
+ if (typeof selector !== 'string' || selector.length === 0) {
344
+ throw new DestinationTunnelSelectorError('empty', 'tunnel selector must not be empty');
345
+ }
346
+ if (!selector.startsWith('[') && !selector.includes(':')) {
347
+ const domain = canonicalizeDestinationTunnelDomain(selector);
348
+ return { value: domain, domain };
349
+ }
350
+ let host;
351
+ let portText;
352
+ if (selector.startsWith('[')) {
353
+ const match = /^\[([^\]]+)\]:(\d+)$/.exec(selector);
354
+ if (!match?.[1] || !match[2]) {
355
+ throw new DestinationTunnelSelectorError('invalid_host', `invalid tunnel selector ${selector}`);
356
+ }
357
+ host = match[1];
358
+ portText = match[2];
359
+ }
360
+ else {
361
+ const separator = selector.lastIndexOf(':');
362
+ if (separator <= 0 || selector.indexOf(':') !== separator) {
363
+ throw new DestinationTunnelSelectorError('invalid_host', `invalid tunnel selector ${selector}`);
364
+ }
365
+ host = selector.slice(0, separator);
366
+ portText = selector.slice(separator + 1);
141
367
  }
368
+ if (!/^\d+$/.test(portText)) {
369
+ throw new DestinationTunnelSelectorError('invalid_port', `invalid tunnel selector port ${portText}`);
370
+ }
371
+ const route = canonicalizeDestinationTunnelRoute({ host, port: Number(portText) }, minRoutePort);
372
+ const formattedHost = route.host.includes(':') ? `[${route.host}]` : route.host;
373
+ return { value: `${formattedHost}:${route.port}`, route };
142
374
  }
143
- function canonicalizeDestinationTunnelRoute(route) {
144
- if (!Number.isInteger(route.port) || route.port < 1 || route.port > 65535 || route.port === 53) {
145
- throw new DestinationTunnelRouteError('invalid_port', `invalid tunnel route port ${route.port}`);
375
+ function canonicalizeDestinationTunnelRoute(route, minPort = 1) {
376
+ if (!Number.isInteger(route.port) || route.port < minPort || route.port > 65535 || route.port === 53) {
377
+ throw new DestinationTunnelSelectorError('invalid_port', `invalid tunnel route port ${route.port}`);
146
378
  }
147
379
  const asciiHost = Buffer.byteLength(route.host, 'utf8') === route.host.length;
148
380
  if (asciiHost && route.host.toLowerCase() === 'localhost') {
@@ -156,11 +388,49 @@ function canonicalizeDestinationTunnelRoute(route) {
156
388
  const hostname = new URL(`http://[${route.host}]/`).hostname;
157
389
  const canonical = hostname.slice(1, -1);
158
390
  if (canonical !== '::1' && /^::(?:[0-9a-f]{1,4}:)?[0-9a-f]{1,4}$/.test(canonical)) {
159
- throw new DestinationTunnelRouteError('invalid_host', `invalid tunnel route host ${route.host}`);
391
+ throw new DestinationTunnelSelectorError('invalid_host', `invalid tunnel route host ${route.host}`);
160
392
  }
161
393
  return { host: canonicalizeIPv6(canonical), port: route.port };
162
394
  }
163
- throw new DestinationTunnelRouteError('invalid_host', `invalid tunnel route host ${route.host}`);
395
+ throw new DestinationTunnelSelectorError('invalid_host', `invalid tunnel route host ${route.host}`);
396
+ }
397
+ function canonicalizeDestinationTunnelDomain(domain) {
398
+ const invalid = () => {
399
+ throw new DestinationTunnelSelectorError('invalid_domain', `invalid tunnel domain ${domain}`);
400
+ };
401
+ if (typeof domain !== 'string' || domain.length === 0 || domain.length > 260)
402
+ invalid();
403
+ // ASCII only: reject anything IDNA mapping would change to avoid ambiguity
404
+ // between implementations. Users provide punycode (xn--) names directly.
405
+ if (Buffer.byteLength(domain, 'utf8') !== domain.length)
406
+ invalid();
407
+ const lowered = domain.toLowerCase();
408
+ const wildcard = lowered.startsWith('*.');
409
+ const base = wildcard ? lowered.slice(2) : lowered;
410
+ if (base.length === 0 || base.length > 253)
411
+ invalid();
412
+ if (base.includes('*'))
413
+ invalid();
414
+ if (base.endsWith('.') || base.startsWith('.'))
415
+ invalid();
416
+ if (net_1.default.isIP(base) !== 0)
417
+ invalid();
418
+ if (base === 'localhost')
419
+ invalid();
420
+ if ((0, url_1.domainToASCII)(base) !== base)
421
+ invalid();
422
+ const labels = base.split('.');
423
+ for (const label of labels) {
424
+ if (label.length === 0 || label.length > 63)
425
+ invalid();
426
+ if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(label))
427
+ invalid();
428
+ }
429
+ // All-numeric final labels would be ambiguous with IPv4 literals.
430
+ const finalLabel = labels[labels.length - 1];
431
+ if (finalLabel !== undefined && /^\d+$/.test(finalLabel))
432
+ invalid();
433
+ return wildcard ? `*.${base}` : base;
164
434
  }
165
435
  function canonicalizeIPv6(host) {
166
436
  const mappedIPv4 = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host);
@@ -171,79 +441,118 @@ function canonicalizeIPv6(host) {
171
441
  const low = Number.parseInt(mappedIPv4[2], 16);
172
442
  return `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`;
173
443
  }
174
- function parseRouteId(routeId) {
175
- const match = /^route-([1-9]\d*)$/.exec(routeId);
176
- if (!match?.[1]) {
177
- return -1;
444
+ function readSelectorReport(value, expectedId) {
445
+ const report = (0, destination_tunnel_wire_reader_1.readRecord)(value, 'tunnel selector');
446
+ const id = (0, destination_tunnel_wire_reader_1.readString)(report, 'id');
447
+ if (id !== expectedId) {
448
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`invalid tunnel selector id ${id}`);
449
+ }
450
+ const kind = (0, destination_tunnel_wire_reader_1.readString)(report, 'kind');
451
+ if (kind !== 'route' && kind !== 'domain') {
452
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`invalid tunnel selector kind ${kind}`);
453
+ }
454
+ const result = {
455
+ id,
456
+ kind,
457
+ value: (0, destination_tunnel_wire_reader_1.readString)(report, 'value'),
458
+ };
459
+ if (report['binds'] !== undefined) {
460
+ result.binds = (0, destination_tunnel_wire_reader_1.readArray)(report, 'binds').map(readBindReport);
178
461
  }
179
- return Number(match[1]) - 1;
462
+ return result;
180
463
  }
181
- function readRoute(value) {
182
- const route = readRecord(value, 'tunnel route');
464
+ function readBindReport(value) {
465
+ const bind = (0, destination_tunnel_wire_reader_1.readRecord)(value, 'tunnel bind report');
466
+ const status = (0, destination_tunnel_wire_reader_1.readString)(bind, 'status');
467
+ if (status !== 'ok' && status !== 'conflict' && status !== 'error') {
468
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`invalid tunnel bind status ${status}`);
469
+ }
183
470
  return {
184
- host: readString(route, 'host'),
185
- port: readPort(route, 'port'),
471
+ address: (0, destination_tunnel_wire_reader_1.readString)(bind, 'address'),
472
+ status,
473
+ ...(0, destination_tunnel_wire_reader_1.readOptionalString)(bind, 'osCode'),
186
474
  };
187
475
  }
188
- function readRecord(value, name) {
189
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
190
- throw new DestinationTunnelProtocolError(`${name} must be an object`);
191
- }
192
- return value;
476
+ function readInspectionConfig(record) {
477
+ const inspection = (0, destination_tunnel_wire_reader_1.readRecord)(record['inspection'], 'inspection');
478
+ const enabled = (0, destination_tunnel_wire_reader_1.readBoolean)(inspection, 'enabled');
479
+ const captureBodies = (0, destination_tunnel_wire_reader_1.readBoolean)(inspection, 'captureBodies');
480
+ const maxBodyBytes = (0, destination_tunnel_wire_reader_1.readInteger)(inspection, 'maxBodyBytes');
481
+ const persist = (0, destination_tunnel_wire_reader_1.readBoolean)(inspection, 'persist');
482
+ const ttlSeconds = (0, destination_tunnel_wire_reader_1.readInteger)(inspection, 'ttlSeconds');
483
+ return normalizeDestinationTunnelInspection({
484
+ enabled,
485
+ captureBodies,
486
+ maxBodyBytes,
487
+ persist,
488
+ ttlSeconds,
489
+ });
193
490
  }
194
- function readArray(record, key) {
195
- const value = record[key];
196
- if (!Array.isArray(value)) {
197
- throw new DestinationTunnelProtocolError(`${key} must be an array`);
491
+ function readTransportRequest(record) {
492
+ const transport = (0, destination_tunnel_wire_reader_1.readRecord)(record['transport'], 'transport');
493
+ const type = (0, destination_tunnel_wire_reader_1.readString)(transport, 'type');
494
+ if (type === 'tcp') {
495
+ return { type };
198
496
  }
199
- return value;
200
- }
201
- function readString(record, key) {
202
- const value = record[key];
203
- if (typeof value !== 'string') {
204
- throw new DestinationTunnelProtocolError(`${key} must be a string`);
497
+ if (type === 'tls') {
498
+ const serverName = (0, destination_tunnel_wire_reader_1.readString)(transport, 'serverName');
499
+ if (serverName.length === 0) {
500
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError('TLS transport requires serverName');
501
+ }
502
+ const alpnProtocols = (0, destination_tunnel_wire_reader_1.readArray)(transport, 'alpnProtocols').map((protocol) => {
503
+ if (typeof protocol !== 'string' || protocol.length === 0) {
504
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError('alpnProtocols must contain non-empty strings');
505
+ }
506
+ return protocol;
507
+ });
508
+ return { type, serverName, alpnProtocols };
205
509
  }
206
- return value;
510
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`unsupported tunnel transport ${type}`);
207
511
  }
208
- function readOptionalString(record, key) {
209
- const value = record[key];
210
- if (value === undefined) {
211
- return {};
512
+ function readTransportResult(record) {
513
+ const transport = (0, destination_tunnel_wire_reader_1.readRecord)(record['transport'], 'transport');
514
+ const type = (0, destination_tunnel_wire_reader_1.readString)(transport, 'type');
515
+ if (type !== 'tcp' && type !== 'tls') {
516
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`unsupported tunnel transport ${type}`);
212
517
  }
213
- if (typeof value !== 'string') {
214
- throw new DestinationTunnelProtocolError(`${key} must be a string`);
518
+ return {
519
+ type,
520
+ ...(0, destination_tunnel_wire_reader_1.readOptionalString)(transport, 'alpnProtocol'),
521
+ ...(0, destination_tunnel_wire_reader_1.readOptionalString)(transport, 'remoteAddress'),
522
+ ...(0, destination_tunnel_wire_reader_1.readOptionalNonNegativeInteger)(transport, 'dnsMs'),
523
+ ...(0, destination_tunnel_wire_reader_1.readOptionalNonNegativeInteger)(transport, 'connectMs'),
524
+ ...(0, destination_tunnel_wire_reader_1.readOptionalNonNegativeInteger)(transport, 'tlsMs'),
525
+ };
526
+ }
527
+ function readPort(record, key) {
528
+ const value = (0, destination_tunnel_wire_reader_1.readInteger)(record, key);
529
+ if (value < 1 || value > 65535) {
530
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`${key} must be between 1 and 65535`);
215
531
  }
216
- return { [key]: value };
532
+ return value;
217
533
  }
218
- function readInteger(record, key) {
219
- const value = record[key];
220
- if (!Number.isInteger(value)) {
221
- throw new DestinationTunnelProtocolError(`${key} must be an integer`);
534
+ function readWindow(record) {
535
+ const value = (0, destination_tunnel_wire_reader_1.readInteger)(record, 'window');
536
+ if (value < 1 || value > exports.DESTINATION_TUNNEL_MAX_WINDOW) {
537
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`window must be an integer between 1 and ${exports.DESTINATION_TUNNEL_MAX_WINDOW}`);
222
538
  }
223
539
  return value;
224
540
  }
225
- function readPort(record, key) {
226
- const value = readInteger(record, key);
227
- if (value < 1 || value > 65535) {
228
- throw new DestinationTunnelProtocolError(`${key} must be between 1 and 65535`);
541
+ function readWindowIncrement(record) {
542
+ const value = (0, destination_tunnel_wire_reader_1.readInteger)(record, 'increment');
543
+ if (value < 1 || value > exports.DESTINATION_TUNNEL_MAX_WINDOW_INCREMENT) {
544
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError(`increment must be an integer between 1 and ${exports.DESTINATION_TUNNEL_MAX_WINDOW_INCREMENT}`);
229
545
  }
230
546
  return value;
231
547
  }
232
548
  function readConnectionId(record) {
233
- const value = readInteger(record, 'connId');
549
+ const value = (0, destination_tunnel_wire_reader_1.readInteger)(record, 'connId');
234
550
  validateConnectionId(value);
235
551
  return value;
236
552
  }
237
553
  function validateConnectionId(value) {
238
554
  if (!Number.isInteger(value) || value < 0 || value > 4294967295) {
239
- throw new DestinationTunnelProtocolError('connId must be an unsigned 32-bit integer');
240
- }
241
- }
242
- function readTCP(record) {
243
- const value = readString(record, 'proto');
244
- if (value !== 'tcp') {
245
- throw new DestinationTunnelProtocolError(`unsupported tunnel transport ${value}`);
555
+ throw new destination_tunnel_wire_reader_1.DestinationTunnelProtocolError('connId must be an unsigned 32-bit integer');
246
556
  }
247
- return value;
248
557
  }
249
558
  //# sourceMappingURL=destination-tunnel.js.map