in webapp/src/cmds.ts [280:549]
body: lf("Flashing your {0} took too long. Please disconnect your {0} from your computer and try reconnecting it.", pxt.appTarget.appTheme.boardName || lf("device")),
disagreeLbl: lf("Ok"),
hideAgree: true
});
})
.then(() => {
return pxt.commands.saveOnlyAsync(r);
});
});
}
function localhostDeployCoreAsync(resp: pxtc.CompileResult): Promise<void> {
log('local deploy');
core.infoNotification(lf("Uploading..."));
let deploy = () => pxt.Util.requestAsync({
url: "/api/deploy",
headers: { "Authorization": Cloud.localToken },
method: "POST",
data: resp,
allowHttpErrors: true // To prevent "Network request failed" warning in case of error. We're not actually doing network requests in localhost scenarios
}).then(r => {
if (r.statusCode !== 200) {
core.errorNotification(lf("There was a problem, please try again"));
} else if (r.json["boardCount"] === 0) {
core.warningNotification(lf("Please connect your {0} to your computer and try again", pxt.appTarget.appTheme.boardName));
}
});
return deploy()
}
function winrtSaveAsync(resp: pxtc.CompileResult) {
return pxt.winrt.saveOnlyAsync(resp)
.then((saved) => {
if (saved) {
core.infoNotification(lf("file saved!"));
}
})
.catch((e) => core.errorNotification(lf("saving file failed...")));
}
export function setExtensionResult(res: pxt.editor.ExtensionResult) {
extensionResult = res;
applyExtensionResult();
}
function applyExtensionResult() {
const res = extensionResult;
if (!res) return;
if (res.mkPacketIOWrapper) {
log(`extension mkPacketIOWrapper`)
pxt.packetio.mkPacketIOWrapper = res.mkPacketIOWrapper;
}
if (res.deployAsync) {
log(`extension deploy core async`);
pxt.commands.deployCoreAsync = res.deployAsync;
}
if (res.saveOnlyAsync) {
log(`extension save only async`);
pxt.commands.saveOnlyAsync = res.saveOnlyAsync;
}
if (res.saveProjectAsync) {
log(`extension save project async`);
pxt.commands.saveProjectAsync = res.saveProjectAsync;
}
if (res.renderBrowserDownloadInstructions) {
log(`extension upload renderBrowserDownloadInstructions`);
pxt.commands.renderBrowserDownloadInstructions = res.renderBrowserDownloadInstructions;
}
if (res.renderUsbPairDialog) {
log(`extension renderUsbPairDialog`)
pxt.commands.renderUsbPairDialog = res.renderUsbPairDialog;
}
if (res.renderIncompatibleHardwareDialog) {
log(`extension renderIncompatibleHardwareDialog`)
pxt.commands.renderIncompatibleHardwareDialog = res.renderIncompatibleHardwareDialog;
}
if (res.showUploadInstructionsAsync) {
log(`extension upload instructions async`);
pxt.commands.showUploadInstructionsAsync = res.showUploadInstructionsAsync;
}
if (res.patchCompileResultAsync) {
log(`extension build patch`);
pxt.commands.patchCompileResultAsync = res.patchCompileResultAsync;
}
if (res.blocklyPatch) {
log(`extension blockly patch`);
pxt.blocks.extensionBlocklyPatch = res.blocklyPatch;
}
if (res.webUsbPairDialogAsync) {
log(`extension webusb pair dialog`);
pxt.commands.webUsbPairDialogAsync = res.webUsbPairDialogAsync;
}
if (res.onTutorialCompleted) {
log(`extension tutorial completed`);
pxt.commands.onTutorialCompleted = res.onTutorialCompleted;
}
}
export async function initAsync() {
log(`cmds init`);
pxt.onAppTargetChanged = () => {
log('app target changed')
initAsync()
}
// check webusb
await pxt.usb.checkAvailableAsync()
// unplug any existing packetio
await pxt.packetio.disconnectAsync()
// reset commands to browser
pxt.packetio.mkPacketIOWrapper = undefined;
pxt.commands.renderDisconnectDialog = undefined;
pxt.commands.deployCoreAsync = browserDownloadDeployCoreAsync;
pxt.commands.browserDownloadAsync = browserDownloadAsync;
pxt.commands.saveOnlyAsync = browserDownloadDeployCoreAsync;
pxt.commands.webUsbPairDialogAsync = webusb.webUsbPairDialogAsync;
pxt.commands.showUploadInstructionsAsync = showUploadInstructionsAsync;
pxt.packetio.mkPacketIOAsync = undefined;
// uf2/hf2 support
if (pxt.appTarget?.compile?.useUF2) {
log(`hf2 wrapper`)
pxt.packetio.mkPacketIOWrapper = pxt.HF2.mkHF2PacketIOWrapper;
}
// check if webUSB is available and usable
if ((pxt.appTarget?.compile?.isNative || pxt.appTarget?.compile?.hasHex) && !pxt.BrowserUtils.isPxtElectron()) {
// TODO: web USB is currently disabled in electron app, but should be supported.
if (pxt.usb.isAvailable() && pxt.appTarget?.compile?.webUSB) {
log(`enabled webusb`);
pxt.usb.setEnabled(true);
pxt.packetio.mkPacketIOAsync = pxt.usb.mkWebUSBHIDPacketIOAsync;
} else if (!pxt.appTarget?.compile?.disableHIDBridge) {
log(`enabled hid bridge (webusb disabled)`);
pxt.usb.setEnabled(false);
pxt.packetio.mkPacketIOAsync = hidbridge.mkHIDBridgePacketIOAsync;
}
}
const forceBrowserDownload = /force(Hex)?(Browser)?Download/i.test(window.location.href);
const webUSBSupported = pxt.usb.isEnabled && pxt.appTarget?.compile?.webUSB;
if (forceBrowserDownload || pxt.appTarget?.serial?.noDeploy) {
log(`deploy: force browser download`);
// commands are ready
} else if (isNativeHost()) {
log(`deploy: webkit deploy/save`);
pxt.commands.deployCoreAsync = nativeHostDeployCoreAsync;
pxt.commands.saveOnlyAsync = nativeHostSaveCoreAsync;
pxt.commands.workspaceLoadedAsync = nativeHostWorkspaceLoadedCoreAsync;
} else if (pxt.winrt.isWinRT()) { // windows app
log(`deploy: winrt`)
pxt.packetio.mkPacketIOAsync = pxt.winrt.mkWinRTPacketIOAsync;
pxt.commands.browserDownloadAsync = pxt.winrt.browserDownloadAsync;
pxt.commands.deployCoreAsync = winrtDeployCoreAsync;
pxt.commands.saveOnlyAsync = winrtSaveAsync;
} else if (pxt.BrowserUtils.isPxtElectron()) {
log(`deploy: electron`);
pxt.commands.deployCoreAsync = electron.driveDeployAsync;
pxt.commands.electronDeployAsync = electron.driveDeployAsync;
} else if (webUSBSupported) {
log(`deploy: webusb`);
pxt.commands.deployCoreAsync = hidDeployCoreAsync;
pxt.commands.renderDisconnectDialog = webusb.renderUnpairDialog;
} else if (hidbridge.shouldUse()) {
log(`deploy: hid`);
pxt.commands.deployCoreAsync = hidDeployCoreAsync;
} else if (pxt.BrowserUtils.isLocalHost() && Cloud.localToken) { // local node.js
log(`deploy: localhost`);
pxt.commands.deployCoreAsync = localhostDeployCoreAsync;
} else { // in browser
log(`deploy: browser only`);
// commands are ready
}
applyExtensionResult();
// don't initialize until extension has hookup as well
if (pxt.winrt.isWinRT()) {
log(`deploy: init winrt`)
pxt.winrt.initWinrtHid(
() => pxt.packetio.initAsync(true).then(wrap => wrap?.reconnectAsync()),
() => pxt.packetio.disconnectAsync()
);
}
}
export function maybeReconnectAsync(pairIfDeviceNotFound = false, skipIfConnected = false) {
log("[CLIENT]: starting reconnect")
if (skipIfConnected && pxt.packetio.isConnected() && !disconnectingPacketIO) return Promise.resolve();
if (reconnectPromise) return reconnectPromise;
reconnectPromise = requestPacketIOLockAsync()
.then(() => pxt.packetio.initAsync())
.then(wrapper => {
if (!wrapper) return Promise.resolve();
return wrapper.reconnectAsync()
.catch(e => {
if (e.type == "devicenotfound")
return pairIfDeviceNotFound && pairAsync();
throw e;
})
})
.finally(() => {
reconnectPromise = undefined;
})
return reconnectPromise;
}
export function pairAsync(): Promise<void> {
pxt.tickEvent("cmds.pair")
return pxt.commands.webUsbPairDialogAsync(pxt.usb.pairAsync, core.confirmAsync)
.then(res => {
if (res) return maybeReconnectAsync();
else return core.infoNotification("Oops, no device was paired.")
});
}
export function showDisconnectAsync(): Promise<void> {
if (pxt.commands.renderDisconnectDialog) {
const { header, jsx, helpUrl } = pxt.commands.renderDisconnectDialog();
return core.dialogAsync({ header, jsx, helpUrl, hasCloseIcon: true });
}
return Promise.resolve();
}
export function disconnectAsync(): Promise<void> {
log("[CLIENT]: starting disconnect")
disconnectingPacketIO = true;
return pxt.packetio.disconnectAsync()
.then(() => {
log("[CLIENT]: sending confirmed disconnect " + lockRef)
hasLock = false;
sendServiceWorkerMessage({
type: "serviceworkerclient",
action: "release-packet-io-lock",
lock: lockRef
});
disconnectingPacketIO = false;
})
}
// Generate a unique id for communicating with the service worker
const lockRef = pxtc.Util.guidGen();
let pendingPacketIOLockResolver: () => void;
let pendingPacketIOLockRejecter: () => void;
let serviceWorkerSupportedResolver: () => void;
let reconnectPromise: Promise<void>;
let hasLock = false;
let deployingPacketIO = false;
let disconnectingPacketIO = false;
let serviceWorkerSupported: boolean | undefined = undefined;
async function requestPacketIOLockAsync() {
if (hasLock) return;
if (pendingPacketIOLockResolver) return Promise.reject("Already waiting for packet lock");
const supported = await checkIfServiceWorkerSupportedAsync();
if (!supported) return;
if (navigator?.serviceWorker?.controller) {
return new Promise<void>((resolve, reject) => {
pendingPacketIOLockResolver = resolve;
pendingPacketIOLockRejecter = reject;
log("[CLIENT]: requesting lock " + lockRef)
sendServiceWorkerMessage({