@volter/twin-tunnel 1.0.0 → 1.0.1
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/README.md +3 -1
- package/dist/journeys/coverage.json +109 -0
- package/dist/journeys/customer-life.json +588 -0
- package/dist/journeys/customer-life.outline.md +34 -0
- package/dist/journeys/customer-life.outline.verdict.json +42 -0
- package/dist/journeys/customer-life.verdict.json +51 -0
- package/dist/journeys/decisions.json +23 -0
- package/dist/journeys/demand.json +24 -0
- package/dist/journeys/score.json +134 -0
- package/dist/journeys/unreachable.json +12 -0
- package/dist/journeys/vendor-examples.json +7 -0
- package/dist/journeys/vendor-examples.verdict.json +18 -0
- package/dist/spec/SOURCE.md +30 -14
- package/dist/spec/openapi.json +47 -15
- package/dist/spec/sources.json +1 -0
- package/dist/src/index.js +4 -3
- package/dist/src/manifest.js +7 -0
- package/dist/src/semantics/around.js +42 -9
- package/dist/src/semantics/doors.js +2 -2
- package/dist/src/semantics/shared.d.ts +8 -3
- package/dist/src/semantics/shared.js +61 -3
- package/dist/src/semantics/sockets.js +97 -16
- package/journeys/coverage.json +109 -0
- package/journeys/customer-life.json +588 -0
- package/journeys/customer-life.outline.md +34 -0
- package/journeys/customer-life.outline.verdict.json +42 -0
- package/journeys/customer-life.verdict.json +51 -0
- package/journeys/decisions.json +23 -0
- package/journeys/demand.json +24 -0
- package/journeys/score.json +134 -0
- package/journeys/unreachable.json +12 -0
- package/journeys/vendor-examples.json +7 -0
- package/journeys/vendor-examples.verdict.json +18 -0
- package/package.json +3 -2
- package/spec/SOURCE.md +30 -14
- package/spec/openapi.json +47 -15
- package/spec/sources.json +1 -0
- package/src/index.ts +5 -4
- package/src/manifest.ts +7 -0
- package/src/semantics/around.ts +37 -10
- package/src/semantics/doors.ts +2 -2
- package/src/semantics/shared.ts +56 -4
- package/src/semantics/sockets.ts +72 -14
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@ that host (or `?__tunnel=<id>` on the relay's direct host) is sent down the sock
|
|
|
7
7
|
what the client sends back: a whole `response`, or `response-start`, `response-chunk`s and `response-end` (the kernel's
|
|
8
8
|
request answered by a session). No client holding the tunnel is 502 "Tunnel not connected"; no answer in 30 s is 504; a
|
|
9
9
|
body over 750000 bytes is 413. A second registration of a live id needs `replace` (the first is closed 4001); another
|
|
10
|
-
account's id and a bad token are refused.
|
|
10
|
+
account's id and a bad token are refused (4002 for ownership/collision, 4003 for an invalid token). Visitor authentication gates, browser WebSocket bridging, quotas, the inspector and reservation expiry are not served. The demand is the API relay, so no workspace screens are needed.
|
|
11
|
+
|
|
12
|
+
The executable life checks public HTTP requests while its client answers on the control socket, including buffered pages and streamed command output. Relay authentication cookies and query parameters are removed before forwarding; visitor-supplied forwarded addresses are replaced by the edge address or the relay sentinel.
|
|
11
13
|
|
|
12
14
|
The World's door: `POST /_twin/accounts` ({ slug }: an account and its token). The credential door answers TUNNEL_SECRET
|
|
13
15
|
and the relay URL (TUNNEL_SERVER_URL, SUPERCODE_TUNNEL_SERVER_URL).
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"vendor": "tunnel",
|
|
3
|
+
"missing": 9,
|
|
4
|
+
"stale": [],
|
|
5
|
+
"code": {
|
|
6
|
+
"lines": 255,
|
|
7
|
+
"run": 242,
|
|
8
|
+
"recorded": 4,
|
|
9
|
+
"missing": 9
|
|
10
|
+
},
|
|
11
|
+
"state": {
|
|
12
|
+
"declared": 0,
|
|
13
|
+
"reached": 0,
|
|
14
|
+
"recorded": 0,
|
|
15
|
+
"missing": [],
|
|
16
|
+
"undeclared": []
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
{
|
|
20
|
+
"file": "semantics/doors.ts",
|
|
21
|
+
"lines": {
|
|
22
|
+
"found": 13,
|
|
23
|
+
"hit": 13
|
|
24
|
+
},
|
|
25
|
+
"missing": []
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"file": "semantics/sockets.ts",
|
|
29
|
+
"lines": {
|
|
30
|
+
"found": 82,
|
|
31
|
+
"hit": 74
|
|
32
|
+
},
|
|
33
|
+
"missing": [
|
|
34
|
+
{
|
|
35
|
+
"from": 114,
|
|
36
|
+
"to": 118,
|
|
37
|
+
"fn": "expireStream"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"from": 123,
|
|
41
|
+
"to": 125,
|
|
42
|
+
"fn": "cancelStream"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"file": "semantics/shared.ts",
|
|
48
|
+
"lines": {
|
|
49
|
+
"found": 46,
|
|
50
|
+
"hit": 46
|
|
51
|
+
},
|
|
52
|
+
"missing": []
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"file": "semantics/around.ts",
|
|
56
|
+
"lines": {
|
|
57
|
+
"found": 40,
|
|
58
|
+
"hit": 39
|
|
59
|
+
},
|
|
60
|
+
"missing": [
|
|
61
|
+
{
|
|
62
|
+
"from": 66,
|
|
63
|
+
"to": 66,
|
|
64
|
+
"fn": "timeout"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"file": "semantics/index.ts",
|
|
70
|
+
"lines": {
|
|
71
|
+
"found": 2,
|
|
72
|
+
"hit": 2
|
|
73
|
+
},
|
|
74
|
+
"missing": []
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"file": "manifest.ts",
|
|
78
|
+
"lines": {
|
|
79
|
+
"found": 43,
|
|
80
|
+
"hit": 43
|
|
81
|
+
},
|
|
82
|
+
"missing": []
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"file": "index.ts",
|
|
86
|
+
"lines": {
|
|
87
|
+
"found": 7,
|
|
88
|
+
"hit": 7
|
|
89
|
+
},
|
|
90
|
+
"missing": []
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"file": "server.ts",
|
|
94
|
+
"lines": {
|
|
95
|
+
"found": 6,
|
|
96
|
+
"hit": 2
|
|
97
|
+
},
|
|
98
|
+
"missing": []
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"file": "fetch.ts",
|
|
102
|
+
"lines": {
|
|
103
|
+
"found": 16,
|
|
104
|
+
"hit": 16
|
|
105
|
+
},
|
|
106
|
+
"missing": []
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
{
|
|
2
|
+
"vendor": "tunnel",
|
|
3
|
+
"author": "Contributor 71",
|
|
4
|
+
"host": "voltertest.xyz",
|
|
5
|
+
"about": "Ada demonstrates Harness remote access to Bob; the local client answers buffered page requests and streams command output. Reconnect, copied configuration, a truncated token and shutdown supply the refusal causes.",
|
|
6
|
+
"steps": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Ada creates her relay account for the shared Harness demo",
|
|
9
|
+
"method": "POST",
|
|
10
|
+
"path": "/_twin/accounts",
|
|
11
|
+
"body": {
|
|
12
|
+
"slug": "ada"
|
|
13
|
+
},
|
|
14
|
+
"status": 201,
|
|
15
|
+
"capture": {
|
|
16
|
+
"ada": "token"
|
|
17
|
+
},
|
|
18
|
+
"expect": [
|
|
19
|
+
{
|
|
20
|
+
"path": "token",
|
|
21
|
+
"matches": "^vt_[A-Za-z0-9]{40}$"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "server",
|
|
25
|
+
"equals": "https://voltertest.xyz"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "Harness connects its named remote-access tunnel",
|
|
31
|
+
"socket": "h1",
|
|
32
|
+
"open": "/ws?id=ada-harness"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "Harness registers with Ada's token and saves the returned visitor URL",
|
|
36
|
+
"socket": "h1",
|
|
37
|
+
"send": {
|
|
38
|
+
"type": "register",
|
|
39
|
+
"secret": "{ada}",
|
|
40
|
+
"authRequired": false,
|
|
41
|
+
"tunnelId": "ada-harness"
|
|
42
|
+
},
|
|
43
|
+
"capture": {
|
|
44
|
+
"public": "last.url"
|
|
45
|
+
},
|
|
46
|
+
"expect": [
|
|
47
|
+
{
|
|
48
|
+
"path": "last.type",
|
|
49
|
+
"equals": "registered"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"path": "last.tunnelId",
|
|
53
|
+
"equals": "ada-harness"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"path": "last.url",
|
|
57
|
+
"equals": "https://ada-harness.voltertest.xyz"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "Bob mistypes the tunnel name from Ada's invitation",
|
|
63
|
+
"method": "GET",
|
|
64
|
+
"path": "https://ada-hanress.voltertest.xyz/demo",
|
|
65
|
+
"status": 502,
|
|
66
|
+
"expect": [
|
|
67
|
+
{
|
|
68
|
+
"path": "",
|
|
69
|
+
"equals": "Tunnel not connected"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "Bob opens the shared demo page",
|
|
75
|
+
"method": "GET",
|
|
76
|
+
"path": "{public}/demo?view=remote",
|
|
77
|
+
"defer": "page"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "Harness receives Bob's page request",
|
|
81
|
+
"socket": "h1",
|
|
82
|
+
"capture": {
|
|
83
|
+
"page": "last.reqId"
|
|
84
|
+
},
|
|
85
|
+
"expect": [
|
|
86
|
+
{
|
|
87
|
+
"path": "last.type",
|
|
88
|
+
"equals": "request"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"path": "last.method",
|
|
92
|
+
"equals": "GET"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"path": "last.path",
|
|
96
|
+
"equals": "/demo?view=remote"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"path": "last.body",
|
|
100
|
+
"equals": null
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"path": "last.headers.x-forwarded-for",
|
|
104
|
+
"equals": "0.0.0.0"
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "The local server returns the remote-access page",
|
|
110
|
+
"socket": "h1",
|
|
111
|
+
"send": {
|
|
112
|
+
"type": "response",
|
|
113
|
+
"reqId": "{page}",
|
|
114
|
+
"status": 200,
|
|
115
|
+
"headers": {
|
|
116
|
+
"content-type": "text/html",
|
|
117
|
+
"x-frame-options": "DENY",
|
|
118
|
+
"content-security-policy": "default-src 'self'; frame-ancestors 'none'"
|
|
119
|
+
},
|
|
120
|
+
"body": "PGgxPkFkYSdzIHJlbW90ZSBkZW1vPC9oMT4="
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "Bob sees the page supplied by Ada's local server",
|
|
125
|
+
"method": "GET",
|
|
126
|
+
"path": "{public}/demo?view=remote",
|
|
127
|
+
"await": "page",
|
|
128
|
+
"status": 200,
|
|
129
|
+
"expect": [
|
|
130
|
+
{
|
|
131
|
+
"path": "",
|
|
132
|
+
"equals": "<h1>Ada's remote demo</h1>"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"header": "content-type",
|
|
136
|
+
"contains": "text/html"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"header": "x-frame-options",
|
|
140
|
+
"exists": false
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"header": "content-security-policy",
|
|
144
|
+
"equals": "default-src 'self'"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"header": "cache-control",
|
|
148
|
+
"equals": "private, no-store, max-age=0"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "Bob's remote viewer requests permission to send its command headers",
|
|
154
|
+
"method": "OPTIONS",
|
|
155
|
+
"path": "{public}/command",
|
|
156
|
+
"headers": {
|
|
157
|
+
"origin": "https://viewer.example",
|
|
158
|
+
"access-control-request-headers": "Content-Type, X-Volter-App"
|
|
159
|
+
},
|
|
160
|
+
"status": 204,
|
|
161
|
+
"expect": [
|
|
162
|
+
{
|
|
163
|
+
"header": "access-control-allow-origin",
|
|
164
|
+
"equals": "https://viewer.example"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"header": "access-control-allow-headers",
|
|
168
|
+
"equals": "Content-Type, X-Volter-App"
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "Bob asks Harness to run a demo command",
|
|
174
|
+
"method": "POST",
|
|
175
|
+
"path": "{public}/command?__volter_token=visitor&mode=demo",
|
|
176
|
+
"body": "run demo",
|
|
177
|
+
"headers": {
|
|
178
|
+
"cookie": "session=bob; __volter_auth=relay",
|
|
179
|
+
"x-forwarded-for": "127.0.0.1",
|
|
180
|
+
"origin": "https://viewer.example",
|
|
181
|
+
"x-volter-app": "demo"
|
|
182
|
+
},
|
|
183
|
+
"defer": "command"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "Harness receives the command without relay authentication data",
|
|
187
|
+
"socket": "h1",
|
|
188
|
+
"capture": {
|
|
189
|
+
"command": "last.reqId"
|
|
190
|
+
},
|
|
191
|
+
"expect": [
|
|
192
|
+
{
|
|
193
|
+
"path": "last.method",
|
|
194
|
+
"equals": "POST"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"path": "last.path",
|
|
198
|
+
"equals": "/command?mode=demo"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"path": "last.body",
|
|
202
|
+
"equals": "cnVuIGRlbW8="
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"path": "last.headers.cookie",
|
|
206
|
+
"equals": "session=bob"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"path": "last.headers.x-forwarded-for",
|
|
210
|
+
"equals": "0.0.0.0"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"path": "last.headers.x-volter-app",
|
|
214
|
+
"equals": "demo"
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"name": "Harness starts streaming the command's output",
|
|
220
|
+
"socket": "h1",
|
|
221
|
+
"send": {
|
|
222
|
+
"type": "response-start",
|
|
223
|
+
"reqId": "{command}",
|
|
224
|
+
"status": 200,
|
|
225
|
+
"headers": {
|
|
226
|
+
"content-type": "text/plain",
|
|
227
|
+
"x-demo": "Ada"
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"name": "The running command reports its progress",
|
|
233
|
+
"socket": "h1",
|
|
234
|
+
"send": {
|
|
235
|
+
"type": "response-chunk",
|
|
236
|
+
"reqId": "{command}",
|
|
237
|
+
"data": "U3RhcnRpbmcgZGVtbwo="
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"name": "The command finishes and the stream ends",
|
|
242
|
+
"socket": "h1",
|
|
243
|
+
"send": [
|
|
244
|
+
{
|
|
245
|
+
"type": "response-chunk",
|
|
246
|
+
"reqId": "{command}",
|
|
247
|
+
"data": "RGVtbyBjb21wbGV0ZQo="
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"type": "response-end",
|
|
251
|
+
"reqId": "{command}"
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "Bob reads both chunks from the completed command",
|
|
257
|
+
"method": "POST",
|
|
258
|
+
"path": "{public}/command?__volter_token=visitor&mode=demo",
|
|
259
|
+
"await": "command",
|
|
260
|
+
"status": 200,
|
|
261
|
+
"expect": [
|
|
262
|
+
{
|
|
263
|
+
"path": "",
|
|
264
|
+
"equals": "Starting demo\nDemo complete\n"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"header": "x-demo",
|
|
268
|
+
"equals": "Ada"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"header": "access-control-allow-origin",
|
|
272
|
+
"equals": "https://viewer.example"
|
|
273
|
+
}
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"name": "Ada opens a separate control-protocol diagnostic while setting up the second Harness connector",
|
|
278
|
+
"socket": "h2",
|
|
279
|
+
"open": "/ws?id=ada-harness"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"name": "Ada's diagnostic omits replacement and receives the relay's live-connector collision refusal",
|
|
283
|
+
"socket": "h2",
|
|
284
|
+
"send": {
|
|
285
|
+
"type": "register",
|
|
286
|
+
"secret": "{ada}",
|
|
287
|
+
"authRequired": false,
|
|
288
|
+
"tunnelId": "ada-harness"
|
|
289
|
+
},
|
|
290
|
+
"status": 4002,
|
|
291
|
+
"expect": [
|
|
292
|
+
{
|
|
293
|
+
"path": "last.type",
|
|
294
|
+
"equals": "error"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"path": "last.message",
|
|
298
|
+
"contains": "already in use"
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"path": "closed.reason",
|
|
302
|
+
"equals": "Tunnel ID already in use"
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"name": "Ada starts the actual Harness connector whose SDK requests replacement automatically",
|
|
308
|
+
"socket": "h3",
|
|
309
|
+
"open": "/ws?id=ada-harness"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"name": "Harness's automatic SDK replacement keeps the existing demo URL",
|
|
313
|
+
"socket": "h3",
|
|
314
|
+
"send": {
|
|
315
|
+
"type": "register",
|
|
316
|
+
"secret": "{ada}",
|
|
317
|
+
"authRequired": false,
|
|
318
|
+
"replace": true,
|
|
319
|
+
"tunnelId": "ada-harness"
|
|
320
|
+
},
|
|
321
|
+
"expect": [
|
|
322
|
+
{
|
|
323
|
+
"path": "last.type",
|
|
324
|
+
"equals": "registered"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"path": "last.url",
|
|
328
|
+
"equals": "{public}"
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"name": "Harness releases the obsolete connector after the relay replaced it",
|
|
334
|
+
"socket": "h1",
|
|
335
|
+
"status": 4001,
|
|
336
|
+
"expect": [
|
|
337
|
+
{
|
|
338
|
+
"path": "closed.reason",
|
|
339
|
+
"equals": "Replaced by new client"
|
|
340
|
+
}
|
|
341
|
+
],
|
|
342
|
+
"close": true
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"name": "Bob checks that the restarted demo still answers",
|
|
346
|
+
"method": "HEAD",
|
|
347
|
+
"path": "{public}/ready",
|
|
348
|
+
"defer": "ready"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"name": "The replacement receives the readiness request",
|
|
352
|
+
"socket": "h3",
|
|
353
|
+
"capture": {
|
|
354
|
+
"ready": "last.reqId"
|
|
355
|
+
},
|
|
356
|
+
"expect": [
|
|
357
|
+
{
|
|
358
|
+
"path": "last.type",
|
|
359
|
+
"equals": "request"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"path": "last.path",
|
|
363
|
+
"equals": "/ready"
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"name": "The local readiness endpoint replies",
|
|
369
|
+
"socket": "h3",
|
|
370
|
+
"send": [
|
|
371
|
+
{
|
|
372
|
+
"type": "response-start",
|
|
373
|
+
"reqId": "{ready}",
|
|
374
|
+
"status": 200,
|
|
375
|
+
"headers": {
|
|
376
|
+
"x-ready": "Ready"
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"type": "response-end",
|
|
381
|
+
"reqId": "{ready}"
|
|
382
|
+
}
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"name": "Bob gets the readiness response through the replacement",
|
|
387
|
+
"method": "HEAD",
|
|
388
|
+
"path": "{public}/ready",
|
|
389
|
+
"await": "ready",
|
|
390
|
+
"status": 200,
|
|
391
|
+
"expect": [
|
|
392
|
+
{
|
|
393
|
+
"path": "",
|
|
394
|
+
"equals": ""
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"header": "x-ready",
|
|
398
|
+
"equals": "Ready"
|
|
399
|
+
}
|
|
400
|
+
]
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"name": "Bob creates an account to host his own remote session",
|
|
404
|
+
"method": "POST",
|
|
405
|
+
"path": "/_twin/accounts",
|
|
406
|
+
"body": {
|
|
407
|
+
"slug": "bob"
|
|
408
|
+
},
|
|
409
|
+
"status": 201,
|
|
410
|
+
"capture": {
|
|
411
|
+
"bob": "token"
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"name": "Bob copied Ada's tunnel name into his connector configuration",
|
|
416
|
+
"socket": "b1",
|
|
417
|
+
"open": "/ws?id=ada-harness"
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"name": "Bob's account cannot claim Ada's reserved name",
|
|
421
|
+
"socket": "b1",
|
|
422
|
+
"send": {
|
|
423
|
+
"type": "register",
|
|
424
|
+
"secret": "{bob}",
|
|
425
|
+
"authRequired": false,
|
|
426
|
+
"tunnelId": "ada-harness",
|
|
427
|
+
"replace": true
|
|
428
|
+
},
|
|
429
|
+
"status": 4002,
|
|
430
|
+
"expect": [
|
|
431
|
+
{
|
|
432
|
+
"path": "last.message",
|
|
433
|
+
"contains": "reserved by another account"
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"path": "closed.reason",
|
|
437
|
+
"equals": "Tunnel ID reserved"
|
|
438
|
+
}
|
|
439
|
+
]
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"name": "Bob corrects the name but pastes an incomplete account token",
|
|
443
|
+
"socket": "b2",
|
|
444
|
+
"open": "/ws?id=bob-harness"
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
"name": "The incomplete token is rejected before any reservation is made",
|
|
448
|
+
"socket": "b2",
|
|
449
|
+
"send": {
|
|
450
|
+
"type": "register",
|
|
451
|
+
"secret": "vt_incomplete",
|
|
452
|
+
"authRequired": false,
|
|
453
|
+
"tunnelId": "bob-harness"
|
|
454
|
+
},
|
|
455
|
+
"status": 4003,
|
|
456
|
+
"expect": [
|
|
457
|
+
{
|
|
458
|
+
"path": "last.message",
|
|
459
|
+
"equals": "Invalid tunnel secret"
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"path": "closed.reason",
|
|
463
|
+
"equals": "Invalid tunnel secret"
|
|
464
|
+
}
|
|
465
|
+
]
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"name": "Bob reconnects with the token his account actually returned",
|
|
469
|
+
"socket": "b3",
|
|
470
|
+
"open": "/ws?id=bob-harness"
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
"name": "Bob now has his own public demo URL",
|
|
474
|
+
"socket": "b3",
|
|
475
|
+
"send": {
|
|
476
|
+
"type": "register",
|
|
477
|
+
"secret": "{bob}",
|
|
478
|
+
"authRequired": false,
|
|
479
|
+
"tunnelId": "bob-harness"
|
|
480
|
+
},
|
|
481
|
+
"capture": {
|
|
482
|
+
"bobPublic": "last.url"
|
|
483
|
+
},
|
|
484
|
+
"expect": [
|
|
485
|
+
{
|
|
486
|
+
"path": "last.type",
|
|
487
|
+
"equals": "registered"
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
"path": "last.url",
|
|
491
|
+
"equals": "https://bob-harness.voltertest.xyz"
|
|
492
|
+
}
|
|
493
|
+
]
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"name": "Bob requests one last local command while Ada is ending the demo",
|
|
497
|
+
"method": "GET",
|
|
498
|
+
"path": "{public}/last-command",
|
|
499
|
+
"defer": "last-command"
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
"name": "Harness receives the last command before the laptop disconnects",
|
|
503
|
+
"socket": "h3",
|
|
504
|
+
"expect": [
|
|
505
|
+
{
|
|
506
|
+
"path": "last.type",
|
|
507
|
+
"equals": "request"
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"path": "last.path",
|
|
511
|
+
"equals": "/last-command"
|
|
512
|
+
}
|
|
513
|
+
]
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
"name": "Ada ends her demo and closes its connector",
|
|
517
|
+
"socket": "h3",
|
|
518
|
+
"close": true,
|
|
519
|
+
"status": 1000
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
"name": "Bob's pending command reports the disconnected connector",
|
|
523
|
+
"method": "GET",
|
|
524
|
+
"path": "{public}/last-command",
|
|
525
|
+
"await": "last-command",
|
|
526
|
+
"status": 502,
|
|
527
|
+
"expect": [
|
|
528
|
+
{
|
|
529
|
+
"path": "",
|
|
530
|
+
"equals": "Tunnel disconnected"
|
|
531
|
+
}
|
|
532
|
+
]
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
"name": "Bob checks his own local relay connection before closing it",
|
|
536
|
+
"method": "GET",
|
|
537
|
+
"path": "http://localhost/check?__tunnel=bob-harness",
|
|
538
|
+
"defer": "bob-check"
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
"name": "Bob's connector receives the direct-host diagnostic with the routing parameter removed",
|
|
542
|
+
"socket": "b3",
|
|
543
|
+
"capture": {
|
|
544
|
+
"bobCheck": "last.reqId"
|
|
545
|
+
},
|
|
546
|
+
"expect": [
|
|
547
|
+
{
|
|
548
|
+
"path": "last.path",
|
|
549
|
+
"equals": "/check"
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
"path": "last.body",
|
|
553
|
+
"equals": null
|
|
554
|
+
}
|
|
555
|
+
]
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"name": "Bob's local server answers the diagnostic",
|
|
559
|
+
"socket": "b3",
|
|
560
|
+
"send": {
|
|
561
|
+
"type": "response",
|
|
562
|
+
"reqId": "{bobCheck}",
|
|
563
|
+
"status": 200,
|
|
564
|
+
"headers": {},
|
|
565
|
+
"body": "Qm9iIGNvbm5lY3RlZA=="
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
"name": "Bob confirms his separate tunnel still works",
|
|
570
|
+
"method": "GET",
|
|
571
|
+
"path": "http://localhost/check?__tunnel=bob-harness",
|
|
572
|
+
"await": "bob-check",
|
|
573
|
+
"status": 200,
|
|
574
|
+
"expect": [
|
|
575
|
+
{
|
|
576
|
+
"path": "",
|
|
577
|
+
"equals": "Bob connected"
|
|
578
|
+
}
|
|
579
|
+
]
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
"name": "Bob closes his own connector after the demo",
|
|
583
|
+
"socket": "b3",
|
|
584
|
+
"close": true,
|
|
585
|
+
"status": 1000
|
|
586
|
+
}
|
|
587
|
+
]
|
|
588
|
+
}
|