api-web-request-spec.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. import { expect } from 'chai';
  2. import * as http from 'http';
  3. import * as http2 from 'http2';
  4. import * as qs from 'querystring';
  5. import * as path from 'path';
  6. import * as fs from 'fs';
  7. import * as url from 'url';
  8. import * as WebSocket from 'ws';
  9. import { ipcMain, protocol, session, WebContents, webContents } from 'electron/main';
  10. import { AddressInfo, Socket } from 'net';
  11. import { listen, defer } from './lib/spec-helpers';
  12. import { once } from 'events';
  13. import { ReadableStream } from 'stream/web';
  14. const fixturesPath = path.resolve(__dirname, 'fixtures');
  15. describe('webRequest module', () => {
  16. const ses = session.defaultSession;
  17. const server = http.createServer((req, res) => {
  18. if (req.url === '/serverRedirect') {
  19. res.statusCode = 301;
  20. res.setHeader('Location', 'http://' + req.rawHeaders[1]);
  21. res.end();
  22. } else if (req.url === '/contentDisposition') {
  23. res.setHeader('content-disposition', [' attachment; filename=aa%E4%B8%ADaa.txt']);
  24. const content = req.url;
  25. res.end(content);
  26. } else {
  27. res.setHeader('Custom', ['Header']);
  28. let content = req.url;
  29. if (req.headers.accept === '*/*;test/header') {
  30. content += 'header/received';
  31. }
  32. if (req.headers.origin === 'http://new-origin') {
  33. content += 'new/origin';
  34. }
  35. res.end(content);
  36. }
  37. });
  38. let defaultURL: string;
  39. let http2URL: string;
  40. const certPath = path.join(fixturesPath, 'certificates');
  41. const h2server = http2.createSecureServer({
  42. key: fs.readFileSync(path.join(certPath, 'server.key')),
  43. cert: fs.readFileSync(path.join(certPath, 'server.pem'))
  44. }, async (req, res) => {
  45. if (req.method === 'POST') {
  46. const chunks = [];
  47. for await (const chunk of req) chunks.push(chunk);
  48. res.end(Buffer.concat(chunks).toString('utf8'));
  49. } else {
  50. res.end('<html></html>');
  51. }
  52. });
  53. before(async () => {
  54. protocol.registerStringProtocol('cors', (req, cb) => cb(''));
  55. defaultURL = (await listen(server)).url + '/';
  56. await new Promise<void>((resolve) => {
  57. h2server.listen(0, '127.0.0.1', () => resolve());
  58. });
  59. http2URL = `https://127.0.0.1:${(h2server.address() as AddressInfo).port}/`;
  60. console.log(http2URL);
  61. });
  62. after(() => {
  63. server.close();
  64. h2server.close();
  65. protocol.unregisterProtocol('cors');
  66. });
  67. let contents: WebContents;
  68. // NB. sandbox: true is used because it makes navigations much (~8x) faster.
  69. before(async () => {
  70. contents = (webContents as typeof ElectronInternal.WebContents).create({ sandbox: true });
  71. // const w = new BrowserWindow({webPreferences: {sandbox: true}})
  72. // contents = w.webContents
  73. await contents.loadFile(path.join(fixturesPath, 'pages', 'fetch.html'));
  74. });
  75. after(() => contents.destroy());
  76. async function ajax (url: string, options = {}) {
  77. return contents.executeJavaScript(`ajax("${url}", ${JSON.stringify(options)})`);
  78. }
  79. describe('webRequest.onBeforeRequest', () => {
  80. afterEach(() => {
  81. ses.webRequest.onBeforeRequest(null);
  82. });
  83. const cancel = (details: Electron.OnBeforeRequestListenerDetails, callback: (response: Electron.CallbackResponse) => void) => {
  84. callback({ cancel: true });
  85. };
  86. it('can cancel the request', async () => {
  87. ses.webRequest.onBeforeRequest(cancel);
  88. await expect(ajax(defaultURL)).to.eventually.be.rejected();
  89. });
  90. it('can filter URLs', async () => {
  91. const filter = { urls: [defaultURL + 'filter/*'] };
  92. ses.webRequest.onBeforeRequest(filter, cancel);
  93. const { data } = await ajax(`${defaultURL}nofilter/test`);
  94. expect(data).to.equal('/nofilter/test');
  95. await expect(ajax(`${defaultURL}filter/test`)).to.eventually.be.rejected();
  96. });
  97. it('can filter URLs and types', async () => {
  98. const filter1: Electron.WebRequestFilter = { urls: [defaultURL + 'filter/*'], types: ['xhr'] };
  99. ses.webRequest.onBeforeRequest(filter1, cancel);
  100. const { data } = await ajax(`${defaultURL}nofilter/test`);
  101. expect(data).to.equal('/nofilter/test');
  102. await expect(ajax(`${defaultURL}filter/test`)).to.eventually.be.rejected();
  103. const filter2: Electron.WebRequestFilter = { urls: [defaultURL + 'filter/*'], types: ['stylesheet'] };
  104. ses.webRequest.onBeforeRequest(filter2, cancel);
  105. expect((await ajax(`${defaultURL}nofilter/test`)).data).to.equal('/nofilter/test');
  106. expect((await ajax(`${defaultURL}filter/test`)).data).to.equal('/filter/test');
  107. });
  108. it('receives details object', async () => {
  109. ses.webRequest.onBeforeRequest((details, callback) => {
  110. expect(details.id).to.be.a('number');
  111. expect(details.timestamp).to.be.a('number');
  112. expect(details.webContentsId).to.be.a('number');
  113. expect(details.webContents).to.be.an('object');
  114. expect(details.webContents!.id).to.equal(details.webContentsId);
  115. expect(details.frame).to.be.an('object');
  116. expect(details.url).to.be.a('string').that.is.equal(defaultURL);
  117. expect(details.method).to.be.a('string').that.is.equal('GET');
  118. expect(details.resourceType).to.be.a('string').that.is.equal('xhr');
  119. expect(details.uploadData).to.be.undefined();
  120. callback({});
  121. });
  122. const { data } = await ajax(defaultURL);
  123. expect(data).to.equal('/');
  124. });
  125. it('receives post data in details object', async () => {
  126. const postData = {
  127. name: 'post test',
  128. type: 'string'
  129. };
  130. ses.webRequest.onBeforeRequest((details, callback) => {
  131. expect(details.url).to.equal(defaultURL);
  132. expect(details.method).to.equal('POST');
  133. expect(details.uploadData).to.have.lengthOf(1);
  134. const data = qs.parse(details.uploadData[0].bytes.toString());
  135. expect(data).to.deep.equal(postData);
  136. callback({ cancel: true });
  137. });
  138. await expect(ajax(defaultURL, {
  139. method: 'POST',
  140. body: qs.stringify(postData)
  141. })).to.eventually.be.rejected();
  142. });
  143. it('can redirect the request', async () => {
  144. ses.webRequest.onBeforeRequest((details, callback) => {
  145. if (details.url === defaultURL) {
  146. callback({ redirectURL: `${defaultURL}redirect` });
  147. } else {
  148. callback({});
  149. }
  150. });
  151. const { data } = await ajax(defaultURL);
  152. expect(data).to.equal('/redirect');
  153. });
  154. it('does not crash for redirects', async () => {
  155. ses.webRequest.onBeforeRequest((details, callback) => {
  156. callback({ cancel: false });
  157. });
  158. await ajax(defaultURL + 'serverRedirect');
  159. await ajax(defaultURL + 'serverRedirect');
  160. });
  161. it('works with file:// protocol', async () => {
  162. ses.webRequest.onBeforeRequest((details, callback) => {
  163. callback({ cancel: true });
  164. });
  165. const fileURL = url.format({
  166. pathname: path.join(fixturesPath, 'blank.html').replace(/\\/g, '/'),
  167. protocol: 'file',
  168. slashes: true
  169. });
  170. await expect(ajax(fileURL)).to.eventually.be.rejected();
  171. });
  172. it('can handle a streaming upload', async () => {
  173. // Streaming fetch uploads are only supported on HTTP/2, which is only
  174. // supported over TLS, so...
  175. session.defaultSession.setCertificateVerifyProc((req, cb) => cb(0));
  176. defer(() => {
  177. session.defaultSession.setCertificateVerifyProc(null);
  178. });
  179. const contents = (webContents as typeof ElectronInternal.WebContents).create({ sandbox: true });
  180. defer(() => contents.close());
  181. await contents.loadURL(http2URL);
  182. ses.webRequest.onBeforeRequest((details, callback) => {
  183. callback({});
  184. });
  185. const result = await contents.executeJavaScript(`
  186. const stream = new ReadableStream({
  187. async start(controller) {
  188. controller.enqueue('hello world');
  189. controller.close();
  190. },
  191. }).pipeThrough(new TextEncoderStream());
  192. fetch("${http2URL}", {
  193. method: 'POST',
  194. body: stream,
  195. duplex: 'half',
  196. }).then(r => r.text())
  197. `);
  198. expect(result).to.equal('hello world');
  199. });
  200. it('can handle a streaming upload if the uploadData is read', async () => {
  201. // Streaming fetch uploads are only supported on HTTP/2, which is only
  202. // supported over TLS, so...
  203. session.defaultSession.setCertificateVerifyProc((req, cb) => cb(0));
  204. defer(() => {
  205. session.defaultSession.setCertificateVerifyProc(null);
  206. });
  207. const contents = (webContents as typeof ElectronInternal.WebContents).create({ sandbox: true });
  208. defer(() => contents.close());
  209. await contents.loadURL(http2URL);
  210. function makeStreamFromPipe (pipe: any): ReadableStream {
  211. const buf = new Uint8Array(1024 * 1024 /* 1 MB */);
  212. return new ReadableStream({
  213. async pull (controller) {
  214. try {
  215. const rv = await pipe.read(buf);
  216. if (rv > 0) {
  217. controller.enqueue(buf.subarray(0, rv));
  218. } else {
  219. controller.close();
  220. }
  221. } catch (e) {
  222. controller.error(e);
  223. }
  224. }
  225. });
  226. }
  227. ses.webRequest.onBeforeRequest(async (details, callback) => {
  228. const chunks = [];
  229. for await (const chunk of makeStreamFromPipe((details.uploadData[0] as any).body)) { chunks.push(chunk); }
  230. callback({});
  231. });
  232. const result = await contents.executeJavaScript(`
  233. const stream = new ReadableStream({
  234. async start(controller) {
  235. controller.enqueue('hello world');
  236. controller.close();
  237. },
  238. }).pipeThrough(new TextEncoderStream());
  239. fetch("${http2URL}", {
  240. method: 'POST',
  241. body: stream,
  242. duplex: 'half',
  243. }).then(r => r.text())
  244. `);
  245. // NOTE: since the upload stream was consumed by the onBeforeRequest
  246. // handler, it can't be used again to upload to the actual server.
  247. // This is a limitation of the WebRequest API.
  248. expect(result).to.equal('');
  249. });
  250. });
  251. describe('webRequest.onBeforeSendHeaders', () => {
  252. afterEach(() => {
  253. ses.webRequest.onBeforeSendHeaders(null);
  254. ses.webRequest.onSendHeaders(null);
  255. });
  256. it('receives details object', async () => {
  257. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  258. expect(details.requestHeaders).to.be.an('object');
  259. expect(details.requestHeaders['Foo.Bar']).to.equal('baz');
  260. callback({});
  261. });
  262. const { data } = await ajax(defaultURL, { headers: { 'Foo.Bar': 'baz' } });
  263. expect(data).to.equal('/');
  264. });
  265. it('can change the request headers', async () => {
  266. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  267. const requestHeaders = details.requestHeaders;
  268. requestHeaders.Accept = '*/*;test/header';
  269. callback({ requestHeaders: requestHeaders });
  270. });
  271. const { data } = await ajax(defaultURL);
  272. expect(data).to.equal('/header/received');
  273. });
  274. it('can change the request headers on a custom protocol redirect', async () => {
  275. protocol.registerStringProtocol('no-cors', (req, callback) => {
  276. if (req.url === 'no-cors://fake-host/redirect') {
  277. callback({
  278. statusCode: 302,
  279. headers: {
  280. Location: 'no-cors://fake-host'
  281. }
  282. });
  283. } else {
  284. let content = '';
  285. if (req.headers.Accept === '*/*;test/header') {
  286. content = 'header-received';
  287. }
  288. callback(content);
  289. }
  290. });
  291. // Note that we need to do navigation every time after a protocol is
  292. // registered or unregistered, otherwise the new protocol won't be
  293. // recognized by current page when NetworkService is used.
  294. await contents.loadFile(path.join(__dirname, 'fixtures', 'pages', 'fetch.html'));
  295. try {
  296. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  297. const requestHeaders = details.requestHeaders;
  298. requestHeaders.Accept = '*/*;test/header';
  299. callback({ requestHeaders: requestHeaders });
  300. });
  301. const { data } = await ajax('no-cors://fake-host/redirect');
  302. expect(data).to.equal('header-received');
  303. } finally {
  304. protocol.unregisterProtocol('no-cors');
  305. }
  306. });
  307. it('can change request origin', async () => {
  308. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  309. const requestHeaders = details.requestHeaders;
  310. requestHeaders.Origin = 'http://new-origin';
  311. callback({ requestHeaders: requestHeaders });
  312. });
  313. const { data } = await ajax(defaultURL);
  314. expect(data).to.equal('/new/origin');
  315. });
  316. it('can capture CORS requests', async () => {
  317. let called = false;
  318. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  319. called = true;
  320. callback({ requestHeaders: details.requestHeaders });
  321. });
  322. await ajax('cors://host');
  323. expect(called).to.be.true();
  324. });
  325. it('resets the whole headers', async () => {
  326. const requestHeaders = {
  327. Test: 'header'
  328. };
  329. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  330. callback({ requestHeaders: requestHeaders });
  331. });
  332. ses.webRequest.onSendHeaders((details) => {
  333. expect(details.requestHeaders).to.deep.equal(requestHeaders);
  334. });
  335. await ajax(defaultURL);
  336. });
  337. it('leaves headers unchanged when no requestHeaders in callback', async () => {
  338. let originalRequestHeaders: Record<string, string>;
  339. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  340. originalRequestHeaders = details.requestHeaders;
  341. callback({});
  342. });
  343. ses.webRequest.onSendHeaders((details) => {
  344. expect(details.requestHeaders).to.deep.equal(originalRequestHeaders);
  345. });
  346. await ajax(defaultURL);
  347. });
  348. it('works with file:// protocol', async () => {
  349. const requestHeaders = {
  350. Test: 'header'
  351. };
  352. let onSendHeadersCalled = false;
  353. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  354. callback({ requestHeaders: requestHeaders });
  355. });
  356. ses.webRequest.onSendHeaders((details) => {
  357. expect(details.requestHeaders).to.deep.equal(requestHeaders);
  358. onSendHeadersCalled = true;
  359. });
  360. await ajax(url.format({
  361. pathname: path.join(fixturesPath, 'blank.html').replace(/\\/g, '/'),
  362. protocol: 'file',
  363. slashes: true
  364. }));
  365. expect(onSendHeadersCalled).to.be.true();
  366. });
  367. });
  368. describe('webRequest.onSendHeaders', () => {
  369. afterEach(() => {
  370. ses.webRequest.onSendHeaders(null);
  371. });
  372. it('receives details object', async () => {
  373. ses.webRequest.onSendHeaders((details) => {
  374. expect(details.requestHeaders).to.be.an('object');
  375. });
  376. const { data } = await ajax(defaultURL);
  377. expect(data).to.equal('/');
  378. });
  379. });
  380. describe('webRequest.onHeadersReceived', () => {
  381. afterEach(() => {
  382. ses.webRequest.onHeadersReceived(null);
  383. });
  384. it('receives details object', async () => {
  385. ses.webRequest.onHeadersReceived((details, callback) => {
  386. expect(details.statusLine).to.equal('HTTP/1.1 200 OK');
  387. expect(details.statusCode).to.equal(200);
  388. expect(details.responseHeaders!.Custom).to.deep.equal(['Header']);
  389. callback({});
  390. });
  391. const { data } = await ajax(defaultURL);
  392. expect(data).to.equal('/');
  393. });
  394. it('can change the response header', async () => {
  395. ses.webRequest.onHeadersReceived((details, callback) => {
  396. const responseHeaders = details.responseHeaders!;
  397. responseHeaders.Custom = ['Changed'] as any;
  398. callback({ responseHeaders: responseHeaders });
  399. });
  400. const { headers } = await ajax(defaultURL);
  401. expect(headers).to.to.have.property('custom', 'Changed');
  402. });
  403. it('can change response origin', async () => {
  404. ses.webRequest.onHeadersReceived((details, callback) => {
  405. const responseHeaders = details.responseHeaders!;
  406. responseHeaders['access-control-allow-origin'] = ['http://new-origin'] as any;
  407. callback({ responseHeaders: responseHeaders });
  408. });
  409. const { headers } = await ajax(defaultURL);
  410. expect(headers).to.to.have.property('access-control-allow-origin', 'http://new-origin');
  411. });
  412. it('can change headers of CORS responses', async () => {
  413. ses.webRequest.onHeadersReceived((details, callback) => {
  414. const responseHeaders = details.responseHeaders!;
  415. responseHeaders.Custom = ['Changed'] as any;
  416. callback({ responseHeaders: responseHeaders });
  417. });
  418. const { headers } = await ajax('cors://host');
  419. expect(headers).to.to.have.property('custom', 'Changed');
  420. });
  421. it('does not change header by default', async () => {
  422. ses.webRequest.onHeadersReceived((details, callback) => {
  423. callback({});
  424. });
  425. const { data, headers } = await ajax(defaultURL);
  426. expect(headers).to.to.have.property('custom', 'Header');
  427. expect(data).to.equal('/');
  428. });
  429. it('does not change content-disposition header by default', async () => {
  430. ses.webRequest.onHeadersReceived((details, callback) => {
  431. expect(details.responseHeaders!['content-disposition']).to.deep.equal([' attachment; filename="aa中aa.txt"']);
  432. callback({});
  433. });
  434. const { data, headers } = await ajax(defaultURL + 'contentDisposition');
  435. expect(headers).to.to.have.property('content-disposition', 'attachment; filename=aa%E4%B8%ADaa.txt');
  436. expect(data).to.equal('/contentDisposition');
  437. });
  438. it('follows server redirect', async () => {
  439. ses.webRequest.onHeadersReceived((details, callback) => {
  440. const responseHeaders = details.responseHeaders;
  441. callback({ responseHeaders: responseHeaders });
  442. });
  443. const { headers } = await ajax(defaultURL + 'serverRedirect');
  444. expect(headers).to.to.have.property('custom', 'Header');
  445. });
  446. it('can change the header status', async () => {
  447. ses.webRequest.onHeadersReceived((details, callback) => {
  448. const responseHeaders = details.responseHeaders;
  449. callback({
  450. responseHeaders: responseHeaders,
  451. statusLine: 'HTTP/1.1 404 Not Found'
  452. });
  453. });
  454. const { headers } = await ajax(defaultURL);
  455. expect(headers).to.to.have.property('custom', 'Header');
  456. });
  457. });
  458. describe('webRequest.onResponseStarted', () => {
  459. afterEach(() => {
  460. ses.webRequest.onResponseStarted(null);
  461. });
  462. it('receives details object', async () => {
  463. ses.webRequest.onResponseStarted((details) => {
  464. expect(details.fromCache).to.be.a('boolean');
  465. expect(details.statusLine).to.equal('HTTP/1.1 200 OK');
  466. expect(details.statusCode).to.equal(200);
  467. expect(details.responseHeaders!.Custom).to.deep.equal(['Header']);
  468. });
  469. const { data, headers } = await ajax(defaultURL);
  470. expect(headers).to.to.have.property('custom', 'Header');
  471. expect(data).to.equal('/');
  472. });
  473. });
  474. describe('webRequest.onBeforeRedirect', () => {
  475. afterEach(() => {
  476. ses.webRequest.onBeforeRedirect(null);
  477. ses.webRequest.onBeforeRequest(null);
  478. });
  479. it('receives details object', async () => {
  480. const redirectURL = defaultURL + 'redirect';
  481. ses.webRequest.onBeforeRequest((details, callback) => {
  482. if (details.url === defaultURL) {
  483. callback({ redirectURL: redirectURL });
  484. } else {
  485. callback({});
  486. }
  487. });
  488. ses.webRequest.onBeforeRedirect((details) => {
  489. expect(details.fromCache).to.be.a('boolean');
  490. expect(details.statusLine).to.equal('HTTP/1.1 307 Internal Redirect');
  491. expect(details.statusCode).to.equal(307);
  492. expect(details.redirectURL).to.equal(redirectURL);
  493. });
  494. const { data } = await ajax(defaultURL);
  495. expect(data).to.equal('/redirect');
  496. });
  497. });
  498. describe('webRequest.onCompleted', () => {
  499. afterEach(() => {
  500. ses.webRequest.onCompleted(null);
  501. });
  502. it('receives details object', async () => {
  503. ses.webRequest.onCompleted((details) => {
  504. expect(details.fromCache).to.be.a('boolean');
  505. expect(details.statusLine).to.equal('HTTP/1.1 200 OK');
  506. expect(details.statusCode).to.equal(200);
  507. });
  508. const { data } = await ajax(defaultURL);
  509. expect(data).to.equal('/');
  510. });
  511. });
  512. describe('webRequest.onErrorOccurred', () => {
  513. afterEach(() => {
  514. ses.webRequest.onErrorOccurred(null);
  515. ses.webRequest.onBeforeRequest(null);
  516. });
  517. it('receives details object', async () => {
  518. ses.webRequest.onBeforeRequest((details, callback) => {
  519. callback({ cancel: true });
  520. });
  521. ses.webRequest.onErrorOccurred((details) => {
  522. expect(details.error).to.equal('net::ERR_BLOCKED_BY_CLIENT');
  523. });
  524. await expect(ajax(defaultURL)).to.eventually.be.rejected();
  525. });
  526. });
  527. describe('WebSocket connections', () => {
  528. it('can be proxyed', async () => {
  529. // Setup server.
  530. const reqHeaders : { [key: string] : any } = {};
  531. const server = http.createServer((req, res) => {
  532. reqHeaders[req.url!] = req.headers;
  533. res.setHeader('foo1', 'bar1');
  534. res.end('ok');
  535. });
  536. const wss = new WebSocket.Server({ noServer: true });
  537. wss.on('connection', function connection (ws) {
  538. ws.on('message', function incoming (message) {
  539. if (message === 'foo') {
  540. ws.send('bar');
  541. }
  542. });
  543. });
  544. server.on('upgrade', function upgrade (request, socket, head) {
  545. const pathname = require('url').parse(request.url).pathname;
  546. if (pathname === '/websocket') {
  547. reqHeaders[request.url!] = request.headers;
  548. wss.handleUpgrade(request, socket as Socket, head, function done (ws) {
  549. wss.emit('connection', ws, request);
  550. });
  551. }
  552. });
  553. // Start server.
  554. const { port } = await listen(server);
  555. // Use a separate session for testing.
  556. const ses = session.fromPartition('WebRequestWebSocket');
  557. // Setup listeners.
  558. const receivedHeaders : { [key: string] : any } = {};
  559. ses.webRequest.onBeforeSendHeaders((details, callback) => {
  560. details.requestHeaders.foo = 'bar';
  561. callback({ requestHeaders: details.requestHeaders });
  562. });
  563. ses.webRequest.onHeadersReceived((details, callback) => {
  564. const pathname = require('url').parse(details.url).pathname;
  565. receivedHeaders[pathname] = details.responseHeaders;
  566. callback({ cancel: false });
  567. });
  568. ses.webRequest.onResponseStarted((details) => {
  569. if (details.url.startsWith('ws://')) {
  570. expect(details.responseHeaders!.Connection[0]).be.equal('Upgrade');
  571. } else if (details.url.startsWith('http')) {
  572. expect(details.responseHeaders!.foo1[0]).be.equal('bar1');
  573. }
  574. });
  575. ses.webRequest.onSendHeaders((details) => {
  576. if (details.url.startsWith('ws://')) {
  577. expect(details.requestHeaders.foo).be.equal('bar');
  578. expect(details.requestHeaders.Upgrade).be.equal('websocket');
  579. } else if (details.url.startsWith('http')) {
  580. expect(details.requestHeaders.foo).be.equal('bar');
  581. }
  582. });
  583. ses.webRequest.onCompleted((details) => {
  584. if (details.url.startsWith('ws://')) {
  585. expect(details.error).be.equal('net::ERR_WS_UPGRADE');
  586. } else if (details.url.startsWith('http')) {
  587. expect(details.error).be.equal('net::OK');
  588. }
  589. });
  590. const contents = (webContents as typeof ElectronInternal.WebContents).create({
  591. session: ses,
  592. nodeIntegration: true,
  593. webSecurity: false,
  594. contextIsolation: false
  595. });
  596. // Cleanup.
  597. after(() => {
  598. contents.destroy();
  599. server.close();
  600. ses.webRequest.onBeforeRequest(null);
  601. ses.webRequest.onBeforeSendHeaders(null);
  602. ses.webRequest.onHeadersReceived(null);
  603. ses.webRequest.onResponseStarted(null);
  604. ses.webRequest.onSendHeaders(null);
  605. ses.webRequest.onCompleted(null);
  606. });
  607. contents.loadFile(path.join(fixturesPath, 'api', 'webrequest.html'), { query: { port: `${port}` } });
  608. await once(ipcMain, 'websocket-success');
  609. expect(receivedHeaders['/websocket'].Upgrade[0]).to.equal('websocket');
  610. expect(receivedHeaders['/'].foo1[0]).to.equal('bar1');
  611. expect(reqHeaders['/websocket'].foo).to.equal('bar');
  612. expect(reqHeaders['/'].foo).to.equal('bar');
  613. });
  614. });
  615. });