Add PostMessage Service

This commit is contained in:
Chi Vinh Le
2018-08-10 15:57:07 +02:00
parent 4e493ad1da
commit ea02e47241
4 changed files with 141 additions and 0 deletions
@@ -6,6 +6,7 @@ import { MediaQueryMatchers } from "react-responsive";
import { Formatter } from "react-timeago";
import { Environment } from "relay-runtime";
import { PostMessageService } from "talk-framework/lib/postMessage";
import { UIContext } from "talk-ui/components";
import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
@@ -22,6 +23,9 @@ export interface TalkContext {
/** media query values for testing purposes */
mediaQueryValues?: MediaQueryMatchers;
/** postMessage service */
postMessage: PostMessageService;
/**
* A way to listen for clicks that are e.g. outside of the
* current frame for `ClickOutside`
@@ -10,6 +10,7 @@ import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
import { generateMessages, LocalesData, negotiateLanguages } from "../i18n";
import { fetchQuery } from "../network";
import { PostMessageService } from "../postMessage";
import { TalkContext } from "./TalkContext";
interface CreateContextArguments {
@@ -99,6 +100,7 @@ export default async function createContext({
pym,
eventEmitter,
registerClickFarAway,
postMessage: new PostMessageService(),
};
// Run custom initializations.
@@ -0,0 +1,69 @@
import { PostMessageService } from "./postMessage";
it("post and subscribe to a message", done => {
const postMessage = new PostMessageService();
const cancel = postMessage.on("test", value => {
expect(value).toBe("value");
done();
cancel();
});
postMessage.send("test", "value", window);
});
it("send to a different origin", done => {
const postMessage = new PostMessageService();
const cancelA = postMessage.on("testA", value => {
throw new Error("Should not reach this");
});
const cancelB = postMessage.on("testB", value => {
done();
cancelA();
cancelB();
});
postMessage.send("testA", "value", window, "http://i-do-not-exist.de");
postMessage.send("testB", "value", window);
});
it("should cancel", done => {
const postMessage = new PostMessageService();
const cancelA = postMessage.on("testA", value => {
throw new Error("Should not reach this");
});
const cancelB = postMessage.on("testB", value => {
done();
cancelB();
});
cancelA();
postMessage.send("testA", "value", window);
postMessage.send("testB", "value", window);
});
it("different scopes are isolated", done => {
const postMessageA = new PostMessageService("scopeA");
const postMessageB = new PostMessageService("scopeB");
const cancelA = postMessageA.on("testA", value => {
throw new Error("Should not reach this");
});
const cancelB = postMessageA.on("testB", value => {
done();
cancelA();
cancelB();
});
postMessageB.send("testA", "value", window);
postMessageA.send("testB", "value", window);
});
it("different message names are isolated", done => {
const postMessage = new PostMessageService();
const cancelA = postMessage.on("testA", value => {
expect(value).toBe("valueA");
});
const cancelB = postMessage.on("testB", value => {
expect(value).toBe("valueB");
done();
cancelA();
cancelB();
});
postMessage.send("testA", "valueA", window);
postMessage.send("testB", "valueB", window);
});
@@ -0,0 +1,66 @@
type PostMessageHandler = (value: any, name: string) => void;
/**
* Wrapper around the HTML postMessage API.
*/
export class PostMessageService {
private origin: string;
private scope: string;
constructor(
scope = "talk",
origin: string = `${location.protocol}//${location.host}`
) {
this.origin = origin;
this.scope = scope;
}
/**
* Send a message over the postMessage API
* @param name string name of the message
* @param value string value of the message
* @param target Window target window, e.g. window.opener
* @param targetOrigin string origin of target
*/
public send(name: string, value: any, target: Window, targetOrigin?: string) {
if (!target) {
return;
}
// Serialize the message to be sent via postMessage.
const msg = { name, value, scope: this.scope };
// Send the message.
target.postMessage(msg, targetOrigin || this.origin);
}
/**
* Subscribe to messages
* @param name string Name of the message
* @param handler PostMessageHandler
*/
public on(name: string, handler: PostMessageHandler) {
const listener = (event: MessageEvent) => {
if (!event.origin) {
if (process.env.NODE_ENV !== "test") {
// tslint:disable-next-line:no-console
console.warn("empty origin received in postMessage", name);
}
} else if (event.origin !== this.origin) {
return;
}
if (event.data.scope !== this.scope) {
return;
}
if (event.data.name !== name) {
return;
}
handler(event.data.value, event.data.name);
};
// Attach the listener to the target.
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}
}