@yh-ui/yh-ui 1.0.59 → 1.0.61
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/dist/chunks/index.mjs +1514 -0
- package/dist/chunks/mermaid.mjs +70304 -0
- package/dist/full.mjs +62917 -16
- package/dist/index.cjs +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +7 -7
|
@@ -0,0 +1,1514 @@
|
|
|
1
|
+
const DEFAULT_EDITOR_ORIGIN = 'https://stackblitz.com';
|
|
2
|
+
const SEARCH_PARAM_AUTH_CODE = 'code';
|
|
3
|
+
const SEARCH_PARAM_ERROR = 'error';
|
|
4
|
+
const SEARCH_PARAM_ERROR_DESCRIPTION = 'error_description';
|
|
5
|
+
const BROADCAST_CHANNEL_NAME = '__wc_api_bc__';
|
|
6
|
+
const STORAGE_TOKENS_NAME = '__wc_api_tokens__';
|
|
7
|
+
const STORAGE_CODE_VERIFIER_NAME = '__wc_api_verifier__';
|
|
8
|
+
const STORAGE_POPUP_NAME = '__wc_api_popup__';
|
|
9
|
+
|
|
10
|
+
class TypedEventTarget {
|
|
11
|
+
_bus = new EventTarget();
|
|
12
|
+
listen(listener) {
|
|
13
|
+
function wrappedListener(event) {
|
|
14
|
+
listener(event.data);
|
|
15
|
+
}
|
|
16
|
+
this._bus.addEventListener('message', wrappedListener);
|
|
17
|
+
return () => this._bus.removeEventListener('message', wrappedListener);
|
|
18
|
+
}
|
|
19
|
+
fireEvent(data) {
|
|
20
|
+
this._bus.dispatchEvent(new MessageEvent('message', { data }));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const IGNORED_ERROR = new Error();
|
|
25
|
+
IGNORED_ERROR.stack = '';
|
|
26
|
+
const accessTokenChangedListeners = new TypedEventTarget();
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
class Tokens {
|
|
31
|
+
origin;
|
|
32
|
+
refresh;
|
|
33
|
+
access;
|
|
34
|
+
expires;
|
|
35
|
+
_revoked = new AbortController();
|
|
36
|
+
constructor(
|
|
37
|
+
// editor origin that those tokens are bound to, mostly used for development
|
|
38
|
+
origin,
|
|
39
|
+
// token to use to get a new access token
|
|
40
|
+
refresh,
|
|
41
|
+
// token to provide to webcontainer
|
|
42
|
+
access,
|
|
43
|
+
// time in UTC when the token expires
|
|
44
|
+
expires) {
|
|
45
|
+
this.origin = origin;
|
|
46
|
+
this.refresh = refresh;
|
|
47
|
+
this.access = access;
|
|
48
|
+
this.expires = expires;
|
|
49
|
+
}
|
|
50
|
+
async activate(onFailedRefresh) {
|
|
51
|
+
if (this._revoked.signal.aborted) {
|
|
52
|
+
throw new Error('Token revoked');
|
|
53
|
+
}
|
|
54
|
+
// if the access token expired we fetch a new one
|
|
55
|
+
if (this.expires < Date.now()) {
|
|
56
|
+
if (!(await this._fetchNewAccessToken())) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this._sync();
|
|
61
|
+
this._startRefreshTokensLoop(onFailedRefresh);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
async revoke(clientId, ignoreRevokeError) {
|
|
65
|
+
this._revoked.abort();
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(`${this.origin}/oauth/revoke`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
71
|
+
},
|
|
72
|
+
body: new URLSearchParams({ token: this.refresh, token_type_hint: 'refresh_token', client_id: clientId }),
|
|
73
|
+
mode: 'cors',
|
|
74
|
+
});
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(`Failed to logout`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!ignoreRevokeError) {
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
clearTokensInStorage();
|
|
85
|
+
}
|
|
86
|
+
static fromStorage() {
|
|
87
|
+
const savedTokens = readTokensFromStorage();
|
|
88
|
+
if (!savedTokens) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return new Tokens(savedTokens.origin, savedTokens.refresh, savedTokens.access, savedTokens.expires);
|
|
92
|
+
}
|
|
93
|
+
static async fromAuthCode({ editorOrigin, clientId, codeVerifier, authCode, redirectUri, }) {
|
|
94
|
+
const response = await fetch(`${editorOrigin}/oauth/token`, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: {
|
|
97
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
98
|
+
},
|
|
99
|
+
body: new URLSearchParams({
|
|
100
|
+
client_id: clientId,
|
|
101
|
+
code: authCode,
|
|
102
|
+
code_verifier: codeVerifier,
|
|
103
|
+
grant_type: 'authorization_code',
|
|
104
|
+
redirect_uri: redirectUri,
|
|
105
|
+
}),
|
|
106
|
+
mode: 'cors',
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`Failed to fetch token: ${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
const tokenResponse = await response.json();
|
|
112
|
+
assertTokenResponse(tokenResponse);
|
|
113
|
+
const { access_token: access, refresh_token: refresh } = tokenResponse;
|
|
114
|
+
const expires = getExpiresFromTokenResponse(tokenResponse);
|
|
115
|
+
return new Tokens(editorOrigin, refresh, access, expires);
|
|
116
|
+
}
|
|
117
|
+
async _fetchNewAccessToken() {
|
|
118
|
+
try {
|
|
119
|
+
const response = await fetch(`${this.origin}/oauth/token`, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: {
|
|
122
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
123
|
+
},
|
|
124
|
+
body: new URLSearchParams({
|
|
125
|
+
grant_type: 'refresh_token',
|
|
126
|
+
refresh_token: this.refresh,
|
|
127
|
+
}),
|
|
128
|
+
mode: 'cors',
|
|
129
|
+
signal: this._revoked.signal,
|
|
130
|
+
});
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
throw IGNORED_ERROR;
|
|
133
|
+
}
|
|
134
|
+
const tokenResponse = await response.json();
|
|
135
|
+
assertTokenResponse(tokenResponse);
|
|
136
|
+
const { access_token: access, refresh_token: refresh } = tokenResponse;
|
|
137
|
+
const expires = getExpiresFromTokenResponse(tokenResponse);
|
|
138
|
+
this.access = access;
|
|
139
|
+
this.expires = expires;
|
|
140
|
+
this.refresh = refresh;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
clearTokensInStorage();
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
_sync() {
|
|
149
|
+
persistTokensInStorage(this);
|
|
150
|
+
fireAccessTokenChanged(this.access);
|
|
151
|
+
}
|
|
152
|
+
async _startRefreshTokensLoop(onFailedRefresh) {
|
|
153
|
+
while (true) {
|
|
154
|
+
const expiresIn = this.expires - Date.now() - 1000;
|
|
155
|
+
await wait(Math.max(expiresIn, 1000));
|
|
156
|
+
if (this._revoked.signal.aborted) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!this._fetchNewAccessToken()) {
|
|
160
|
+
onFailedRefresh();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
this._sync();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
function clearTokensInStorage() {
|
|
171
|
+
localStorage.removeItem(STORAGE_TOKENS_NAME);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* @internal
|
|
175
|
+
*/
|
|
176
|
+
function addAccessTokenChangedListener(listener) {
|
|
177
|
+
return accessTokenChangedListeners.listen(listener);
|
|
178
|
+
}
|
|
179
|
+
function readTokensFromStorage() {
|
|
180
|
+
const serializedTokens = localStorage.getItem(STORAGE_TOKENS_NAME);
|
|
181
|
+
if (!serializedTokens) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return JSON.parse(serializedTokens);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function persistTokensInStorage(tokens) {
|
|
192
|
+
localStorage.setItem(STORAGE_TOKENS_NAME, JSON.stringify(tokens));
|
|
193
|
+
}
|
|
194
|
+
function getExpiresFromTokenResponse({ created_at, expires_in }) {
|
|
195
|
+
return (created_at + expires_in) * 1000;
|
|
196
|
+
}
|
|
197
|
+
function assertTokenResponse(token) {
|
|
198
|
+
if (typeof token !== 'object' || !token) {
|
|
199
|
+
throw new Error('Invalid Token Response');
|
|
200
|
+
}
|
|
201
|
+
if (typeof token.access_token !== 'string' ||
|
|
202
|
+
typeof token.refresh_token !== 'string' ||
|
|
203
|
+
typeof token.created_at !== 'number' ||
|
|
204
|
+
typeof token.expires_in !== 'number') {
|
|
205
|
+
throw new Error('Invalid Token Response');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function wait(ms) {
|
|
209
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
210
|
+
}
|
|
211
|
+
function fireAccessTokenChanged(accessToken) {
|
|
212
|
+
accessTokenChangedListeners.fireEvent(accessToken);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const params = {};
|
|
216
|
+
let editorOrigin = null;
|
|
217
|
+
const iframeSettings = {
|
|
218
|
+
get editorOrigin() {
|
|
219
|
+
if (editorOrigin == null) {
|
|
220
|
+
editorOrigin = new URL(globalThis.WEBCONTAINER_API_IFRAME_URL ?? DEFAULT_EDITOR_ORIGIN).origin;
|
|
221
|
+
}
|
|
222
|
+
return editorOrigin;
|
|
223
|
+
},
|
|
224
|
+
set editorOrigin(newOrigin) {
|
|
225
|
+
editorOrigin = new URL(newOrigin).origin;
|
|
226
|
+
},
|
|
227
|
+
setQueryParam(key, value) {
|
|
228
|
+
params[key] = value;
|
|
229
|
+
},
|
|
230
|
+
get url() {
|
|
231
|
+
const url = new URL(this.editorOrigin);
|
|
232
|
+
url.pathname = '/headless';
|
|
233
|
+
for (const param in params) {
|
|
234
|
+
url.searchParams.set(param, params[param]);
|
|
235
|
+
}
|
|
236
|
+
url.searchParams.set('version', "1.6.4");
|
|
237
|
+
return url;
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Implementation of https://www.rfc-editor.org/rfc/rfc7636#section-4.2 that can
|
|
243
|
+
* run in the browser.
|
|
244
|
+
*
|
|
245
|
+
* @internal
|
|
246
|
+
*
|
|
247
|
+
* @param input Code verifier.
|
|
248
|
+
*/
|
|
249
|
+
async function S256(input) {
|
|
250
|
+
// input here is assumed to match https://www.rfc-editor.org/rfc/rfc3986#section-2.3
|
|
251
|
+
const ascii = new TextEncoder().encode(input);
|
|
252
|
+
const sha256 = new Uint8Array(await crypto.subtle.digest('SHA-256', ascii));
|
|
253
|
+
// base64url encode, based on https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem
|
|
254
|
+
return btoa(sha256.reduce((binary, byte) => binary + String.fromCodePoint(byte), ''))
|
|
255
|
+
.replace(/\+/g, '-')
|
|
256
|
+
.replace(/\//g, '_')
|
|
257
|
+
.replace(/=+$/, '');
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Implementation of https://www.rfc-editor.org/rfc/rfc7636#section-4.1 with
|
|
261
|
+
* a slight deviation:
|
|
262
|
+
*
|
|
263
|
+
* - We use 128 characters (it's expected to be between 43 and 128)
|
|
264
|
+
* - We use 64 characters instead of 66
|
|
265
|
+
*
|
|
266
|
+
* So the entropy is lower given the space size is 64^128 instead of 66^128.
|
|
267
|
+
* It still satisfies the entropy constraint given that 64^128 > 66^43.
|
|
268
|
+
*
|
|
269
|
+
* @internal
|
|
270
|
+
*/
|
|
271
|
+
function newCodeVerifier() {
|
|
272
|
+
const random = new Uint8Array(96);
|
|
273
|
+
crypto.getRandomValues(random);
|
|
274
|
+
let codeVerifier = '';
|
|
275
|
+
for (let i = 0; i < 32; ++i) {
|
|
276
|
+
codeVerifier += nextFourChars(random[3 * i + 0], random[3 * i + 1], random[3 * i + 2]);
|
|
277
|
+
}
|
|
278
|
+
return codeVerifier;
|
|
279
|
+
}
|
|
280
|
+
function nextFourChars(byte1, byte2, byte3) {
|
|
281
|
+
const char1 = byte1 >> 2;
|
|
282
|
+
const char2 = ((byte1 & 3) << 4) | (byte2 >> 4);
|
|
283
|
+
const char3 = (byte2 & 15) | ((byte3 & 192) >> 2);
|
|
284
|
+
const char4 = byte3 & 63;
|
|
285
|
+
return [char1, char2, char3, char4].map(unreservedCharacters).join('');
|
|
286
|
+
}
|
|
287
|
+
function unreservedCharacters(code) {
|
|
288
|
+
let offset;
|
|
289
|
+
if (code < 26) {
|
|
290
|
+
offset = code + 65; // [A-Z]
|
|
291
|
+
}
|
|
292
|
+
else if (code < 52) {
|
|
293
|
+
offset = code - 26 + 97; // [a-z]
|
|
294
|
+
}
|
|
295
|
+
else if (code < 62) {
|
|
296
|
+
offset = code - 52 + 48; // [0-9]
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
offset = code === 62 ? 30 /* _ */ : 45 /* - */;
|
|
300
|
+
}
|
|
301
|
+
return String.fromCharCode(offset);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @internal
|
|
306
|
+
*/
|
|
307
|
+
function resettablePromise() {
|
|
308
|
+
let resolve;
|
|
309
|
+
let promise;
|
|
310
|
+
function reset() {
|
|
311
|
+
promise = new Promise((_resolve) => (resolve = _resolve));
|
|
312
|
+
}
|
|
313
|
+
reset();
|
|
314
|
+
return {
|
|
315
|
+
get promise() {
|
|
316
|
+
return promise;
|
|
317
|
+
},
|
|
318
|
+
resolve(value) {
|
|
319
|
+
return resolve(value);
|
|
320
|
+
},
|
|
321
|
+
reset,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @internal
|
|
327
|
+
*/
|
|
328
|
+
const authState = {
|
|
329
|
+
initialized: false,
|
|
330
|
+
bootCalled: false,
|
|
331
|
+
authComplete: resettablePromise(),
|
|
332
|
+
clientId: '',
|
|
333
|
+
oauthScope: '',
|
|
334
|
+
broadcastChannel: null,
|
|
335
|
+
get editorOrigin() {
|
|
336
|
+
return iframeSettings.editorOrigin;
|
|
337
|
+
},
|
|
338
|
+
tokens: null,
|
|
339
|
+
};
|
|
340
|
+
const authFailedListeners = new TypedEventTarget();
|
|
341
|
+
const loggedOutListeners = new TypedEventTarget();
|
|
342
|
+
function broadcastMessage(message) {
|
|
343
|
+
if (!authState.broadcastChannel) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
authState.broadcastChannel.postMessage(message);
|
|
347
|
+
// check if we are in a popup mode
|
|
348
|
+
if (localStorage.getItem(STORAGE_POPUP_NAME) === 'true' && message.type !== 'auth-logout') {
|
|
349
|
+
localStorage.removeItem(STORAGE_POPUP_NAME);
|
|
350
|
+
// wait a tick to make sure the posted message has been sent
|
|
351
|
+
setTimeout(() => {
|
|
352
|
+
window.close();
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const auth$1 = {
|
|
357
|
+
init({ editorOrigin, clientId, scope }) {
|
|
358
|
+
if (authState.initialized) {
|
|
359
|
+
throw new Error('Init should only be called once');
|
|
360
|
+
}
|
|
361
|
+
if (authState.bootCalled) {
|
|
362
|
+
throw new Error('`auth.init` should always be called before `WebContainer.boot`');
|
|
363
|
+
}
|
|
364
|
+
authState.initialized = true;
|
|
365
|
+
authState.tokens = Tokens.fromStorage();
|
|
366
|
+
authState.clientId = clientId;
|
|
367
|
+
authState.oauthScope = scope;
|
|
368
|
+
authState.broadcastChannel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);
|
|
369
|
+
// configure iframe url
|
|
370
|
+
iframeSettings.setQueryParam('client_id', clientId);
|
|
371
|
+
if (editorOrigin) {
|
|
372
|
+
iframeSettings.editorOrigin = new URL(editorOrigin).origin;
|
|
373
|
+
}
|
|
374
|
+
loggedOutListeners.listen(() => authState.authComplete.reset());
|
|
375
|
+
// if authentication or logout are done in another page, we want to reflect the state on this page as well
|
|
376
|
+
authState.broadcastChannel.addEventListener('message', onChannelMessage);
|
|
377
|
+
async function onChannelMessage(event) {
|
|
378
|
+
const typedEvent = event.data;
|
|
379
|
+
if (typedEvent.type === 'auth-complete') {
|
|
380
|
+
authState.tokens = Tokens.fromStorage();
|
|
381
|
+
// we ignore the possible error here because they can't have expired just yet
|
|
382
|
+
await authState.tokens.activate(onFailedTokenRefresh);
|
|
383
|
+
authState.authComplete.resolve();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (typedEvent.type === 'auth-failed') {
|
|
387
|
+
authFailedListeners.fireEvent(typedEvent);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (typedEvent.type === 'auth-logout') {
|
|
391
|
+
loggedOutListeners.fireEvent();
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (authState.tokens) {
|
|
396
|
+
const tokens = authState.tokens;
|
|
397
|
+
if (tokens.origin === authState.editorOrigin) {
|
|
398
|
+
/**
|
|
399
|
+
* Here we assume that the refresh token never expires which
|
|
400
|
+
* might not be correct. If that is the case though, we will
|
|
401
|
+
* emit a 'logged-out' event to signal that the user has been
|
|
402
|
+
* logged out, which could also happen at a later time anyway.
|
|
403
|
+
*
|
|
404
|
+
* Because this flow is done entirely locally, we do not broadcast
|
|
405
|
+
* anything to the other tabs. They should be performing a similar
|
|
406
|
+
* check.
|
|
407
|
+
*/
|
|
408
|
+
(async () => {
|
|
409
|
+
const success = await tokens.activate(onFailedTokenRefresh);
|
|
410
|
+
if (!success) {
|
|
411
|
+
// if we got new token in the meantime we discard this error
|
|
412
|
+
if (authState.tokens !== tokens) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
loggedOutListeners.fireEvent();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
authState.authComplete.resolve();
|
|
419
|
+
})();
|
|
420
|
+
return { status: 'authorized' };
|
|
421
|
+
}
|
|
422
|
+
clearTokensInStorage();
|
|
423
|
+
authState.tokens = null;
|
|
424
|
+
}
|
|
425
|
+
const locationURL = new URL(window.location.href);
|
|
426
|
+
const { searchParams } = locationURL;
|
|
427
|
+
const updateURL = () => window.history.replaceState({}, document.title, locationURL);
|
|
428
|
+
// check for errors first, aka the user declined the authorisation or stackblitz did
|
|
429
|
+
if (searchParams.has(SEARCH_PARAM_ERROR)) {
|
|
430
|
+
const error = searchParams.get(SEARCH_PARAM_ERROR);
|
|
431
|
+
const description = searchParams.get(SEARCH_PARAM_ERROR_DESCRIPTION);
|
|
432
|
+
searchParams.delete(SEARCH_PARAM_ERROR);
|
|
433
|
+
searchParams.delete(SEARCH_PARAM_ERROR_DESCRIPTION);
|
|
434
|
+
updateURL();
|
|
435
|
+
broadcastMessage({ type: 'auth-failed', error, description });
|
|
436
|
+
return { status: 'auth-failed', error, description };
|
|
437
|
+
}
|
|
438
|
+
// if there's an auth code
|
|
439
|
+
if (searchParams.has(SEARCH_PARAM_AUTH_CODE)) {
|
|
440
|
+
const authCode = searchParams.get(SEARCH_PARAM_AUTH_CODE);
|
|
441
|
+
const editorOrigin = authState.editorOrigin;
|
|
442
|
+
searchParams.delete(SEARCH_PARAM_AUTH_CODE);
|
|
443
|
+
updateURL();
|
|
444
|
+
const codeVerifier = localStorage.getItem(STORAGE_CODE_VERIFIER_NAME);
|
|
445
|
+
if (!codeVerifier) {
|
|
446
|
+
return { status: 'need-auth' };
|
|
447
|
+
}
|
|
448
|
+
localStorage.removeItem(STORAGE_CODE_VERIFIER_NAME);
|
|
449
|
+
Tokens.fromAuthCode({
|
|
450
|
+
editorOrigin,
|
|
451
|
+
clientId: authState.clientId,
|
|
452
|
+
authCode,
|
|
453
|
+
codeVerifier,
|
|
454
|
+
redirectUri: defaultRedirectUri(),
|
|
455
|
+
})
|
|
456
|
+
.then(async (tokens) => {
|
|
457
|
+
authState.tokens = tokens;
|
|
458
|
+
assertAuthTokens(authState.tokens);
|
|
459
|
+
const success = await authState.tokens.activate(onFailedTokenRefresh);
|
|
460
|
+
// if authentication failed we throw, and we'll mark auth as failed
|
|
461
|
+
if (!success) {
|
|
462
|
+
throw new Error();
|
|
463
|
+
}
|
|
464
|
+
authState.authComplete.resolve();
|
|
465
|
+
broadcastMessage({ type: 'auth-complete' });
|
|
466
|
+
})
|
|
467
|
+
.catch((error) => {
|
|
468
|
+
// this should never happen unless the rails app is now down for some reason?
|
|
469
|
+
console.error(error);
|
|
470
|
+
// treat it as a logged out event so that the user can retry to login
|
|
471
|
+
loggedOutListeners.fireEvent();
|
|
472
|
+
broadcastMessage({ type: 'auth-logout' });
|
|
473
|
+
});
|
|
474
|
+
return { status: 'authorized' };
|
|
475
|
+
}
|
|
476
|
+
return { status: 'need-auth' };
|
|
477
|
+
},
|
|
478
|
+
async startAuthFlow({ popup } = {}) {
|
|
479
|
+
if (!authState.initialized) {
|
|
480
|
+
throw new Error('auth.init must be called first');
|
|
481
|
+
}
|
|
482
|
+
if (popup) {
|
|
483
|
+
localStorage.setItem(STORAGE_POPUP_NAME, 'true');
|
|
484
|
+
const height = 500;
|
|
485
|
+
const width = 620;
|
|
486
|
+
const left = window.screenLeft + (window.outerWidth - width) / 2;
|
|
487
|
+
const top = window.screenTop + (window.outerHeight - height) / 2;
|
|
488
|
+
window.open(await generateOAuthRequest(), '_blank', `popup,width=${width},height=${height},left=${left},top=${top}`);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
window.location.href = await generateOAuthRequest();
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
async logout({ ignoreRevokeError } = {}) {
|
|
495
|
+
await authState.tokens?.revoke(authState.clientId, ignoreRevokeError ?? false);
|
|
496
|
+
loggedOutListeners.fireEvent();
|
|
497
|
+
broadcastMessage({ type: 'auth-logout' });
|
|
498
|
+
},
|
|
499
|
+
loggedIn() {
|
|
500
|
+
return authState.authComplete.promise;
|
|
501
|
+
},
|
|
502
|
+
on(event, listener) {
|
|
503
|
+
switch (event) {
|
|
504
|
+
case 'auth-failed': {
|
|
505
|
+
return authFailedListeners.listen(listener);
|
|
506
|
+
}
|
|
507
|
+
case 'logged-out': {
|
|
508
|
+
return loggedOutListeners.listen(listener);
|
|
509
|
+
}
|
|
510
|
+
default: {
|
|
511
|
+
throw new Error(`Unsupported event type '${event}'.`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
function onFailedTokenRefresh() {
|
|
517
|
+
loggedOutListeners.fireEvent();
|
|
518
|
+
broadcastMessage({ type: 'auth-logout' });
|
|
519
|
+
}
|
|
520
|
+
function defaultRedirectUri() {
|
|
521
|
+
return window.location.href;
|
|
522
|
+
}
|
|
523
|
+
async function generateOAuthRequest() {
|
|
524
|
+
const codeVerifier = newCodeVerifier();
|
|
525
|
+
localStorage.setItem(STORAGE_CODE_VERIFIER_NAME, codeVerifier);
|
|
526
|
+
const codeChallenge = await S256(codeVerifier);
|
|
527
|
+
const url = new URL('/oauth/authorize', authState.editorOrigin);
|
|
528
|
+
const { searchParams } = url;
|
|
529
|
+
searchParams.append('response_type', 'code');
|
|
530
|
+
searchParams.append('client_id', authState.clientId);
|
|
531
|
+
searchParams.append('redirect_uri', defaultRedirectUri());
|
|
532
|
+
searchParams.append('scope', authState.oauthScope);
|
|
533
|
+
searchParams.append('code_challenge', codeChallenge);
|
|
534
|
+
searchParams.append('code_challenge_method', 'S256');
|
|
535
|
+
return url.toString();
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* @internal
|
|
539
|
+
*/
|
|
540
|
+
function assertAuthTokens(tokens) {
|
|
541
|
+
if (!tokens) {
|
|
542
|
+
throw new Error('Oops! Tokens is not defined when it always should be.');
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* This type is in a separate module so that localservice can import it
|
|
548
|
+
* without bundling all the other webcontainer specific stuff.
|
|
549
|
+
*/
|
|
550
|
+
var PreviewMessageType;
|
|
551
|
+
(function (PreviewMessageType) {
|
|
552
|
+
PreviewMessageType["UncaughtException"] = "PREVIEW_UNCAUGHT_EXCEPTION";
|
|
553
|
+
PreviewMessageType["UnhandledRejection"] = "PREVIEW_UNHANDLED_REJECTION";
|
|
554
|
+
PreviewMessageType["ConsoleError"] = "PREVIEW_CONSOLE_ERROR";
|
|
555
|
+
})(PreviewMessageType || (PreviewMessageType = {}));
|
|
556
|
+
|
|
557
|
+
var __defProp = Object.defineProperty;
|
|
558
|
+
var __export = (target, all) => {
|
|
559
|
+
for (var name in all)
|
|
560
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// dist/vendor/comlink.js
|
|
564
|
+
var comlink_exports = {};
|
|
565
|
+
__export(comlink_exports, {
|
|
566
|
+
createEndpoint: () => createEndpoint,
|
|
567
|
+
expose: () => expose,
|
|
568
|
+
proxy: () => proxy,
|
|
569
|
+
proxyMarker: () => proxyMarker,
|
|
570
|
+
releaseProxy: () => releaseProxy,
|
|
571
|
+
transfer: () => transfer,
|
|
572
|
+
transferHandlers: () => transferHandlers,
|
|
573
|
+
windowEndpoint: () => windowEndpoint,
|
|
574
|
+
wrap: () => wrap
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// ../../node_modules/comlink/dist/esm/comlink.mjs
|
|
578
|
+
var proxyMarker = Symbol("Comlink.proxy");
|
|
579
|
+
var createEndpoint = Symbol("Comlink.endpoint");
|
|
580
|
+
var releaseProxy = Symbol("Comlink.releaseProxy");
|
|
581
|
+
var throwMarker = Symbol("Comlink.thrown");
|
|
582
|
+
var isObject = (val) => typeof val === "object" && val !== null || typeof val === "function";
|
|
583
|
+
var proxyTransferHandler = {
|
|
584
|
+
canHandle: (val) => isObject(val) && val[proxyMarker],
|
|
585
|
+
serialize(obj) {
|
|
586
|
+
const { port1, port2 } = new MessageChannel();
|
|
587
|
+
expose(obj, port1);
|
|
588
|
+
return [port2, [port2]];
|
|
589
|
+
},
|
|
590
|
+
deserialize(port) {
|
|
591
|
+
port.start();
|
|
592
|
+
return wrap(port);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
var throwTransferHandler = {
|
|
596
|
+
canHandle: (value) => isObject(value) && throwMarker in value,
|
|
597
|
+
serialize({ value }) {
|
|
598
|
+
let serialized;
|
|
599
|
+
if (value instanceof Error) {
|
|
600
|
+
serialized = {
|
|
601
|
+
isError: true,
|
|
602
|
+
value: {
|
|
603
|
+
message: value.message,
|
|
604
|
+
name: value.name,
|
|
605
|
+
stack: value.stack
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
} else {
|
|
609
|
+
serialized = { isError: false, value };
|
|
610
|
+
}
|
|
611
|
+
return [serialized, []];
|
|
612
|
+
},
|
|
613
|
+
deserialize(serialized) {
|
|
614
|
+
if (serialized.isError) {
|
|
615
|
+
throw Object.assign(new Error(serialized.value.message), serialized.value);
|
|
616
|
+
}
|
|
617
|
+
throw serialized.value;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
var transferHandlers = /* @__PURE__ */ new Map([
|
|
621
|
+
["proxy", proxyTransferHandler],
|
|
622
|
+
["throw", throwTransferHandler]
|
|
623
|
+
]);
|
|
624
|
+
function expose(obj, ep = self) {
|
|
625
|
+
ep.addEventListener("message", function callback(ev) {
|
|
626
|
+
if (!ev || !ev.data) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const { id, type, path } = Object.assign({ path: [] }, ev.data);
|
|
630
|
+
const argumentList = (ev.data.argumentList || []).map(fromWireValue);
|
|
631
|
+
let returnValue;
|
|
632
|
+
try {
|
|
633
|
+
const parent = path.slice(0, -1).reduce((obj2, prop) => obj2[prop], obj);
|
|
634
|
+
const rawValue = path.reduce((obj2, prop) => obj2[prop], obj);
|
|
635
|
+
switch (type) {
|
|
636
|
+
case 0:
|
|
637
|
+
{
|
|
638
|
+
returnValue = rawValue;
|
|
639
|
+
}
|
|
640
|
+
break;
|
|
641
|
+
case 1:
|
|
642
|
+
{
|
|
643
|
+
parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
|
|
644
|
+
returnValue = true;
|
|
645
|
+
}
|
|
646
|
+
break;
|
|
647
|
+
case 2:
|
|
648
|
+
{
|
|
649
|
+
returnValue = rawValue.apply(parent, argumentList);
|
|
650
|
+
}
|
|
651
|
+
break;
|
|
652
|
+
case 3:
|
|
653
|
+
{
|
|
654
|
+
const value = new rawValue(...argumentList);
|
|
655
|
+
returnValue = proxy(value);
|
|
656
|
+
}
|
|
657
|
+
break;
|
|
658
|
+
case 4:
|
|
659
|
+
{
|
|
660
|
+
const { port1, port2 } = new MessageChannel();
|
|
661
|
+
expose(obj, port2);
|
|
662
|
+
returnValue = transfer(port1, [port1]);
|
|
663
|
+
}
|
|
664
|
+
break;
|
|
665
|
+
case 5:
|
|
666
|
+
{
|
|
667
|
+
returnValue = void 0;
|
|
668
|
+
}
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
} catch (value) {
|
|
672
|
+
returnValue = { value, [throwMarker]: 0 };
|
|
673
|
+
}
|
|
674
|
+
Promise.resolve(returnValue).catch((value) => {
|
|
675
|
+
return { value, [throwMarker]: 0 };
|
|
676
|
+
}).then((returnValue2) => {
|
|
677
|
+
const [wireValue, transferables] = toWireValue(returnValue2);
|
|
678
|
+
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
|
|
679
|
+
if (type === 5) {
|
|
680
|
+
ep.removeEventListener("message", callback);
|
|
681
|
+
closeEndPoint(ep);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
if (ep.start) {
|
|
686
|
+
ep.start();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function isMessagePort(endpoint) {
|
|
690
|
+
return endpoint.constructor.name === "MessagePort";
|
|
691
|
+
}
|
|
692
|
+
function closeEndPoint(endpoint) {
|
|
693
|
+
if (isMessagePort(endpoint))
|
|
694
|
+
endpoint.close();
|
|
695
|
+
}
|
|
696
|
+
function wrap(ep, target) {
|
|
697
|
+
return createProxy(ep, [], target);
|
|
698
|
+
}
|
|
699
|
+
function throwIfProxyReleased(isReleased) {
|
|
700
|
+
if (isReleased) {
|
|
701
|
+
throw new Error("Proxy has been released and is not useable");
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function createProxy(ep, path = [], target = function() {
|
|
705
|
+
}) {
|
|
706
|
+
let isProxyReleased = false;
|
|
707
|
+
const proxy2 = new Proxy(target, {
|
|
708
|
+
get(_target, prop) {
|
|
709
|
+
throwIfProxyReleased(isProxyReleased);
|
|
710
|
+
if (prop === releaseProxy) {
|
|
711
|
+
return () => {
|
|
712
|
+
return requestResponseMessage(ep, {
|
|
713
|
+
type: 5,
|
|
714
|
+
path: path.map((p) => p.toString())
|
|
715
|
+
}).then(() => {
|
|
716
|
+
closeEndPoint(ep);
|
|
717
|
+
isProxyReleased = true;
|
|
718
|
+
});
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
if (prop === "then") {
|
|
722
|
+
if (path.length === 0) {
|
|
723
|
+
return { then: () => proxy2 };
|
|
724
|
+
}
|
|
725
|
+
const r = requestResponseMessage(ep, {
|
|
726
|
+
type: 0,
|
|
727
|
+
path: path.map((p) => p.toString())
|
|
728
|
+
}).then(fromWireValue);
|
|
729
|
+
return r.then.bind(r);
|
|
730
|
+
}
|
|
731
|
+
return createProxy(ep, [...path, prop]);
|
|
732
|
+
},
|
|
733
|
+
set(_target, prop, rawValue) {
|
|
734
|
+
throwIfProxyReleased(isProxyReleased);
|
|
735
|
+
const [value, transferables] = toWireValue(rawValue);
|
|
736
|
+
return requestResponseMessage(ep, {
|
|
737
|
+
type: 1,
|
|
738
|
+
path: [...path, prop].map((p) => p.toString()),
|
|
739
|
+
value
|
|
740
|
+
}, transferables).then(fromWireValue);
|
|
741
|
+
},
|
|
742
|
+
apply(_target, _thisArg, rawArgumentList) {
|
|
743
|
+
throwIfProxyReleased(isProxyReleased);
|
|
744
|
+
const last = path[path.length - 1];
|
|
745
|
+
if (last === createEndpoint) {
|
|
746
|
+
return requestResponseMessage(ep, {
|
|
747
|
+
type: 4
|
|
748
|
+
}).then(fromWireValue);
|
|
749
|
+
}
|
|
750
|
+
if (last === "bind") {
|
|
751
|
+
return createProxy(ep, path.slice(0, -1));
|
|
752
|
+
}
|
|
753
|
+
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
754
|
+
return requestResponseMessage(ep, {
|
|
755
|
+
type: 2,
|
|
756
|
+
path: path.map((p) => p.toString()),
|
|
757
|
+
argumentList
|
|
758
|
+
}, transferables).then(fromWireValue);
|
|
759
|
+
},
|
|
760
|
+
construct(_target, rawArgumentList) {
|
|
761
|
+
throwIfProxyReleased(isProxyReleased);
|
|
762
|
+
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
763
|
+
return requestResponseMessage(ep, {
|
|
764
|
+
type: 3,
|
|
765
|
+
path: path.map((p) => p.toString()),
|
|
766
|
+
argumentList
|
|
767
|
+
}, transferables).then(fromWireValue);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
return proxy2;
|
|
771
|
+
}
|
|
772
|
+
function myFlat(arr) {
|
|
773
|
+
return Array.prototype.concat.apply([], arr);
|
|
774
|
+
}
|
|
775
|
+
function processArguments(argumentList) {
|
|
776
|
+
const processed = argumentList.map(toWireValue);
|
|
777
|
+
return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
|
|
778
|
+
}
|
|
779
|
+
var transferCache = /* @__PURE__ */ new WeakMap();
|
|
780
|
+
function transfer(obj, transfers) {
|
|
781
|
+
transferCache.set(obj, transfers);
|
|
782
|
+
return obj;
|
|
783
|
+
}
|
|
784
|
+
function proxy(obj) {
|
|
785
|
+
return Object.assign(obj, { [proxyMarker]: true });
|
|
786
|
+
}
|
|
787
|
+
function windowEndpoint(w, context = self, targetOrigin = "*") {
|
|
788
|
+
return {
|
|
789
|
+
postMessage: (msg, transferables) => w.postMessage(msg, targetOrigin, transferables),
|
|
790
|
+
addEventListener: context.addEventListener.bind(context),
|
|
791
|
+
removeEventListener: context.removeEventListener.bind(context)
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
function toWireValue(value) {
|
|
795
|
+
for (const [name, handler] of transferHandlers) {
|
|
796
|
+
if (handler.canHandle(value)) {
|
|
797
|
+
const [serializedValue, transferables] = handler.serialize(value);
|
|
798
|
+
return [
|
|
799
|
+
{
|
|
800
|
+
type: 3,
|
|
801
|
+
name,
|
|
802
|
+
value: serializedValue
|
|
803
|
+
},
|
|
804
|
+
transferables
|
|
805
|
+
];
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return [
|
|
809
|
+
{
|
|
810
|
+
type: 0,
|
|
811
|
+
value
|
|
812
|
+
},
|
|
813
|
+
transferCache.get(value) || []
|
|
814
|
+
];
|
|
815
|
+
}
|
|
816
|
+
function fromWireValue(value) {
|
|
817
|
+
switch (value.type) {
|
|
818
|
+
case 3:
|
|
819
|
+
return transferHandlers.get(value.name).deserialize(value.value);
|
|
820
|
+
case 0:
|
|
821
|
+
return value.value;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function requestResponseMessage(ep, msg, transfers) {
|
|
825
|
+
return new Promise((resolve) => {
|
|
826
|
+
const id = generateUUID();
|
|
827
|
+
ep.addEventListener("message", function l(ev) {
|
|
828
|
+
if (!ev.data || !ev.data.id || ev.data.id !== id) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
ep.removeEventListener("message", l);
|
|
832
|
+
resolve(ev.data);
|
|
833
|
+
});
|
|
834
|
+
if (ep.start) {
|
|
835
|
+
ep.start();
|
|
836
|
+
}
|
|
837
|
+
ep.postMessage(Object.assign({ id }, msg), transfers);
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
function generateUUID() {
|
|
841
|
+
return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-");
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* This function reloads the provided iframe.
|
|
846
|
+
*
|
|
847
|
+
* @param preview The iframe page to reload.
|
|
848
|
+
* @param hardRefreshTimeout The timeout after which the preview is reset if it hasn't responded to the reload event.
|
|
849
|
+
*/
|
|
850
|
+
function reloadPreview(preview, hardRefreshTimeout = 200) {
|
|
851
|
+
const { port1, port2 } = new MessageChannel();
|
|
852
|
+
let resolve;
|
|
853
|
+
const promise = new Promise((_resolve) => {
|
|
854
|
+
resolve = _resolve;
|
|
855
|
+
});
|
|
856
|
+
const done = () => {
|
|
857
|
+
resolve();
|
|
858
|
+
port2.close();
|
|
859
|
+
};
|
|
860
|
+
const timeout = setTimeout(() => {
|
|
861
|
+
const iframeSrc = preview.src;
|
|
862
|
+
preview.src = iframeSrc;
|
|
863
|
+
done();
|
|
864
|
+
}, hardRefreshTimeout);
|
|
865
|
+
port2.addEventListener('message', (event) => {
|
|
866
|
+
const data = event.data;
|
|
867
|
+
if (data == null || typeof data !== 'object') {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (data.type === 'LOCALSERVICE_WINDOW_RELOADED') {
|
|
871
|
+
clearTimeout(timeout);
|
|
872
|
+
done();
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
preview.contentWindow?.postMessage({
|
|
876
|
+
type: 'LOCALSERVICE_RELOAD_WINDOW',
|
|
877
|
+
callback: port1,
|
|
878
|
+
}, '*', [port1]);
|
|
879
|
+
return promise;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const PREVIEW_MESSAGE_TYPES = [
|
|
883
|
+
PreviewMessageType.ConsoleError,
|
|
884
|
+
PreviewMessageType.UncaughtException,
|
|
885
|
+
PreviewMessageType.UnhandledRejection,
|
|
886
|
+
];
|
|
887
|
+
function isPreviewMessage(data) {
|
|
888
|
+
if (data == null || typeof data !== 'object') {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
if (!('type' in data) || !PREVIEW_MESSAGE_TYPES.includes(data.type)) {
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* @internal
|
|
899
|
+
*/
|
|
900
|
+
function nullPrototype(source) {
|
|
901
|
+
const prototype = Object.create(null);
|
|
902
|
+
if (!source) {
|
|
903
|
+
return prototype;
|
|
904
|
+
}
|
|
905
|
+
return Object.assign(prototype, source);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* @internal
|
|
910
|
+
*/
|
|
911
|
+
function toInternalFileSystemTree(tree) {
|
|
912
|
+
const newTree = { d: {} };
|
|
913
|
+
for (const name of Object.keys(tree)) {
|
|
914
|
+
const entry = tree[name];
|
|
915
|
+
if ('file' in entry) {
|
|
916
|
+
if ('symlink' in entry.file) {
|
|
917
|
+
newTree.d[name] = { f: { l: entry.file.symlink } };
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
const contents = entry.file.contents;
|
|
921
|
+
const stringContents = typeof contents === 'string' ? contents : toLatin1(contents);
|
|
922
|
+
const binary = typeof contents === 'string' ? {} : { b: true };
|
|
923
|
+
newTree.d[name] = { f: { c: stringContents, ...binary } };
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const newEntry = toInternalFileSystemTree(entry.directory);
|
|
927
|
+
newTree.d[name] = newEntry;
|
|
928
|
+
}
|
|
929
|
+
return newTree;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* @internal
|
|
933
|
+
*/
|
|
934
|
+
function toExternalFileSystemTree(tree) {
|
|
935
|
+
const newTree = nullPrototype();
|
|
936
|
+
if ('f' in tree) {
|
|
937
|
+
throw new Error('It is not possible to export a single file in the JSON format.');
|
|
938
|
+
}
|
|
939
|
+
if ('d' in tree) {
|
|
940
|
+
for (const name of Object.keys(tree.d)) {
|
|
941
|
+
const entry = tree.d[name];
|
|
942
|
+
if ('d' in entry) {
|
|
943
|
+
newTree[name] = nullPrototype({
|
|
944
|
+
directory: toExternalFileSystemTree(entry),
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
else if ('f' in entry) {
|
|
948
|
+
if ('c' in entry.f) {
|
|
949
|
+
newTree[name] = nullPrototype({
|
|
950
|
+
file: nullPrototype({
|
|
951
|
+
contents: entry.f.b ? fromBinaryString(entry.f.c) : entry.f.c,
|
|
952
|
+
}),
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
else if ('l' in entry.f) {
|
|
956
|
+
newTree[name] = nullPrototype({
|
|
957
|
+
file: nullPrototype({
|
|
958
|
+
symlink: entry.f.l,
|
|
959
|
+
}),
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return newTree;
|
|
966
|
+
}
|
|
967
|
+
function fromBinaryString(s) {
|
|
968
|
+
const encoded = new Uint8Array(s.length);
|
|
969
|
+
for (let i = 0; i < s.length; i++) {
|
|
970
|
+
encoded[i] = s[i].charCodeAt(0);
|
|
971
|
+
}
|
|
972
|
+
return encoded;
|
|
973
|
+
}
|
|
974
|
+
function toLatin1(bytes) {
|
|
975
|
+
let decoded = '';
|
|
976
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
977
|
+
decoded += String.fromCharCode(bytes[i]);
|
|
978
|
+
}
|
|
979
|
+
return decoded;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* The WebContainer Public API allows you build custom applications on top of an in-browser Node.js runtime.
|
|
984
|
+
*
|
|
985
|
+
* Its main entrypoint is the {@link WebContainer} class.
|
|
986
|
+
*
|
|
987
|
+
* @packageDocumentation
|
|
988
|
+
*/
|
|
989
|
+
const auth = auth$1;
|
|
990
|
+
let bootPromise = null;
|
|
991
|
+
let cachedServerPromise = null;
|
|
992
|
+
let cachedBootOptions = {};
|
|
993
|
+
const decoder = new TextDecoder();
|
|
994
|
+
const encoder = new TextEncoder();
|
|
995
|
+
/**
|
|
996
|
+
* The main export of this library. An instance of `WebContainer` represents a runtime
|
|
997
|
+
* ready to be used.
|
|
998
|
+
*/
|
|
999
|
+
class WebContainer {
|
|
1000
|
+
_instance;
|
|
1001
|
+
_runtimeInfo;
|
|
1002
|
+
/**
|
|
1003
|
+
* Gives access to the underlying file system.
|
|
1004
|
+
*/
|
|
1005
|
+
fs;
|
|
1006
|
+
/** @internal */
|
|
1007
|
+
static _instance = null;
|
|
1008
|
+
/** @internal */
|
|
1009
|
+
static _teardownPromise = null;
|
|
1010
|
+
_tornDown = false;
|
|
1011
|
+
_unsubscribeFromTokenChangedListener = () => { };
|
|
1012
|
+
/** @internal */
|
|
1013
|
+
constructor(
|
|
1014
|
+
/** @internal */
|
|
1015
|
+
_instance, fs, previewScript,
|
|
1016
|
+
/** @internal */
|
|
1017
|
+
_runtimeInfo) {
|
|
1018
|
+
this._instance = _instance;
|
|
1019
|
+
this._runtimeInfo = _runtimeInfo;
|
|
1020
|
+
this.fs = new FileSystemAPIClient(fs);
|
|
1021
|
+
// forward the credentials to webcontainer if needed
|
|
1022
|
+
if (authState.initialized) {
|
|
1023
|
+
this._unsubscribeFromTokenChangedListener = addAccessTokenChangedListener((accessToken) => {
|
|
1024
|
+
this._instance.setCredentials({ accessToken, editorOrigin: authState.editorOrigin });
|
|
1025
|
+
});
|
|
1026
|
+
(async () => {
|
|
1027
|
+
await authState.authComplete.promise;
|
|
1028
|
+
if (this._tornDown) {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
assertAuthTokens(authState.tokens);
|
|
1032
|
+
await this._instance.setCredentials({
|
|
1033
|
+
accessToken: authState.tokens.access,
|
|
1034
|
+
editorOrigin: authState.editorOrigin,
|
|
1035
|
+
});
|
|
1036
|
+
})().catch((error) => {
|
|
1037
|
+
// print the error as this is likely a bug in webcontainer
|
|
1038
|
+
console.error(error);
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
async spawn(command, optionsOrArgs, options) {
|
|
1043
|
+
let args = [];
|
|
1044
|
+
if (Array.isArray(optionsOrArgs)) {
|
|
1045
|
+
args = optionsOrArgs;
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
options = optionsOrArgs;
|
|
1049
|
+
}
|
|
1050
|
+
let output = undefined;
|
|
1051
|
+
let outputStream = new ReadableStream();
|
|
1052
|
+
if (options?.output !== false) {
|
|
1053
|
+
const result = streamWithPush();
|
|
1054
|
+
output = result.push;
|
|
1055
|
+
outputStream = result.stream;
|
|
1056
|
+
}
|
|
1057
|
+
let stdout = undefined;
|
|
1058
|
+
let stdoutStream;
|
|
1059
|
+
let stderr = undefined;
|
|
1060
|
+
let stderrStream;
|
|
1061
|
+
const wrappedOutput = proxyListener(binaryListener(output));
|
|
1062
|
+
const wrappedStdout = proxyListener(binaryListener(stdout));
|
|
1063
|
+
const wrappedStderr = proxyListener(binaryListener(stderr));
|
|
1064
|
+
const process = await this._instance.run({
|
|
1065
|
+
command,
|
|
1066
|
+
args,
|
|
1067
|
+
cwd: options?.cwd,
|
|
1068
|
+
env: options?.env,
|
|
1069
|
+
terminal: options?.terminal,
|
|
1070
|
+
}, wrappedStdout, wrappedStderr, wrappedOutput);
|
|
1071
|
+
return new WebContainerProcessImpl(process, outputStream, stdoutStream, stderrStream);
|
|
1072
|
+
}
|
|
1073
|
+
async export(path, options) {
|
|
1074
|
+
const serializeOptions = {
|
|
1075
|
+
format: options?.format ?? 'json',
|
|
1076
|
+
includes: options?.includes,
|
|
1077
|
+
excludes: options?.excludes,
|
|
1078
|
+
external: true,
|
|
1079
|
+
};
|
|
1080
|
+
const result = await this._instance.serialize(path, serializeOptions);
|
|
1081
|
+
if (serializeOptions.format === 'json') {
|
|
1082
|
+
const data = JSON.parse(decoder.decode(result));
|
|
1083
|
+
return toExternalFileSystemTree(data);
|
|
1084
|
+
}
|
|
1085
|
+
return result;
|
|
1086
|
+
}
|
|
1087
|
+
on(event, listener) {
|
|
1088
|
+
if (event === 'preview-message') {
|
|
1089
|
+
const originalListener = listener;
|
|
1090
|
+
listener = ((message) => {
|
|
1091
|
+
if (isPreviewMessage(message)) {
|
|
1092
|
+
originalListener(message);
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
const { listener: wrapped, subscribe } = syncSubscription(listener);
|
|
1097
|
+
return subscribe(this._instance.on(event, comlink_exports.proxy(wrapped)));
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Mounts a tree of files into the filesystem. This can be specified as a tree object ({@link FileSystemTree})
|
|
1101
|
+
* or as a binary snapshot generated by [`@webcontainer/snapshot`](https://www.npmjs.com/package/@webcontainer/snapshot).
|
|
1102
|
+
*
|
|
1103
|
+
* @param snapshotOrTree - A tree of files, or a binary snapshot. Note that binary payloads will be transferred.
|
|
1104
|
+
* @param options - Additional options.
|
|
1105
|
+
*/
|
|
1106
|
+
mount(snapshotOrTree, options) {
|
|
1107
|
+
const payload = snapshotOrTree instanceof Uint8Array
|
|
1108
|
+
? snapshotOrTree
|
|
1109
|
+
: snapshotOrTree instanceof ArrayBuffer
|
|
1110
|
+
? new Uint8Array(snapshotOrTree)
|
|
1111
|
+
: encoder.encode(JSON.stringify(toInternalFileSystemTree(snapshotOrTree)));
|
|
1112
|
+
return this._instance.loadFiles(comlink_exports.transfer(payload, [payload.buffer]), {
|
|
1113
|
+
mountPoints: options?.mountPoint,
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Set a custom script to be injected into all previews. When this function is called, every
|
|
1118
|
+
* future page reload will contain the provided script tag on all HTML responses.
|
|
1119
|
+
*
|
|
1120
|
+
* Note:
|
|
1121
|
+
*
|
|
1122
|
+
* When this function resolves, every preview reloaded _after_ will have the new script.
|
|
1123
|
+
* Existing preview have to be explicitely reloaded.
|
|
1124
|
+
*
|
|
1125
|
+
* To reload a preview you can use `reloadPreview`.
|
|
1126
|
+
*
|
|
1127
|
+
* @param scriptSrc Source for the script tag.
|
|
1128
|
+
* @param options Options to define which type of script this is.
|
|
1129
|
+
*/
|
|
1130
|
+
setPreviewScript(scriptSrc, options) {
|
|
1131
|
+
return this._instance.setPreviewScript(scriptSrc, options);
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* The default value of the `PATH` environment variable for processes started through {@link spawn}.
|
|
1135
|
+
*/
|
|
1136
|
+
get path() {
|
|
1137
|
+
return this._runtimeInfo.path;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* The full path to the working directory (see {@link FileSystemAPI}).
|
|
1141
|
+
*/
|
|
1142
|
+
get workdir() {
|
|
1143
|
+
return this._runtimeInfo.cwd;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Destroys the WebContainer instance, turning it unusable, and releases its resources. After this,
|
|
1147
|
+
* a new WebContainer instance can be obtained by calling {@link WebContainer.boot | `boot`}.
|
|
1148
|
+
*
|
|
1149
|
+
* All entities derived from this instance (e.g. processes, the file system, etc.) also become unusable
|
|
1150
|
+
* after calling this method.
|
|
1151
|
+
*/
|
|
1152
|
+
teardown() {
|
|
1153
|
+
if (this._tornDown) {
|
|
1154
|
+
throw new Error('WebContainer already torn down');
|
|
1155
|
+
}
|
|
1156
|
+
this._tornDown = true;
|
|
1157
|
+
this._unsubscribeFromTokenChangedListener();
|
|
1158
|
+
const teardownFn = async () => {
|
|
1159
|
+
try {
|
|
1160
|
+
await this.fs._teardown();
|
|
1161
|
+
await this._instance.teardown();
|
|
1162
|
+
}
|
|
1163
|
+
finally {
|
|
1164
|
+
this._instance[comlink_exports.releaseProxy]();
|
|
1165
|
+
if (WebContainer._instance === this) {
|
|
1166
|
+
WebContainer._instance = null;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
WebContainer._teardownPromise = teardownFn();
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Boots a WebContainer. Only a single instance of WebContainer can be booted concurrently
|
|
1174
|
+
* (see {@link WebContainer.teardown | `teardown`}).
|
|
1175
|
+
*
|
|
1176
|
+
* Booting WebContainer is an expensive operation.
|
|
1177
|
+
*/
|
|
1178
|
+
static async boot(options = {}) {
|
|
1179
|
+
await this._teardownPromise;
|
|
1180
|
+
WebContainer._teardownPromise = null;
|
|
1181
|
+
const { workdirName } = options;
|
|
1182
|
+
if (window.crossOriginIsolated && options.coep === 'none') {
|
|
1183
|
+
console.warn(`A Cross-Origin-Embedder-Policy header is required in cross origin isolated environments.\nSet the 'coep' option to 'require-corp'.`);
|
|
1184
|
+
}
|
|
1185
|
+
if (workdirName?.includes('/') || workdirName === '..' || workdirName === '.') {
|
|
1186
|
+
throw new Error('workdirName should be a valid folder name');
|
|
1187
|
+
}
|
|
1188
|
+
// signal that boot was called to auth module as calling auth.init after boot is likely incorrect
|
|
1189
|
+
authState.bootCalled = true;
|
|
1190
|
+
// try to "acquire the lock", i.e. wait for any ongoing boot request to finish
|
|
1191
|
+
while (bootPromise) {
|
|
1192
|
+
await bootPromise;
|
|
1193
|
+
}
|
|
1194
|
+
if (WebContainer._instance) {
|
|
1195
|
+
throw new Error('Only a single WebContainer instance can be booted');
|
|
1196
|
+
}
|
|
1197
|
+
const instancePromise = unsynchronizedBoot(options);
|
|
1198
|
+
// the "lock" is a promise for the ongoing boot that never fails
|
|
1199
|
+
bootPromise = instancePromise.catch(() => { });
|
|
1200
|
+
try {
|
|
1201
|
+
const instance = await instancePromise;
|
|
1202
|
+
WebContainer._instance = instance;
|
|
1203
|
+
return instance;
|
|
1204
|
+
}
|
|
1205
|
+
finally {
|
|
1206
|
+
// release the "lock"
|
|
1207
|
+
bootPromise = null;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Configure an API key to be used for this instance of WebContainer.
|
|
1213
|
+
*
|
|
1214
|
+
* @param key WebContainer API key.
|
|
1215
|
+
*/
|
|
1216
|
+
function configureAPIKey(key) {
|
|
1217
|
+
if (authState.bootCalled) {
|
|
1218
|
+
throw new Error('`configureAPIKey` should always be called before `WebContainer.boot`');
|
|
1219
|
+
}
|
|
1220
|
+
iframeSettings.setQueryParam('client_id', key);
|
|
1221
|
+
}
|
|
1222
|
+
const DIR_ENTRY_TYPE_FILE = 1;
|
|
1223
|
+
const DIR_ENTRY_TYPE_DIR = 2;
|
|
1224
|
+
/**
|
|
1225
|
+
* @internal
|
|
1226
|
+
*/
|
|
1227
|
+
class DirEntImpl {
|
|
1228
|
+
name;
|
|
1229
|
+
_type;
|
|
1230
|
+
constructor(name, _type) {
|
|
1231
|
+
this.name = name;
|
|
1232
|
+
this._type = _type;
|
|
1233
|
+
}
|
|
1234
|
+
isFile() {
|
|
1235
|
+
return this._type === DIR_ENTRY_TYPE_FILE;
|
|
1236
|
+
}
|
|
1237
|
+
isDirectory() {
|
|
1238
|
+
return this._type === DIR_ENTRY_TYPE_DIR;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
class FSWatcher {
|
|
1242
|
+
_apiClient;
|
|
1243
|
+
_path;
|
|
1244
|
+
_options;
|
|
1245
|
+
_listener;
|
|
1246
|
+
_wrappedListener;
|
|
1247
|
+
_watcher;
|
|
1248
|
+
_closed = false;
|
|
1249
|
+
constructor(_apiClient, _path, _options, _listener) {
|
|
1250
|
+
this._apiClient = _apiClient;
|
|
1251
|
+
this._path = _path;
|
|
1252
|
+
this._options = _options;
|
|
1253
|
+
this._listener = _listener;
|
|
1254
|
+
this._apiClient._watchers.add(this);
|
|
1255
|
+
this._wrappedListener = (event, filename) => {
|
|
1256
|
+
if (this._listener && !this._closed) {
|
|
1257
|
+
this._listener(event, filename);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
this._apiClient._fs
|
|
1261
|
+
.watch(this._path, this._options, proxyListener(this._wrappedListener))
|
|
1262
|
+
.then((_watcher) => {
|
|
1263
|
+
this._watcher = _watcher;
|
|
1264
|
+
if (this._closed) {
|
|
1265
|
+
return this._teardown();
|
|
1266
|
+
}
|
|
1267
|
+
return undefined;
|
|
1268
|
+
})
|
|
1269
|
+
.catch(console.error);
|
|
1270
|
+
}
|
|
1271
|
+
async close() {
|
|
1272
|
+
if (!this._closed) {
|
|
1273
|
+
this._closed = true;
|
|
1274
|
+
this._apiClient._watchers.delete(this);
|
|
1275
|
+
await this._teardown();
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* @internal
|
|
1280
|
+
*/
|
|
1281
|
+
async _teardown() {
|
|
1282
|
+
await this._watcher?.close().finally(() => {
|
|
1283
|
+
this._watcher?.[comlink_exports.releaseProxy]();
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* @internal
|
|
1289
|
+
*/
|
|
1290
|
+
class WebContainerProcessImpl {
|
|
1291
|
+
output;
|
|
1292
|
+
input;
|
|
1293
|
+
exit;
|
|
1294
|
+
_process;
|
|
1295
|
+
stdout;
|
|
1296
|
+
stderr;
|
|
1297
|
+
constructor(process, output, stdout, stderr) {
|
|
1298
|
+
this.output = output;
|
|
1299
|
+
this._process = process;
|
|
1300
|
+
this.input = new WritableStream({
|
|
1301
|
+
write: (data) => {
|
|
1302
|
+
// this promise is not supposed to fail anyway
|
|
1303
|
+
this._getProcess()
|
|
1304
|
+
?.write(data)
|
|
1305
|
+
.catch(() => { });
|
|
1306
|
+
},
|
|
1307
|
+
});
|
|
1308
|
+
this.exit = this._onExit();
|
|
1309
|
+
this.stdout = stdout;
|
|
1310
|
+
this.stderr = stderr;
|
|
1311
|
+
}
|
|
1312
|
+
kill() {
|
|
1313
|
+
this._process?.kill();
|
|
1314
|
+
}
|
|
1315
|
+
resize(dimensions) {
|
|
1316
|
+
this._getProcess()?.resize(dimensions);
|
|
1317
|
+
}
|
|
1318
|
+
async _onExit() {
|
|
1319
|
+
try {
|
|
1320
|
+
return await this._process.onExit;
|
|
1321
|
+
}
|
|
1322
|
+
finally {
|
|
1323
|
+
this._process?.[comlink_exports.releaseProxy]();
|
|
1324
|
+
this._process = null;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
_getProcess() {
|
|
1328
|
+
if (this._process == null) {
|
|
1329
|
+
console.warn('This process already exited');
|
|
1330
|
+
}
|
|
1331
|
+
return this._process;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* @internal
|
|
1336
|
+
*/
|
|
1337
|
+
class FileSystemAPIClient {
|
|
1338
|
+
_fs;
|
|
1339
|
+
_watchers = new Set([]);
|
|
1340
|
+
constructor(fs) {
|
|
1341
|
+
this._fs = fs;
|
|
1342
|
+
}
|
|
1343
|
+
rm(...args) {
|
|
1344
|
+
return this._fs.rm(...args);
|
|
1345
|
+
}
|
|
1346
|
+
async readFile(path, encoding) {
|
|
1347
|
+
return await this._fs.readFile(path, encoding);
|
|
1348
|
+
}
|
|
1349
|
+
async rename(oldPath, newPath) {
|
|
1350
|
+
return await this._fs.rename(oldPath, newPath);
|
|
1351
|
+
}
|
|
1352
|
+
async writeFile(path, data, options) {
|
|
1353
|
+
if (data instanceof Uint8Array) {
|
|
1354
|
+
const buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
1355
|
+
data = comlink_exports.transfer(new Uint8Array(buffer), [buffer]);
|
|
1356
|
+
}
|
|
1357
|
+
await this._fs.writeFile(path, data, options);
|
|
1358
|
+
}
|
|
1359
|
+
async readdir(path, options) {
|
|
1360
|
+
const result = await this._fs.readdir(path, options);
|
|
1361
|
+
if (isStringArray(result)) {
|
|
1362
|
+
return result;
|
|
1363
|
+
}
|
|
1364
|
+
if (isTypedArrayCollection(result)) {
|
|
1365
|
+
return result;
|
|
1366
|
+
}
|
|
1367
|
+
const entries = result.map((entry) => new DirEntImpl(entry.name, entry['Symbol(type)']));
|
|
1368
|
+
return entries;
|
|
1369
|
+
}
|
|
1370
|
+
async mkdir(path, options) {
|
|
1371
|
+
return await this._fs.mkdir(path, options);
|
|
1372
|
+
}
|
|
1373
|
+
watch(path, options, listener) {
|
|
1374
|
+
if (typeof options === 'function') {
|
|
1375
|
+
listener = options;
|
|
1376
|
+
options = null;
|
|
1377
|
+
}
|
|
1378
|
+
return new FSWatcher(this, path, options, listener);
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* @internal
|
|
1382
|
+
*/
|
|
1383
|
+
async _teardown() {
|
|
1384
|
+
this._fs[comlink_exports.releaseProxy]();
|
|
1385
|
+
await Promise.all([...this._watchers].map((watcher) => watcher.close()));
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
async function unsynchronizedBoot(options) {
|
|
1389
|
+
const { serverPromise } = serverFactory(options);
|
|
1390
|
+
const server = await serverPromise;
|
|
1391
|
+
const instance = await server.build({
|
|
1392
|
+
host: window.location.host,
|
|
1393
|
+
version: "1.6.4",
|
|
1394
|
+
workdirName: options.workdirName,
|
|
1395
|
+
forwardPreviewErrors: options.forwardPreviewErrors,
|
|
1396
|
+
});
|
|
1397
|
+
const [fs, previewScript, runtimeInfo] = await Promise.all([
|
|
1398
|
+
instance.fs(),
|
|
1399
|
+
instance.previewScript(),
|
|
1400
|
+
instance.runtimeInfo(),
|
|
1401
|
+
]);
|
|
1402
|
+
return new WebContainer(instance, fs, previewScript, runtimeInfo);
|
|
1403
|
+
}
|
|
1404
|
+
function binaryListener(listener) {
|
|
1405
|
+
if (listener == null) {
|
|
1406
|
+
return undefined;
|
|
1407
|
+
}
|
|
1408
|
+
return (data) => {
|
|
1409
|
+
if (data instanceof Uint8Array) {
|
|
1410
|
+
listener(decoder.decode(data));
|
|
1411
|
+
}
|
|
1412
|
+
else if (data == null) {
|
|
1413
|
+
listener(null);
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
function proxyListener(listener) {
|
|
1418
|
+
if (listener == null) {
|
|
1419
|
+
return undefined;
|
|
1420
|
+
}
|
|
1421
|
+
return comlink_exports.proxy(listener);
|
|
1422
|
+
}
|
|
1423
|
+
function serverFactory(options) {
|
|
1424
|
+
if (cachedServerPromise != null) {
|
|
1425
|
+
if (options.coep !== cachedBootOptions.coep) {
|
|
1426
|
+
console.warn(`Attempting to boot WebContainer with 'coep: ${options.coep}'`);
|
|
1427
|
+
console.warn(`First boot had 'coep: ${cachedBootOptions.coep}', new settings will not take effect!`);
|
|
1428
|
+
}
|
|
1429
|
+
return { serverPromise: cachedServerPromise };
|
|
1430
|
+
}
|
|
1431
|
+
if (options.coep) {
|
|
1432
|
+
iframeSettings.setQueryParam('coep', options.coep);
|
|
1433
|
+
}
|
|
1434
|
+
if (options.experimentalNode) {
|
|
1435
|
+
iframeSettings.setQueryParam('experimental_node', '1');
|
|
1436
|
+
}
|
|
1437
|
+
const iframe = document.createElement('iframe');
|
|
1438
|
+
iframe.style.display = 'none';
|
|
1439
|
+
iframe.setAttribute('allow', 'cross-origin-isolated');
|
|
1440
|
+
const url = iframeSettings.url;
|
|
1441
|
+
iframe.src = url.toString();
|
|
1442
|
+
const { origin } = url;
|
|
1443
|
+
cachedBootOptions = { ...options };
|
|
1444
|
+
cachedServerPromise = new Promise((resolve) => {
|
|
1445
|
+
const onMessage = (event) => {
|
|
1446
|
+
if (event.origin !== origin) {
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
const { data } = event;
|
|
1450
|
+
if (data.type === 'init') {
|
|
1451
|
+
resolve(comlink_exports.wrap(event.ports[0]));
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
if (data.type === 'warning') {
|
|
1455
|
+
console[data.level].call(console, data.message);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
window.addEventListener('message', onMessage);
|
|
1460
|
+
});
|
|
1461
|
+
document.body.insertBefore(iframe, null);
|
|
1462
|
+
return { serverPromise: cachedServerPromise };
|
|
1463
|
+
}
|
|
1464
|
+
function isStringArray(list) {
|
|
1465
|
+
return typeof list[0] === 'string';
|
|
1466
|
+
}
|
|
1467
|
+
function isTypedArrayCollection(list) {
|
|
1468
|
+
return list[0] instanceof Uint8Array;
|
|
1469
|
+
}
|
|
1470
|
+
function streamWithPush() {
|
|
1471
|
+
let controller = null;
|
|
1472
|
+
const stream = new ReadableStream({
|
|
1473
|
+
start(controller_) {
|
|
1474
|
+
controller = controller_;
|
|
1475
|
+
},
|
|
1476
|
+
});
|
|
1477
|
+
const push = (item) => {
|
|
1478
|
+
if (item != null) {
|
|
1479
|
+
controller?.enqueue(item);
|
|
1480
|
+
}
|
|
1481
|
+
else {
|
|
1482
|
+
controller?.close();
|
|
1483
|
+
controller = null;
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
return { stream, push };
|
|
1487
|
+
}
|
|
1488
|
+
function syncSubscription(listener) {
|
|
1489
|
+
let stopped = false;
|
|
1490
|
+
let unsubscribe = () => { };
|
|
1491
|
+
const wrapped = ((...args) => {
|
|
1492
|
+
if (stopped) {
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
listener(...args);
|
|
1496
|
+
});
|
|
1497
|
+
return {
|
|
1498
|
+
subscribe(promise) {
|
|
1499
|
+
promise.then((unsubscribe_) => {
|
|
1500
|
+
unsubscribe = unsubscribe_;
|
|
1501
|
+
if (stopped) {
|
|
1502
|
+
unsubscribe();
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
return () => {
|
|
1506
|
+
stopped = true;
|
|
1507
|
+
unsubscribe();
|
|
1508
|
+
};
|
|
1509
|
+
},
|
|
1510
|
+
listener: wrapped,
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
export { PreviewMessageType, WebContainer, auth, configureAPIKey, isPreviewMessage, reloadPreview };
|