UNPKG

6.56 kBPlain TextView Raw
1/*
2 * Copyright 2021 Inrupt Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal in
6 * the Software without restriction, including without limitation the rights to use,
7 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8 * Software, and to permit persons to whom the Software is furnished to do so,
9 * subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22import { jest, it, describe, expect } from "@jest/globals";
23import {
24 postRedirectUrlToParent,
25 redirectInIframe,
26 setupIframeListener,
27} from "../src/iframe";
28
29describe("redirectInIframe", () => {
30 it("creates an iframe with the appropriate attributes and performs the redirection in it", () => {
31 redirectInIframe("http://some.iri");
32
33 const iframe = document.getElementsByTagName("iframe")[0];
34
35 // This verifies that the iframe has been added to the DOM, i.e. that
36 // window.document.body.appendChild has been called.
37 expect(iframe).not.toBeUndefined();
38
39 // The iframe has the appropriate attributes.
40 expect(iframe.getAttribute("hidden")).toBe("true");
41 expect(iframe.getAttribute("sandbox")).toBe(
42 "allow-scripts allow-same-origin"
43 );
44 expect(iframe.getAttribute("src")).toBe("http://some.iri");
45 });
46});
47
48describe("setupIframeListener", () => {
49 const setupDom = (originMatch: boolean, sourceMatch: boolean) => {
50 jest.spyOn(window, "location", "get").mockReturnValue({
51 // The test iframe message has "" as an origin.
52 origin: originMatch ? "" : "https://some.other/origin",
53 } as Location);
54
55 // Mock mismatching window
56 redirectInIframe("http://some.iri");
57 const iframe = document.getElementsByTagName("iframe")[0];
58 jest
59 .spyOn(iframe, "contentWindow", "get")
60 .mockReturnValue((sourceMatch ? null : ({} as unknown)) as Window);
61 };
62
63 const mockEventListener = () => {
64 let messageReceived = false;
65 // This promis prevents the test from completing while the message event
66 // hasn't been received, to ensure the listener callback is executed.
67
68 // The following promise has an async executor to be able to sleep
69 // while waiting for a change.
70 // eslint-disable-next-line no-async-promise-executor
71 const blockingPromise = new Promise(async (resolve) => {
72 while (!messageReceived) {
73 // Wait for the message event to be processed.
74 // eslint-disable-next-line no-await-in-loop
75 await new Promise((resolveSleep) => setTimeout(resolveSleep, 100));
76 }
77 resolve(undefined);
78 });
79 window.addEventListener("message", () => {
80 messageReceived = true;
81 });
82 return blockingPromise;
83 };
84
85 it("ignores message from iframes on different origins", async () => {
86 const callback = jest.fn();
87 const blockingPromise = mockEventListener();
88 setupDom(false, true);
89 // eslint-disable-next-line @typescript-eslint/no-explicit-any
90 setupIframeListener(callback as any);
91
92 window.postMessage(
93 {
94 redirectUrl: "http://some.redirect/url",
95 },
96 "http://localhost"
97 );
98
99 await blockingPromise;
100
101 expect(callback).not.toHaveBeenCalled();
102 });
103
104 it("ignores messages from iframes with an unknown source", async () => {
105 const callback = jest.fn();
106 const blockingPromise = mockEventListener();
107 setupDom(true, false);
108 // eslint-disable-next-line @typescript-eslint/no-explicit-any
109 setupIframeListener(callback as any);
110
111 window.postMessage(
112 {
113 redirectUrl: "http://some.redirect/url",
114 },
115 "http://localhost"
116 );
117
118 await blockingPromise;
119
120 expect(callback).not.toHaveBeenCalled();
121 });
122
123 it("ignores messages from valid iframes but with an unexpected structure", async () => {
124 const callback = jest.fn();
125 const blockingPromise = mockEventListener();
126 setupDom(true, true);
127 // eslint-disable-next-line @typescript-eslint/no-explicit-any
128 setupIframeListener(callback as any);
129
130 window.postMessage(
131 {
132 // We expect a message with a 'redirectUrl' property.
133 someUnknownkey: "some value",
134 },
135 "http://localhost"
136 );
137
138 await blockingPromise;
139
140 expect(callback).not.toHaveBeenCalled();
141 });
142
143 it("calls the given callback", async () => {
144 const callback = jest.fn();
145 const blockingPromise = mockEventListener();
146 setupDom(true, true);
147 // eslint-disable-next-line @typescript-eslint/no-explicit-any
148 setupIframeListener(callback as any);
149
150 window.postMessage(
151 {
152 redirectUrl: "http://some.redirect/url",
153 },
154 "http://localhost"
155 );
156
157 await blockingPromise;
158
159 expect(callback).toHaveBeenCalled();
160 });
161
162 it("cleans up the iframe after the message is received", async () => {
163 const callback = jest.fn();
164 const blockingPromise = mockEventListener();
165 setupDom(true, true);
166 // eslint-disable-next-line @typescript-eslint/no-explicit-any
167 setupIframeListener(callback as any);
168
169 expect(document.getElementsByTagName("iframe")[0]).not.toBeUndefined();
170
171 window.postMessage(
172 {
173 redirectUrl: "http://some.redirect/url",
174 },
175 "http://localhost"
176 );
177
178 await blockingPromise;
179
180 // Verify that the iframe has correctly been removed from the DOM
181 expect(document.getElementsByTagName("iframe")[0]).toBeUndefined();
182 });
183});
184
185describe("postRedirectUrlToParent", () => {
186 it("posts a message to the parent window with the provided url", () => {
187 const spyPost = jest.spyOn(window.top, "postMessage");
188 jest.spyOn(window, "location", "get").mockReturnValue({
189 origin: "https://some.origin/",
190 } as unknown as Location);
191 postRedirectUrlToParent("https://some.redirect.url/");
192 expect(spyPost).toHaveBeenCalledWith(
193 { redirectUrl: "https://some.redirect.url/" },
194 "https://some.origin/"
195 );
196 });
197});