blob: f703c93182b3b89dd2d7b34ff1f6af9b17aa4045 [file] [edit]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:js_interop';
import 'package:dartpad/src/util/message_port_channel.dart';
import 'package:dartpad_worker/src/util/log.dart';
import 'package:dartpad_worker/src/worker.dart';
import 'package:http/http.dart' as http;
import 'package:http/retry.dart' as http;
import 'package:web/web.dart' as web;
const _defaultSdkLocation = 'dart/';
void main() async {
await runZonedGuarded(_startWorker, (e, st) {
logError('uncaught exception: $e\n$st');
});
}
@JS('assetBaseUrl')
external String? get assetBaseUrl;
@JS('sdkLocation')
external String? get sdkLocation;
@JS('pubHostedUrl')
external String? get pubHostedUrl;
Future<void> _startWorker() async {
final workerFuture = _createWorker();
// Check if we are running in a SharedWorker environment
if (globalContext.isA<web.SharedWorkerGlobalScope>()) {
final scope = globalContext as web.SharedWorkerGlobalScope;
scope.onconnect = (web.MessageEvent event) {
final port = event.ports.toDart[0];
_handleConnect(port, workerFuture);
}.toJS;
} else {
// Running as a standard DedicatedWorker
final scope = globalContext as web.DedicatedWorkerGlobalScope;
scope.onmessage = (web.MessageEvent event) {
if (event.ports.toDart.isEmpty) {
logError('Message missing MessagePort');
return;
}
if (!event.data.isA<JSObject>()) {
logError('Message data is not a JS Object');
return;
}
final message = event.data as HandshakeMessage;
// Check for the explicit handshake action
if (message.action == 'connect') {
final port = event.ports.toDart[0];
_handleConnect(port, workerFuture);
} else {
logError('Unknown action "${message.action}"');
}
}.toJS;
}
try {
await workerFuture;
} catch (e, st) {
logError('startup failed: $e\n$st');
}
}
Future<void> _handleConnect(
web.MessagePort port,
Future<Worker> workerFuture,
) async {
// TODO(jonasfj): Consider tracking and sending progress events
final worker = await workerFuture;
worker.connect(messagePortChannel(port).cast());
}
Future<Worker> _createWorker() async {
final assetBaseUrl_ = assetBaseUrl;
var assetBaseUri = assetBaseUrl_ != null
? Uri.tryParse(assetBaseUrl_) ?? Uri.base
: Uri.base;
if (!assetBaseUri.path.endsWith('/')) {
assetBaseUri = assetBaseUri.replace(path: '${assetBaseUri.path}/');
}
var sdkLocation_ = assetBaseUri.resolve(sdkLocation ?? _defaultSdkLocation);
if (!sdkLocation_.path.endsWith('/')) {
sdkLocation_ = sdkLocation_.replace(path: '${sdkLocation_.path}/');
}
final c = http.RetryClient(http.Client());
try {
final sdkTar = sdkLocation_.resolve('sdk.tar');
final r = await c.send(http.Request('GET', sdkTar));
if (r.statusCode != 200) {
logError('Failed to fetch sdk.tar from: "$sdkTar"');
throw Exception('unable to fetch sdk.tar');
}
return await Worker.create(r.stream, pubHostedUrl: pubHostedUrl);
} finally {
c.close();
}
}
extension type HandshakeMessage._(JSObject _) implements JSObject {
external String? get action;
}