Skip to content

Commit ddac7c8

Browse files
committed
Implement cross-realm communication emulation via postMessage
1 parent 1ed4082 commit ddac7c8

File tree

5 files changed

+3577
-58
lines changed

5 files changed

+3577
-58
lines changed

package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99
"build": "npm run build:clean && npm run build:lib",
1010
"build:clean": "rimraf dist",
1111
"build:lib": "cross-env BABEL_ENV=production tsc -p tsconfig.json",
12-
"prepublish": "npm run build"
12+
"prepublish": "npm run build",
13+
"test": "jest"
14+
},
15+
"jest": {
16+
"preset": "ts-jest/presets/js-with-babel"
1317
},
1418
"pre-commit": [
1519
"prettify",
16-
"tsc"
20+
"tsc",
21+
"test"
1722
],
1823
"repository": {
1924
"type": "git",
@@ -30,10 +35,13 @@
3035
},
3136
"homepage": "https://github.com/react-figma/figma-api-stub#readme",
3237
"devDependencies": {
38+
"@types/jest": "^24.0.23",
3339
"cross-env": "^6.0.3",
40+
"jest": "^24.9.0",
3441
"pre-commit": "^1.2.2",
3542
"prettier": "^1.18.2",
3643
"rimraf": "^3.0.0",
44+
"ts-jest": "^24.2.0",
3745
"typescript": "3.5.3"
3846
}
3947
}

src/__tests__/postmessage.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { createParentPostMessage, createFigma } from "../stubs";
2+
3+
describe("postMessage", () => {
4+
beforeEach(() => {
5+
// @ts-ignore
6+
global.figma = createFigma({});
7+
// @ts-ignore
8+
global.parent.postMessage = createParentPostMessage(global.figma);
9+
});
10+
11+
it("UI sends message and plugin receives it", () => {
12+
figma.ui.onmessage = jest.fn();
13+
parent.postMessage({ pluginMessage: "abc" }, "*");
14+
15+
expect(figma.ui.onmessage).toHaveBeenCalledTimes(1);
16+
expect(figma.ui.onmessage).toHaveBeenCalledWith("abc", expect.any(Object));
17+
});
18+
19+
it("Plugin sends message and UI receives it", () => {
20+
//@ts-ignore
21+
global.onmessage = jest.fn();
22+
figma.ui.postMessage("abc");
23+
//@ts-ignore
24+
expect(global.onmessage).toHaveBeenCalledTimes(1);
25+
});
26+
});

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { createFigma } from "./stubs";
1+
export { createFigma, createParentPostMessage } from "./stubs";

