Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/channel/ChannelReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import { InternalEmitterRequestType, InternalReceiverRequestType } from "./types

export type ChannelReceiverOptions = {
readyTimeout: number
allowedOrigin: string | null
}

export const channelReceiverDefaultOptions: ChannelReceiverOptions &
Partial<ChannelNetworkOptions> = {
readyTimeout: 20000,
requestIDPrefix: "receiver-",
allowedOrigin: null,
}

export type AllChannelReceiverOptions = ChannelReceiverOptions & ChannelNetworkOptions
Expand Down Expand Up @@ -65,7 +67,10 @@ export abstract class ChannelReceiver<
>(
request,
(request) => {
window.parent.postMessage(request, "*")
window.parent.postMessage(
request,
this.options.allowedOrigin ? this.options.allowedOrigin : "*",
)
},
{
timeout: this.options.readyTimeout,
Expand All @@ -79,6 +84,14 @@ export abstract class ChannelReceiver<

/** Handles public messages */
private _onPublicMessage(event: MessageEvent<unknown>): void {
// Validate origin if allowedOrigin is configured
if (
this.options.allowedOrigin &&
this.options.allowedOrigin !== event.origin
) {
return
}

try {
const message = validateMessage(event.data)

Expand All @@ -102,6 +115,7 @@ export abstract class ChannelReceiver<
debug: this.options.debug,
requestIDPrefix: this.options.requestIDPrefix,
readyTimeout: this.options.readyTimeout,
allowedOrigin: this.options.allowedOrigin,
}

const response = createSuccessResponseMessage(message.requestID, undefined)
Expand Down
8 changes: 7 additions & 1 deletion src/channel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,13 @@ export type InternalEmitterTransactions<
[InternalEmitterRequestType.Connect]: Transaction<
RequestMessage<
InternalEmitterRequestType.Connect,
Partial<Omit<TReceiverOptions, "debug" | "requestIDPrefix" | "readyTimeout">> | undefined
| Partial<
Omit<
TReceiverOptions,
"debug" | "requestIDPrefix" | "readyTimeout" | "allowedOrigin"
>
>
| undefined
>
>
}
Expand Down
90 changes: 49 additions & 41 deletions src/kit/SimulatorManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ import { StateEventType } from "./types"

type ManagerConstructorArgs = {
slices?: SliceZone
allowedOrigin?: string
}

export class SimulatorManager {
public state: State
private _api: SimulatorAPI | null
private _initialized: boolean
private _allowedOrigin: string | null

constructor(args?: ManagerConstructorArgs) {
this.state = new State(args)
this._api = null
this._initialized = false
this._allowedOrigin = args?.allowedOrigin ?? null
}

async init(): Promise<void> {
Expand Down Expand Up @@ -57,54 +60,59 @@ export class SimulatorManager {

private async _initAPI(): Promise<void> {
// Register SimulatorAPI request handlers
this._api = new SimulatorAPI({
[ClientRequestType.SetSliceZone]: (req, res) => {
this.state.setSliceZone(req.data)

return res.success()
},
[ClientRequestType.ScrollToSlice]: (req, res) => {
// Error if `sliceIndex` is invalid
if (req.data.sliceIndex < 0) {
return res.error("`sliceIndex` must be > 0", 400)
} else if (req.data.sliceIndex >= this.state.slices.length) {
return res.error(
`\`sliceIndex\` must be < ${this.state.slices.length} (\`<SliceZone />\` current length)`,
400,
)
}
this._api = new SimulatorAPI(
{
[ClientRequestType.SetSliceZone]: (req, res) => {
this.state.setSliceZone(req.data)

return res.success()
},
[ClientRequestType.ScrollToSlice]: (req, res) => {
// Error if `sliceIndex` is invalid
if (req.data.sliceIndex < 0) {
return res.error("`sliceIndex` must be > 0", 400)
} else if (req.data.sliceIndex >= this.state.slices.length) {
return res.error(
`\`sliceIndex\` must be < ${this.state.slices.length} (\`<SliceZone />\` current length)`,
400,
)
}

const $sliceZone = getSliceZoneDOM(this.state.slices.length)
if (!$sliceZone) {
return res.error("Failed to find `<SliceZone />`", 500)
}
const $sliceZone = getSliceZoneDOM(this.state.slices.length)
if (!$sliceZone) {
return res.error("Failed to find `<SliceZone />`", 500)
}

// Destroy existing active slice as we're about to scroll
this.state.activeSlice = null
// Destroy existing active slice as we're about to scroll
this.state.activeSlice = null

const $slice = $sliceZone.children[req.data.sliceIndex]
if (!$slice) {
return res.error(
`Failed fo find slice at index $\`{req.data.sliceIndex}\` in \`<SliceZone />\``,
500,
)
}
const $slice = $sliceZone.children[req.data.sliceIndex]
if (!$slice) {
return res.error(
`Failed fo find slice at index $\`{req.data.sliceIndex}\` in \`<SliceZone />\``,
500,
)
}

// Scroll to Slice
$slice.scrollIntoView({
behavior: req.data.behavior,
block: req.data.block,
inline: req.data.inline,
})
// Scroll to Slice
$slice.scrollIntoView({
behavior: req.data.behavior,
block: req.data.block,
inline: req.data.inline,
})

// Update active slice after scrolling
if (this._api?.options.activeSliceAPI) {
setTimeout(this.state.setActiveSlice, 750)
}
// Update active slice after scrolling
if (this._api?.options.activeSliceAPI) {
setTimeout(this.state.setActiveSlice, 750)
}

return res.success()
return res.success()
},
},
{
allowedOrigin: this._allowedOrigin,
},
})
)

// Mark API as ready
await this._api.ready()
Expand Down
29 changes: 29 additions & 0 deletions test/SimulatorAPI.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,35 @@ const callsPostFormattedRequestCorrectly = <
},
]

it("passes allowedOrigin through to receiver options", () => {
const simulatorAPI = new SimulatorAPI(
{
[ClientRequestType.SetSliceZone]: (_req, res) => {
return res.success()
},
[ClientRequestType.ScrollToSlice]: (_req, res) => {
return res.success()
},
},
{ allowedOrigin: "https://example.com" },
)

expect(simulatorAPI.options.allowedOrigin).toBe("https://example.com")
})

it("defaults allowedOrigin to null when not provided", () => {
const simulatorAPI = new SimulatorAPI({
[ClientRequestType.SetSliceZone]: (_req, res) => {
return res.success()
},
[ClientRequestType.ScrollToSlice]: (_req, res) => {
return res.success()
},
})

expect(simulatorAPI.options.allowedOrigin).toBeNull()
})

it(...callsPostFormattedRequestCorrectly(APIRequestType.SetActiveSlice, null))
it(
...callsPostFormattedRequestCorrectly(APIRequestType.SetSliceZoneSize, {
Expand Down
Loading
Loading