Skip to content

Commit 92a20ee

Browse files
committed
[broadcast] enhance openGephiLite API
- added a timeout - integrate the await newInstance message logic - throw if host does not match (broadcast API security constraint)
1 parent 676786e commit 92a20ee

File tree

3 files changed

+37
-20
lines changed

3 files changed

+37
-20
lines changed

packages/broadcast/README.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@ import Graph from "graphology";
1313
async function openGraphInGephiLite(graph: Graph) {
1414
const driver = new GephiLiteDriver();
1515

16-
await new Promise<void>((resolve) => {
17-
// Wait for new instance to be fully working:
18-
driver.on("newInstance", () => {
19-
resolve();
20-
});
21-
driver.openGephiLite();
22-
});
16+
await driver.openGephiLite();
2317

2418
await driver.importGraph(graph.toJSON());
2519

packages/broadcast/src/driver.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export class GephiLiteDriver extends TypedEventEmitter<GephiLiteEvents> {
4141
private timeout: number = 2000;
4242
private channel: BroadcastChannel;
4343
private pendingReplies: Map<string, (payload: unknown) => void> = new Map();
44+
private window: Window | null = null;
4445

4546
constructor(name: string = uuidV4()) {
4647
super();
@@ -141,17 +142,48 @@ export class GephiLiteDriver extends TypedEventEmitter<GephiLiteEvents> {
141142
setFilters(filters: FiltersState) {
142143
return this.callMethod<SetFiltersMethod>("setFilters", filters);
143144
}
145+
getWindow() {
146+
return this.window;
147+
}
144148

145149
/**
146150
* Helper/lifecycle methods:
147151
* *************************
148152
*/
149-
openGephiLite({
153+
async openGephiLite({
150154
baseUrl = "/gephi-lite",
151155
target = "_blank",
152156
features = "noopener",
153-
}: { baseUrl?: string; target?: string; features?: string } = {}) {
154-
return open(`${baseUrl}?broadcast=${this.name}`, target, features);
157+
timeout = 10000,
158+
}: { baseUrl?: string; target?: string; features?: string; timeout?: number } = {}) {
159+
let url: URL | undefined = undefined;
160+
try {
161+
url = new URL(baseUrl);
162+
} catch {
163+
// not an url, assuming a path, nothing to check
164+
}
165+
166+
if (url) {
167+
if (url.host !== window.location.host) {
168+
throw new Error("Communicating with Gephi Lite is only possible on the same domain.");
169+
}
170+
}
171+
172+
const gephiLiteWindow = await new Promise<Window | null>((resolve, reject) => {
173+
const openingGephiLiteTimeout = timeout
174+
? setTimeout(() => {
175+
reject(new Error(`Couldn't set up communication channel with Gephi Lite before ${timeout}`));
176+
}, timeout)
177+
: null;
178+
179+
// Wait for new instance to be fully working:
180+
this.on("newInstance", () => {
181+
if (openingGephiLiteTimeout) clearTimeout(openingGephiLiteTimeout);
182+
resolve(this.getWindow());
183+
});
184+
this.window = open(`${baseUrl}?broadcast=${this.name}`, target, features);
185+
});
186+
return gephiLiteWindow;
155187
}
156188
destroy(): void {
157189
this.channel.onmessage = null;

packages/gephi-lite/src/core/broadcast/utils.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,7 @@ export async function openInNewTab({
1212
filters = filtersAtom.get(),
1313
}: { dataset?: GraphDataset; appearance?: AppearanceState; filters?: FiltersState } = {}) {
1414
const driver = new GephiLiteDriver();
15-
16-
await new Promise<void>((resolve) => {
17-
// Wait for new instance to be fully working:
18-
driver.on("newInstance", () => {
19-
resolve();
20-
});
21-
driver.openGephiLite({
22-
baseUrl: location.pathname,
23-
});
24-
});
15+
await driver.openGephiLite({ baseUrl: location.pathname });
2516

2617
await Promise.all([driver.setAppearance(appearance), driver.setFilters(filters)]);
2718
await driver.setGraphDataset(dataset);

0 commit comments

Comments
 (0)