Remove broadcast-channel dependency from security plugin (#132427)

* Remove broadcast-channel dependency from security plugin

* cleanup

* Update x-pack/plugins/security/public/session/session_timeout.ts

Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>

Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>
This commit is contained in:
Larry Gregory 2022-05-19 13:48:42 -04:00 committed by GitHub
parent ccb25d048c
commit 0dfa6374ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 118 additions and 75 deletions

View file

@ -229,7 +229,6 @@
"base64-js": "^1.3.1",
"bitmap-sdf": "^1.0.3",
"brace": "0.11.1",
"broadcast-channel": "4.10.0",
"canvg": "^3.0.9",
"chalk": "^4.1.0",
"cheerio": "^1.0.0-rc.10",

View file

@ -18,6 +18,8 @@ export * from './redux_helpers';
export * from './router_helpers';
export * from './stub_broadcast_channel';
export * from './stub_browser_storage';
export * from './stub_web_worker';

View file

@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
const channelCache: BroadcastChannel[] = [];
class StubBroadcastChannel implements BroadcastChannel {
constructor(public readonly name: string) {
channelCache.push(this);
}
onmessage = jest.fn();
onmessageerror = jest.fn();
close = jest.fn();
postMessage = jest.fn().mockImplementation((data: any) => {
channelCache.forEach((channel) => {
if (channel === this) return; // don't postMessage to ourselves
if (channel.onmessage) {
channel.onmessage(new MessageEvent(this.name, { data }));
}
});
});
addEventListener<K extends keyof BroadcastChannelEventMap>(
type: K,
listener: (this: BroadcastChannel, ev: BroadcastChannelEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): void;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
): void;
addEventListener(type: any, listener: any, options?: any): void {
throw new Error('Method not implemented.');
}
removeEventListener<K extends keyof BroadcastChannelEventMap>(
type: K,
listener: (this: BroadcastChannel, ev: BroadcastChannelEventMap[K]) => any,
options?: boolean | EventListenerOptions
): void;
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions
): void;
removeEventListener(type: any, listener: any, options?: any): void {
throw new Error('Method not implemented.');
}
dispatchEvent(event: Event): boolean {
throw new Error('Method not implemented.');
}
}
/**
* Returns all BroadcastChannel instances.
* @returns BroadcastChannel[]
*/
function getBroadcastChannelInstances() {
return [...channelCache];
}
/**
* Removes all BroadcastChannel instances.
*/
function clearBroadcastChannelInstances() {
channelCache.splice(0, channelCache.length);
}
/**
* Stubs the global window.BroadcastChannel for use in jest tests.
*/
function stubBroadcastChannel() {
if (!window.BroadcastChannel) {
window.BroadcastChannel = StubBroadcastChannel;
}
}
export { stubBroadcastChannel, getBroadcastChannelInstances, clearBroadcastChannelInstances };

View file

