Skip to content

Commit

Permalink
Refactor iframe communication and use MessageChannel (#9945)
Browse files Browse the repository at this point in the history
* Refactor iframe communication and use MessageChannel

* remove unused typing

* Update pxtservices/iframeEmbeddedClient.ts

Co-authored-by: Eric Anderson <eanders@microsoft.com>

* Update pxtservices/iframeEmbeddedClient.ts

Co-authored-by: Eric Anderson <eanders@microsoft.com>

* pr feedback

* lint

---------

Co-authored-by: Eric Anderson <eanders@microsoft.com>
  • Loading branch information
riknoll and eanders-ms committed Apr 2, 2024
1 parent a344259 commit f4e9aad
Show file tree
Hide file tree
Showing 10 changed files with 865 additions and 581 deletions.
14 changes: 9 additions & 5 deletions gulpfile.js
Expand Up @@ -48,6 +48,7 @@ const cli = () => compileTsProject("cli", "built", true);
const webapp = () => compileTsProject("webapp", "built", true);
const reactCommon = () => compileTsProject("react-common", "built/react-common", true);
const pxtblocks = () => compileTsProject("pxtblocks", "built/pxtblocks", true);
const pxtservices = () => compileTsProject("pxtservices", "built/pxtservices", true);

const pxtapp = () => gulp.src([
"node_modules/lzma/src/lzma_worker-min.js",
Expand Down Expand Up @@ -118,7 +119,7 @@ function initWatch() {
pxtlib,
gulp.parallel(pxtcompiler, pxtsim, backendutils),
pxtpy,
gulp.parallel(pxtblocks, pxteditor),
gulp.parallel(pxtblocks, pxteditor, pxtservices),
gulp.parallel(pxtrunner, cli, pxtcommon),
gulp.parallel(updatestrings, browserifyEmbed),
gulp.parallel(pxtjs, pxtdts, pxtapp, pxtworker, pxtembed),
Expand All @@ -139,6 +140,7 @@ function initWatch() {

gulp.watch("./pxtpy/**/*", gulp.series(pxtpy, ...tasks.slice(3)));
gulp.watch("./pxtblocks/**/*", gulp.series(pxtblocks, ...tasks.slice(4)));
gulp.watch("./pxtservices/**/*", gulp.series(pxtservices, ...tasks.slice(4)));

gulp.watch("./pxteditor/**/*", gulp.series(pxteditor, ...tasks.slice(4)));

Expand Down Expand Up @@ -219,6 +221,7 @@ function updatestrings() {
return buildStrings("built/strings.json", [
"cli",
"pxtblocks",
"pxtservices",
"pxtcompiler",
"pxteditor",
"pxtlib",
Expand Down Expand Up @@ -610,8 +613,9 @@ const maybeBuildWebapps = () => {

const lintWithEslint = () => Promise.all(
["cli", "pxtblocks", "pxteditor", "pxtlib", "pxtcompiler",
"pxtpy", "pxtrunner", "pxtsim", "webapp",
"docfiles/pxtweb", "skillmap", "authcode", "multiplayer"/*, "kiosk"*/, "teachertool", "docs/static/streamer"].map(dirname =>
"pxtpy", "pxtrunner", "pxtsim", "webapp", "pxtservices",
"docfiles/pxtweb", "skillmap", "authcode",
"multiplayer"/*, "kiosk"*/, "teachertool", "docs/static/streamer"].map(dirname =>
exec(`node node_modules/eslint/bin/eslint.js -c .eslintrc.js --ext .ts,.tsx ./${dirname}/`, true)))
.then(() => console.log("linted"))
const lint = lintWithEslint
Expand Down Expand Up @@ -723,7 +727,7 @@ const buildAll = gulp.series(
gulp.parallel(pxtlib, pxtweb),
gulp.parallel(pxtcompiler, pxtsim, backendutils),
pxtpy,
gulp.parallel(pxteditor, pxtblocks),
gulp.parallel(pxteditor, pxtblocks, pxtservices),
gulp.parallel(pxtrunner, cli, pxtcommon),
browserifyEmbed,
gulp.parallel(pxtjs, pxtdts, pxtapp, pxtworker, pxtembed),
Expand All @@ -747,7 +751,7 @@ exports.clean = clean;
exports.build = buildAll;

exports.webapp = gulp.series(
gulp.parallel(reactCommon, pxtblocks, pxteditor),
gulp.parallel(reactCommon, pxtblocks, pxteditor, pxtservices),
webapp,
browserifyWebapp,
browserifyAssetEditor
Expand Down
70 changes: 70 additions & 0 deletions localtypings/pxteditor.d.ts
Expand Up @@ -16,6 +16,11 @@ declare namespace pxt.editor {
* flag to request response
*/
response?: boolean;

/**
* Frame identifier that can be passed to the iframe by adding the frameId query parameter
*/
frameId?: string;
}

export interface EditorMessageResponse extends EditorMessage {
Expand Down Expand Up @@ -1237,6 +1242,71 @@ declare namespace pxt.editor {
*/
blockId?: string;
}

interface BaseAssetEditorRequest {
id?: number;
files: pxt.Map<string>;
palette?: string[];
}

interface OpenAssetEditorRequest extends BaseAssetEditorRequest {
type: "open";
assetId: string;
assetType: pxt.AssetType;
}

interface CreateAssetEditorRequest extends BaseAssetEditorRequest {
type: "create";
assetType: pxt.AssetType;
displayName?: string;
}

interface SaveAssetEditorRequest extends BaseAssetEditorRequest {
type: "save";
}

interface DuplicateAssetEditorRequest extends BaseAssetEditorRequest {
type: "duplicate";
assetId: string;
assetType: pxt.AssetType;
}

type AssetEditorRequest = OpenAssetEditorRequest | CreateAssetEditorRequest | SaveAssetEditorRequest | DuplicateAssetEditorRequest;

interface BaseAssetEditorResponse {
id?: number;
}

interface OpenAssetEditorResponse extends BaseAssetEditorResponse {
type: "open";
}

interface CreateAssetEditorResponse extends BaseAssetEditorResponse {
type: "create";
}

interface SaveAssetEditorResponse extends BaseAssetEditorResponse {
type: "save";
files: pxt.Map<string>;
}

interface DuplicateAssetEditorResponse extends BaseAssetEditorResponse {
type: "duplicate";
}

type AssetEditorResponse = OpenAssetEditorResponse | CreateAssetEditorResponse | SaveAssetEditorResponse | DuplicateAssetEditorResponse;

interface AssetEditorRequestSaveEvent {
type: "event";
kind: "done-clicked";
}

interface AssetEditorReadyEvent {
type: "event";
kind: "ready";
}

type AssetEditorEvent = AssetEditorRequestSaveEvent | AssetEditorReadyEvent;
}

declare namespace pxt.workspace {
Expand Down
37 changes: 29 additions & 8 deletions pxteditor/editorcontroller.ts
Expand Up @@ -3,10 +3,15 @@
import { runValidatorPlan } from "./code-validation/runValidatorPlan";
import IProjectView = pxt.editor.IProjectView;

import { IFrameEmbeddedClient } from "../pxtservices/iframeEmbeddedClient";

const pendingRequests: pxt.Map<{
resolve: (res?: pxt.editor.EditorMessageResponse | PromiseLike<pxt.editor.EditorMessageResponse>) => void;
reject: (err: any) => void;
}> = {};

let iframeClient: IFrameEmbeddedClient;

/**
* Binds incoming window messages to the project view.
* Requires the "allowParentController" flag in the pxtarget.json/appTheme object.
Expand All @@ -24,7 +29,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)

if (!allowEditorMessages && !allowExtensionMessages && !allowSimTelemetry) return;

window.addEventListener("message", (msg: MessageEvent) => {
const handleMessage = (msg: MessageEvent) => {
const data = msg.data as pxt.editor.EditorMessage;
if (!data || !/^pxt(host|editor|pkgext|sim)$/.test(data.type)) return false;

Expand Down Expand Up @@ -154,7 +159,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
})
});
}
case "renderxml": {
case "renderxml": {
const rendermsg = data as pxt.editor.EditorMessageRenderXmlRequest;
return Promise.resolve()
.then(() => {
Expand Down Expand Up @@ -186,7 +191,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
resp = { result: results };
});
}
case "gettoolboxcategories": {
case "gettoolboxcategories": {
const msg = data as pxt.editor.EditorMessageGetToolboxCategoriesRequest;
return Promise.resolve()
.then(() => {
Expand Down Expand Up @@ -289,7 +294,9 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
}

return true;
}, false)
};

iframeClient = new IFrameEmbeddedClient(handleMessage);
}

/**
Expand Down Expand Up @@ -343,13 +350,20 @@ export function enableControllerAnalytics() {

function sendResponse(request: pxt.editor.EditorMessage, resp: any, success: boolean, error: any) {
if (request.response) {
window.parent.postMessage({
const toSend = {
type: request.type,
id: request.id,
resp,
success,
error
}, "*");
};

if (iframeClient) {
iframeClient.postMessage(toSend)
}
else {
window.parent.postMessage(toSend, "*");
}
}
}

Expand All @@ -369,8 +383,15 @@ export function postHostMessageAsync(msg: pxt.editor.EditorMessageRequest): Prom
env.id = ts.pxtc.Util.guidGen();
if (msg.response)
pendingRequests[env.id] = { resolve, reject };
window.parent.postMessage(env, "*");

if (iframeClient) {
iframeClient.postMessage(env);
}
else {
window.parent.postMessage(env, "*");
}

if (!msg.response)
resolve(undefined)
})
}
}
5 changes: 5 additions & 0 deletions pxtservices/.eslintrc.js
@@ -0,0 +1,5 @@
module.exports = {
"parserOptions": {
"project": "pxtservices/tsconfig.json",
}
}
65 changes: 65 additions & 0 deletions pxtservices/assetEditorDriver.ts
@@ -0,0 +1,65 @@
import { IframeDriver } from "./iframeDriver";

export class AssetEditorDriver extends IframeDriver {
constructor(frame: HTMLIFrameElement) {
super(frame);
}

async openAsset(assetId: string, assetType: pxt.AssetType, files: pxt.Map<string>, palette?: string[]) {
await this.sendRequest(
{
type: "open",
assetId,
assetType,
files,
palette
} as pxt.editor.OpenAssetEditorRequest
);
}

async createAsset(assetType: pxt.AssetType, files: pxt.Map<string>, displayName?: string, palette?: string[]) {
await this.sendRequest({
type: "create",
assetType,
files,
displayName,
palette
} as pxt.editor.CreateAssetEditorRequest);
}

async saveAsset() {
const resp = await this.sendRequest({
type: "save"
} as pxt.editor.SaveAssetEditorRequest);

return (resp as pxt.editor.SaveAssetEditorResponse).files;
}

async duplicateAsset(assetId: string, assetType: pxt.AssetType, files: pxt.Map<string>, palette?: string[]) {
await this.sendRequest({
type: "duplicate",
assetId,
assetType,
files,
palette
} as pxt.editor.DuplicateAssetEditorRequest);
}

addEventListener(event: "ready", handler: (ev: pxt.editor.AssetEditorReadyEvent) => void): void;
addEventListener(event: "done-clicked", handler: (ev: pxt.editor.AssetEditorRequestSaveEvent) => void): void;
addEventListener(event: string, handler: (ev: any) => void): void {
super.addEventListener(event, handler);
}

protected handleMessage(event: MessageEvent<any>): void {
const data = event.data;
if (!data) return;

if (data.type === "event") {
this.fireEvent((data as pxt.editor.AssetEditorEvent).kind, data);
}
else {
this.resolvePendingMessage(event);
}
}
}

0 comments on commit f4e9aad

Please sign in to comment.