Skip to content

Commit

Permalink
Add 'connected' function to ping across to the other end of the comms
Browse files Browse the repository at this point in the history
  • Loading branch information
mrvisser committed Jun 2, 2020
1 parent 78bf6db commit 506479c
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
54 changes: 53 additions & 1 deletion src/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import {
} from './types';
import { hasValue } from './util';

type SystemProtocol = {
ping: {
body: {};
response: {};
};
};

function mkPayloadType<
P extends Protoframe,
T extends ProtoframeMessageType<P>
Expand Down Expand Up @@ -376,6 +383,26 @@ export class ProtoframePublisher<P extends Protoframe>

export class ProtoframePubsub<P extends Protoframe>
implements AbstractProtoframePubsub<P> {
public static async connect<P extends Protoframe>(
pubsub: ProtoframePubsub<P>,
tries = 25,
timeout = 500,
): Promise<ProtoframePubsub<P>> {
for (let i = 0; i < tries; i++) {
try {
await pubsub.ping({ timeout });
return pubsub;
} catch (_) {
continue;
}
}
throw new Error(
`Could not connect on protocol ${pubsub.protocol.type} after ${
tries * timeout
}ms`,
);
}

/**
* We are a "parent" page that is embedding an iframe, and we wish to connect
* to that iframe for communication.
Expand Down Expand Up @@ -432,14 +459,39 @@ export class ProtoframePubsub<P extends Protoframe>
);
}

private systemProtocol: ProtoframeDescriptor<SystemProtocol> = {
type: `system|${this.protocol.type}`,
};
private listeners: [Window, (ev: MessageEvent) => void][] = [];

constructor(
private readonly protocol: ProtoframeDescriptor<P>,
private readonly targetWindow: Window,
private readonly thisWindow: Window = window,
private readonly targetOrigin: string = '*',
) {}
) {
// Answer to ping requests
handleAsk0(
thisWindow,
targetWindow,
this.systemProtocol,
'ping',
targetOrigin,
() => Promise.resolve({}),
);
}

public async ping({ timeout = 10000 }: { timeout?: number }): Promise<void> {
await ask0(
this.thisWindow,
this.targetWindow,
this.systemProtocol,
'ping',
{},
this.targetOrigin,
timeout,
);
}

public handleTell<
T extends ProtoframeMessageType<P>,
Expand Down
40 changes: 40 additions & 0 deletions test/connector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,46 @@ describe('ProtoframeSubscriber', () => {
});

describe('ProtoframePubsub', () => {
describe('connect', () => {
it('should fail if we cannot connect within the allocated time', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const iframe: any = {
contentWindow: {
// This iframe window we will post messages on is not linked to the
// one we are listening to. So any `ask` messages will be dropped
postMessage: (): void => undefined,
},
};
spyOn(iframe.contentWindow, 'postMessage');

const pubsub = ProtoframePubsub.parent(cacheProtocol, iframe);
try {
await expectAsync(
ProtoframePubsub.connect(pubsub, 5, 10),
).toBeRejectedWithError(
'Could not connect on protocol cache after 50ms',
);

// Ensure it attempted to connect 5 times
expect(iframe.contentWindow.postMessage).toHaveBeenCalledTimes(5);
} finally {
pubsub.destroy();
}
});
it('should connect if there is a connector on both ends', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const iframe: any = {
contentWindow: window,
};
const pubsub = ProtoframePubsub.parent(cacheProtocol, iframe);
try {
const connectedPubsub = await ProtoframePubsub.connect(pubsub, 5, 10);
expect(pubsub).toBe(connectedPubsub);
} finally {
pubsub.destroy();
}
});
});
describe('parent', () => {
it('should fail if the child iframe has no contentWindow', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down

0 comments on commit 506479c

Please sign in to comment.