src/stubs.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,26 @@ const defaultConfig: TConfig = {
1111
export const createFigma = (config: TConfig): PluginAPI => {
1212
const joinedConfig = { ...defaultConfig, ...config };
1313

14+
class UIAPIStub {
15+
onmessage: MessageEventHandler | undefined;
16+
17+
postMessage(pluginMessage: any, options?: UIPostMessageOptions): void {
18+
const message = {
19+
data: { pluginMessage, pluginId: "000000000000000000" },
20+
type: "message"
21+
};
22+
23+
// @ts-ignore
24+
if (global && global.onmessage) {
25+
// @ts-ignore
26+
global.onmessage(message);
27+
}
28+
}
29+
}
30+
1431
class ChildrenMixinStub implements ChildrenMixin {
1532
children: Array<any>;
33+
1634
appendChild(item) {
1735
if (!this.children) {
1836
this.children = [];
@@ -40,6 +58,7 @@ export const createFigma = (config: TConfig): PluginAPI => {
4058
item.parent = this;
4159
this.children.push(item);
4260
}
61+
4362
insertChild(index: number, child: any) {
4463
if (!this.children) {
4564
this.children = [];
@@ -73,12 +92,14 @@ export const createFigma = (config: TConfig): PluginAPI => {
7392
child.parent = this;
7493
this.children.splice(index, 0, child);
7594
}
95+
7696
findAll(callback) {
7797
if (!this.children) {
7898
return [];
7999
}
80100
return this.children.filter(callback);
81101
}
102+
82103
findOne(callback) {
83104
if (!this.children) {
84105
return null;
@@ -101,12 +122,14 @@ export const createFigma = (config: TConfig): PluginAPI => {
101122
}
102123
this.pluginData[key] = value;
103124
}
125+
104126
getPluginData(key: string) {
105127
if (!this.pluginData) {
106128
return;
107129
}
108130
return this.pluginData[key];
109131
}
132+
110133
setSharedPluginData(namespace: string, key: string, value: string) {
111134
if (!this.sharedPluginData) {
112135
this.sharedPluginData = {};
@@ -116,12 +139,14 @@ export const createFigma = (config: TConfig): PluginAPI => {
116139
}
117140
this.pluginData[key] = value;
118141
}
142+
119143
getSharedPluginData(namespace: string, key: string) {
120144
if (!this.sharedPluginData || !this.sharedPluginData[namespace]) {
121145
return;
122146
}
123147
return this.pluginData[namespace][key];
124148
}
149+
125150
remove() {
126151
this.removed = true;
127152
if (this.parent) {
@@ -147,6 +172,7 @@ export const createFigma = (config: TConfig): PluginAPI => {
147172
this.width = width;
148173
this.height = height;
149174
}
175+
150176
resizeWithoutConstraints(width, height) {
151177
this.width = width;
152178
this.height = height;
@@ -156,53 +182,63 @@ export const createFigma = (config: TConfig): PluginAPI => {
156182
class RectangleNodeStub {
157183
type = "RECTANGLE";
158184
}
185+
159186
applyMixins(RectangleNodeStub, [BaseNodeMixinStub, LayoutMixinStub]);
160187

161188
class TextNodeStub {
162189
type = "TEXT";
163190
}
191+
164192
applyMixins(TextNodeStub, [BaseNodeMixinStub, LayoutMixinStub]);
165193

166194
class DocumentNodeStub {
167195
type = "DOCUMENT";
168196
children = [];
169197
}
198+
170199
applyMixins(DocumentNodeStub, [BaseNodeMixinStub, ChildrenMixinStub]);
171200

172201
class PageNodeStub {
173202
type = "PAGE";
174203
children = [];
175204
}
205+
176206
applyMixins(PageNodeStub, [BaseNodeMixinStub, ChildrenMixinStub]);
177207

178208
class FrameNodeStub {
179209
type = "FRAME";
180210
children = [];
181211
}
212+
182213
applyMixins(FrameNodeStub, [BaseNodeMixinStub, ChildrenMixinStub]);
183214

184215
class GroupNodeStub {
185216
type = "GROUP";
186217
}
218+
187219
applyMixins(GroupNodeStub, [BaseNodeMixinStub, ChildrenMixinStub]);
188220

189221
class ComponentNodeStub {
190222
type = "COMPONENT";
191223
children = [];
192224
}
225+
193226
applyMixins(ComponentNodeStub, [BaseNodeMixinStub, ChildrenMixinStub]);
194227

195228
// @ts-ignore
196229
class PluginApiStub implements PluginAPI {
197230
root: DocumentNode;
198231
currentPage: PageNode;
232+
readonly ui: UIAPI;
199233

200234
constructor() {
201235
// @ts-ignore
202236
this.root = new DocumentNodeStub();
203237
// @ts-ignore
204238
this.currentPage = new PageNodeStub();
205239
this.root.appendChild(this.currentPage);
240+
// @ts-ignore
241+
this.ui = new UIAPIStub();
206242
}
207243

208244
// @ts-ignore
@@ -218,24 +254,28 @@ export const createFigma = (config: TConfig): PluginAPI => {
218254
this.currentPage.appendChild(result);
219255
return result;
220256
}
257+
221258
// @ts-ignore
222259
createComponent() {
223260
const result: any = new ComponentNodeStub();
224261
this.currentPage.appendChild(result);
225262
return result;
226263
}
264+
227265
// @ts-ignore
228266
createRectangle() {
229267
const result: any = new RectangleNodeStub();
230268
this.currentPage.appendChild(result);
231269
return result;
232270
}
271+
233272
// @ts-ignore
234273
createText() {
235274
const result: any = new TextNodeStub();
236275
this.currentPage.appendChild(result);
237276
return result;
238277
}
278+
239279
// @ts-ignore
240280
group(nodes: any, parent: any, index) {
241281
if (joinedConfig.simulateErrors && (!nodes || nodes.length === 0)) {
@@ -259,3 +299,12 @@ export const createFigma = (config: TConfig): PluginAPI => {
259299
// @ts-ignore
260300
return new PluginApiStub();
261301
};
302+
303+
export const createParentPostMessage = (figma: PluginAPI) => (
304+
message: { pluginMessage: any },
305+
target: string
306+
) => {
307+
if (figma.ui.onmessage) {
308+
figma.ui.onmessage(message.pluginMessage, { origin: null });
309+
}
310+
};

0 commit comments

Comments
 (0)