@ -114,7 +114,6 @@
{
"groupName": "platform security modules",
"matchPackageNames": [
"broadcast-channel",
"node-forge",
"@types/node-forge",
"require-in-the-middle",

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { enforceOptions } from 'broadcast-channel';
import { Observable } from 'rxjs';
import type { CoreSetup } from '@kbn/core/public';
@ -14,19 +13,15 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { FeaturesPluginStart } from '@kbn/features-plugin/public';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
import { managementPluginMock } from '@kbn/management-plugin/public/mocks';
import { stubBroadcastChannel } from '@kbn/test-jest-helpers';
import { ManagementService } from './management';
import type { PluginStartDependencies } from './plugin';
import { SecurityPlugin } from './plugin';
describe('Security Plugin', () => {
beforeAll(() => {
enforceOptions({ type: 'simulate' });
});
afterAll(() => {
enforceOptions(null);
});
stubBroadcastChannel();
describe('Security Plugin', () => {
describe('#setup', () => {
it('should be able to setup if optional plugins are not available', () => {
const plugin = new SecurityPlugin(coreMock.createPluginInitializerContext());

View file

@ -5,10 +5,14 @@
* 2.0.
*/
import type { BroadcastChannel } from 'broadcast-channel';
import type { ToastInputFields } from '@kbn/core/public';
import { coreMock } from '@kbn/core/public/mocks';
import {
clearBroadcastChannelInstances,
getBroadcastChannelInstances,
stubBroadcastChannel,
} from '@kbn/test-jest-helpers';
stubBroadcastChannel();
import {
SESSION_CHECK_MS,
@ -19,11 +23,8 @@ import {
} from '../../common/constants';
import type { SessionInfo } from '../../common/types';
import { createSessionExpiredMock } from './session_expired.mock';
import type { SessionState } from './session_timeout';
import { SessionTimeout, startTimer } from './session_timeout';
jest.mock('broadcast-channel');
jest.useFakeTimers();
jest.spyOn(window, 'addEventListener');
@ -56,6 +57,7 @@ describe('SessionTimeout', () => {
afterEach(async () => {
jest.clearAllMocks();
jest.clearAllTimers();
clearBroadcastChannelInstances();
});
test(`does not initialize when starting an anonymous path`, async () => {
@ -242,14 +244,17 @@ describe('SessionTimeout', () => {
jest.advanceTimersByTime(30 * 1000);
const [broadcastChannelMock] = jest.requireMock('broadcast-channel').BroadcastChannel.mock
.instances as [BroadcastChannel<SessionState>];
const [broadcastChannelMock] = getBroadcastChannelInstances();
broadcastChannelMock.onmessage!({
lastExtensionTime: Date.now(),
expiresInMs: 60 * 1000,
canBeExtended: true,
});
broadcastChannelMock.onmessage!(
new MessageEvent('name', {
data: {
lastExtensionTime: Date.now(),
expiresInMs: 60 * 1000,
canBeExtended: true,
},
})
);
jest.advanceTimersByTime(30 * 1000);

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import type { BroadcastChannel as BroadcastChannelType } from 'broadcast-channel';
import type { Subscription } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { skip, tap, throttleTime } from 'rxjs/operators';
@ -34,7 +33,7 @@ export interface SessionState extends Pick<SessionInfo, 'expiresInMs' | 'canBeEx
}
export class SessionTimeout {
private channel?: BroadcastChannelType<SessionState>;
private channel?: BroadcastChannel;
private isVisible = document.visibilityState !== 'hidden';
private isFetchingSessionInfo = false;
@ -77,11 +76,8 @@ export class SessionTimeout {
// Subscribe to a broadcast channel for session timeout messages.
// This allows us to synchronize the UX across tabs and avoid repetitive API calls.
try {
const { BroadcastChannel } = await import('broadcast-channel');
this.channel = new BroadcastChannel(`${this.tenant}/session_timeout`, {
webWorkerSupport: false,
});
this.channel.onmessage = this.handleChannelMessage;
this.channel = new BroadcastChannel(`${this.tenant}/session_timeout`);
this.channel.onmessage = (event) => this.handleChannelMessage(event);
} catch (error) {
// eslint-disable-next-line no-console
console.warn(
@ -108,8 +104,14 @@ export class SessionTimeout {
/**
* Event handler that receives session information from other browser tabs.
*/
private handleChannelMessage = (message: SessionState) => {
this.sessionState$.next(message);
private handleChannelMessage = (messageEvent: MessageEvent<any>) => {
if (this.isSessionState(messageEvent.data)) {
this.sessionState$.next(messageEvent.data);
}
};
private isSessionState = (data: unknown): data is SessionState => {
return typeof data === 'object' && Object.hasOwn(data ?? {}, 'canBeExtended');
};
/**

View file

@ -9428,20 +9428,6 @@ brfs@^2.0.0, brfs@^2.0.2:
static-module "^3.0.2"
through2 "^2.0.0"
broadcast-channel@4.10.0:
version "4.10.0"
resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198"
integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ==
dependencies:
"@babel/runtime" "^7.16.0"
detect-node "^2.1.0"
microseconds "0.2.0"
nano-time "1.0.0"
oblivious-set "1.0.0"
p-queue "6.6.2"
rimraf "3.0.2"
unload "2.3.1"
broadcast-channel@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.4.1.tgz#65b63068d0a5216026a19905c9b2d5e9adf0928a"
@ -12520,7 +12506,7 @@ detect-node-es@^1.1.0:
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
detect-node@2.1.0, detect-node@^2.0.4, detect-node@^2.1.0:
detect-node@^2.0.4:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
@ -13921,7 +13907,7 @@ eventemitter2@^6.4.3:
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.3.tgz#35c563619b13f3681e7eb05cbdaf50f56ba58820"
integrity sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==
eventemitter3@^4.0.0, eventemitter3@^4.0.4:
eventemitter3@^4.0.0:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
@ -21304,11 +21290,6 @@ objectorarray@^1.0.4:
resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.4.tgz#d69b2f0ff7dc2701903d308bb85882f4ddb49483"
integrity sha512-91k8bjcldstRz1bG6zJo8lWD7c6QXcB4nTDUqiEvIL1xAsLoZlOOZZG+nd6YPz+V7zY1580J4Xxh1vZtyv4i/w==
oblivious-set@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566"
integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==
oboe@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.4.tgz#20c88cdb0c15371bb04119257d4fdd34b0aa49f6"
@ -21654,14 +21635,6 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
p-queue@6.6.2:
version "6.6.2"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
dependencies:
eventemitter3 "^4.0.4"
p-timeout "^3.2.0"
p-retry@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328"
@ -21684,13 +21657,6 @@ p-timeout@^2.0.1:
dependencies:
p-finally "^1.0.0"
p-timeout@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
dependencies:
p-finally "^1.0.0"
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@ -28557,14 +28523,6 @@ unload@2.2.0:
"@babel/runtime" "^7.6.2"
detect-node "^2.0.4"
unload@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/unload/-/unload-2.3.1.tgz#9d16862d372a5ce5cb630ad1309c2fd6e35dacfe"
integrity sha512-MUZEiDqvAN9AIDRbbBnVYVvfcR6DrjCqeU2YQMmliFZl9uaBUjTkhuDQkBiyAy8ad5bx1TXVbqZ3gg7namsWjA==
dependencies:
"@babel/runtime" "^7.6.2"
detect-node "2.1.0"
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"