-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9cb8865
commit 143e7fa
Showing
12 changed files
with
436 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { assert } from "sinon"; | ||
|
||
import * as BrowserClient from "./BrowserClient.js"; | ||
import * as Test from "./Test.js"; | ||
|
||
const { test } = Test; | ||
|
||
afterEach(async () => { | ||
await navigator.serviceWorker | ||
.getRegistrations() | ||
.then((registrations) => | ||
Promise.all(registrations.map((r) => r.unregister())) | ||
); | ||
}); | ||
|
||
test("a frame making a request to a service worker", async () => { | ||
const [worker] = await Test.createServiceWorker(/* ts */ ` | ||
import * as BrowserServer from "http://localhost:8000/packages/browser/src/BrowserServer.ts"; | ||
const server = BrowserServer.listen({ | ||
address: "foo", | ||
handle(_request) { | ||
return "hi from the worker"; | ||
} | ||
}); | ||
`); | ||
|
||
const client = BrowserClient.from(worker); | ||
const response = await client.fetch("foo", { body: "hi" }); | ||
|
||
assert.match(response, "hi from the worker"); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { | ||
filter, | ||
firstValueFrom, | ||
fromEvent | ||
} from "@daniel-nagy/transporter/Observable"; | ||
import { assert } from "sinon"; | ||
|
||
import * as BrowserClient from "./BrowserClient.js"; | ||
import * as BrowserServer from "./BrowserServer.js"; | ||
import * as Test from "./Test.js"; | ||
|
||
const { test } = Test; | ||
|
||
test("a child frame making a request to a parent frame", async () => { | ||
BrowserServer.listen({ | ||
address: "foo", | ||
handle(_request) { | ||
return "hi from the parent"; | ||
} | ||
}); | ||
|
||
const srcDoc = /* html */ ` | ||
<script type="module" data-transpile> | ||
import * as BrowserClient from "/packages/browser/src/BrowserClient.ts"; | ||
const client = BrowserClient.from(self.parent); | ||
const response = await client.fetch("foo", { body: "hi" }); | ||
self.parent.postMessage( | ||
{ type: "received", response }, | ||
{ targetOrigin: "*" } | ||
); | ||
</script> | ||
`; | ||
|
||
const messageStream = fromEvent<MessageEvent>(self, "message"); | ||
document.body.append(await Test.createIframe(srcDoc)); | ||
|
||
const response = await firstValueFrom( | ||
messageStream.pipe(filter((message) => message.data.type === "received")) | ||
); | ||
|
||
assert.match(response.data.response, "hi"); | ||
}); | ||
|
||
test("a frame making a request to a dedicated worker", async () => { | ||
const worker = await Test.createWorker(/* ts */ ` | ||
import * as BrowserServer from "http://localhost:8000/packages/browser/src/BrowserServer.ts"; | ||
const server = BrowserServer.listen({ | ||
address: "foo", | ||
handle(_request) { | ||
return "hi from the worker"; | ||
} | ||
}); | ||
`); | ||
|
||
const client = BrowserClient.from(worker); | ||
const response = await client.fetch("foo", { body: "hi" }); | ||
|
||
assert.match(response, "hi from the worker"); | ||
}); | ||
|
||
test("a frame making a request to a shared worker", async () => { | ||
const worker = await Test.createSharedWorker(/* ts */ ` | ||
import * as BrowserServer from "http://localhost:8000/packages/browser/src/BrowserServer.ts"; | ||
const server = BrowserServer.listen({ | ||
address: "foo", | ||
handle(_request) { | ||
return "hi from the worker"; | ||
} | ||
}); | ||
`); | ||
|
||
const client = BrowserClient.from(worker); | ||
const response = await client.fetch("foo", { body: "hi" }); | ||
|
||
assert.match(response, "hi from the worker"); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { | ||
filter, | ||
firstValueFrom, | ||
fromEvent, | ||
map | ||
} from "@daniel-nagy/transporter/Observable"; | ||
|
||
import * as Request from "./BrowserRequest.js"; | ||
import * as Response from "./BrowserResponse.js"; | ||
import * as StructuredCloneable from "./StructuredCloneable.js"; | ||
|
||
export { BrowserClient as t }; | ||
|
||
export type Options = { | ||
origin?: string; | ||
}; | ||
|
||
export class BrowserClient { | ||
public readonly origin: string; | ||
public readonly target: Window | Worker | SharedWorker | ServiceWorker; | ||
|
||
constructor({ | ||
origin = "*", | ||
target | ||
}: { | ||
origin?: string; | ||
target: Window | Worker | SharedWorker | ServiceWorker; | ||
}) { | ||
this.origin = origin; | ||
this.target = target; | ||
} | ||
|
||
async fetch( | ||
address: string, | ||
payload: { | ||
body: StructuredCloneable.t; | ||
} | ||
) { | ||
const messageSink = getMessageSink(this.target); | ||
const messageSource = getMessageSource(this.target); | ||
const request = Request.t({ address, ...payload }); | ||
|
||
const response = fromEvent<MessageEvent>(messageSource, "message").pipe( | ||
filter( | ||
(message): message is MessageEvent<Response.t> => | ||
Response.isResponse(message) && message.data.id === request.id | ||
), | ||
map((message) => message.data.body) | ||
); | ||
|
||
// Not sure it this is necessary or useful. | ||
if (this.target instanceof ServiceWorker) | ||
await navigator.serviceWorker.ready; | ||
|
||
messageSink.postMessage(request, { targetOrigin: this.origin }); | ||
|
||
return firstValueFrom(response); | ||
} | ||
} | ||
|
||
export function from( | ||
target: Window | Worker | SharedWorker | ServiceWorker, | ||
options: Options = {} | ||
) { | ||
if (target instanceof SharedWorker) target.port.start(); | ||
return new BrowserClient({ ...options, target }); | ||
} | ||
|
||
function getMessageSink( | ||
target: Window | Worker | SharedWorker | ServiceWorker | ||
) { | ||
switch (true) { | ||
case target instanceof SharedWorker: | ||
return target.port; | ||
default: | ||
return target; | ||
} | ||
} | ||
|
||
function getMessageSource( | ||
target: Window | Worker | SharedWorker | ServiceWorker | ||
) { | ||
switch (true) { | ||
case target instanceof ServiceWorker: | ||
return navigator.serviceWorker; | ||
case target instanceof SharedWorker: | ||
return target.port; | ||
case target instanceof Worker: | ||
return target; | ||
default: | ||
return self; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as JsObject from "@daniel-nagy/transporter/JsObject"; | ||
|
||
import * as StructuredCloneable from "./StructuredCloneable.js"; | ||
|
||
export { Request as t }; | ||
|
||
const Type = "Request"; | ||
|
||
export type Request = { | ||
address: string; | ||
body: StructuredCloneable.t; | ||
id: string; | ||
type: typeof Type; | ||
}; | ||
|
||
export const Request = ({ | ||
address, | ||
body | ||
}: { | ||
address: string; | ||
body: StructuredCloneable.t; | ||
}): Request => ({ | ||
address, | ||
body, | ||
id: crypto.randomUUID(), | ||
type: Type | ||
}); | ||
|
||
export function isRequest(event: MessageEvent): event is MessageEvent<Request> { | ||
return ( | ||
JsObject.isObject(event.data) && | ||
JsObject.has(event.data, "type") && | ||
event.data.type === Type | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as JsObject from "@daniel-nagy/transporter/JsObject"; | ||
|
||
import * as StructuredCloneable from "./StructuredCloneable.js"; | ||
|
||
export { Response as t }; | ||
|
||
const Type = "Response"; | ||
|
||
export type Response = { | ||
body: StructuredCloneable.t; | ||
id: string; | ||
type: typeof Type; | ||
}; | ||
|
||
export const Response = ({ | ||
body, | ||
id | ||
}: { | ||
body: StructuredCloneable.t; | ||
id: string; | ||
}): Response => ({ | ||
body, | ||
id, | ||
type: Type | ||
}); | ||
|
||
export function isResponse( | ||
event: MessageEvent | ||
): event is MessageEvent<Response> { | ||
return ( | ||
JsObject.isObject(event.data) && | ||
JsObject.has(event.data, "type") && | ||
event.data.type === Type | ||
); | ||
} |
Oops, something went wrong.