diff --git a/apps/desktop/forge.config.ts b/apps/desktop/forge.config.ts index 8e6825d..c02705c 100644 --- a/apps/desktop/forge.config.ts +++ b/apps/desktop/forge.config.ts @@ -3,6 +3,7 @@ import { MakerSquirrel } from '@electron-forge/maker-squirrel' import { MakerZIP } from '@electron-forge/maker-zip' import { MakerDeb } from '@electron-forge/maker-deb' import { MakerRpm } from '@electron-forge/maker-rpm' +import { MakerDMG } from '@electron-forge/maker-dmg' import { VitePlugin } from '@electron-forge/plugin-vite' import { FusesPlugin } from '@electron-forge/plugin-fuses' import { FuseV1Options, FuseVersion } from '@electron/fuses' @@ -26,6 +27,7 @@ const config: ForgeConfig = { new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({}), + new MakerDMG({}), ], plugins: [ new VitePlugin({ @@ -34,7 +36,7 @@ const config: ForgeConfig = { build: [ { // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`. - entry: 'src/main/index.ts', + entry: 'src/main.ts', config: 'vite.main.config.ts', }, { @@ -48,8 +50,8 @@ const config: ForgeConfig = { config: 'vite.renderer.menu.config.ts', }, { - name: 'picture_window', - config: 'vite.renderer.picture.config.ts', + name: 'intercom_window', + config: 'vite.renderer.intercom.config.ts', }, ], }), diff --git a/apps/desktop/forge.env.d.ts b/apps/desktop/forge.env.d.ts index 2d607c3..4e81463 100644 --- a/apps/desktop/forge.env.d.ts +++ b/apps/desktop/forge.env.d.ts @@ -7,8 +7,8 @@ declare global { const MENU_WINDOW_VITE_DEV_SERVER_URL: string const MENU_WINDOW_VITE_NAME: string - const PICTURE_WINDOW_VITE_DEV_SERVER_URL: string - const PICTURE_WINDOW_VITE_NAME: string + const INTERCOM_WINDOW_VITE_DEV_SERVER_URL: string + const INTERCOM_WINDOW_VITE_NAME: string namespace NodeJS { interface Process { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 713f6e4..9d8e2ab 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -2,7 +2,7 @@ "name": "desktop", "private": true, "version": "0.0.0", - "main": ".vite/build/index.js", + "main": ".vite/build/main.js", "scripts": { "dev": "electron-forge start", "package": "electron-forge package", @@ -14,11 +14,13 @@ }, "dependencies": { "electron-squirrel-startup": "^1.0.0", - "lucide": "^0.378.0" + "lucide": "^0.378.0", + "ws": "^8.17.0" }, "devDependencies": { "@electron-forge/cli": "^7.4.0", "@electron-forge/maker-deb": "^7.4.0", + "@electron-forge/maker-dmg": "^7.4.0", "@electron-forge/maker-rpm": "^7.4.0", "@electron-forge/maker-squirrel": "^7.4.0", "@electron-forge/maker-zip": "^7.4.0", diff --git a/apps/desktop/src/assets/svg/phone-off.svg b/apps/desktop/src/assets/svg/phone-off.svg new file mode 100644 index 0000000..31d89da --- /dev/null +++ b/apps/desktop/src/assets/svg/phone-off.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/assets/svg/phone.svg b/apps/desktop/src/assets/svg/phone.svg new file mode 100644 index 0000000..ed0fea2 --- /dev/null +++ b/apps/desktop/src/assets/svg/phone.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/components/menuButton.ts b/apps/desktop/src/components/menuButton.ts index 7ba4d1f..c543e70 100644 --- a/apps/desktop/src/components/menuButton.ts +++ b/apps/desktop/src/components/menuButton.ts @@ -5,7 +5,7 @@ import { customElement } from 'lit/decorators.js' export class MenuButton extends LitElement { protected render() { return html` - ` diff --git a/apps/desktop/src/features/control.ts b/apps/desktop/src/features/control.ts index 684fdf7..c38ef53 100644 --- a/apps/desktop/src/features/control.ts +++ b/apps/desktop/src/features/control.ts @@ -12,12 +12,29 @@ export class Control extends LitElement { @property({ type: Array }) readonly members?: IMember[] + @property({ type: Object }) + readonly initialConstraints?: MediaStreamConstraints + @state() private _constraints: MediaStreamConstraints = { video: false, audio: false, } + connectedCallback(): void { + super.connectedCallback() + + if (this.initialConstraints) { + if (this.initialConstraints.video) { + this._turnViewerVideo() + } + + if (this.initialConstraints.audio) { + this._turnViewerAudio() + } + } + } + private async _updateStream() { if (!this.viewer) return if (!this.viewer.stream) return @@ -185,7 +202,6 @@ export class Control extends LitElement { static styles = css` :host { - width: 100%; display: flex; align-items: center; justify-content: center; diff --git a/apps/desktop/src/intercom/incomingCall.ts b/apps/desktop/src/intercom/incomingCall.ts new file mode 100644 index 0000000..0457c13 --- /dev/null +++ b/apps/desktop/src/intercom/incomingCall.ts @@ -0,0 +1,93 @@ +import { LitElement, css, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import { unsafeHTML } from 'lit-html/directives/unsafe-html.js' + +// SVG +import PhoneSVG from '../assets/svg/phone.svg?raw' +import PhoneOffSVG from '../assets/svg/phone-off.svg?raw' + +@customElement('bell-incoming-call') +export class IncomingCall extends LitElement { + private _acceptIncomingCall() { + console.log('accept call') + + this.dispatchEvent(new CustomEvent('call:accept')) + } + + private _rejectIncomingCall() { + console.log('reject call') + + this.dispatchEvent(new CustomEvent('call:reject')) + } + + render() { + return html` +
+ +
+ Donatello +

is calling you

+
+ +
+ + ${unsafeHTML(PhoneOffSVG)} + + + ${unsafeHTML(PhoneSVG)} + +
+ ` + } + + static styles = css` + :host { + position: absolute; + top: 0; + left: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding: 18px 0 8px 0; + box-sizing: border-box; + width: 100%; + height: 100%; + } + + .overlay { + width: 100%; + height: 100%; + background: #ffffff57; + backdrop-filter: blur(2px); + position: absolute; + top: 0; + left: 0; + } + + .info { + z-index: 1; + text-align: center; + + span { + font-size: 26px; + font-weight: 700; + } + + p { + font-size: 13px; + font-weight: 400; + margin: 0; + } + } + + .actions { + display: flex; + gap: 10px; + + bell-button { + stroke: #00b607 !important; // #EF2727 + } + } + ` +} diff --git a/apps/desktop/src/picture/index.css b/apps/desktop/src/intercom/index.css similarity index 100% rename from apps/desktop/src/picture/index.css rename to apps/desktop/src/intercom/index.css diff --git a/apps/desktop/src/picture/index.html b/apps/desktop/src/intercom/index.html similarity index 100% rename from apps/desktop/src/picture/index.html rename to apps/desktop/src/intercom/index.html diff --git a/apps/desktop/src/intercom/main.ts b/apps/desktop/src/intercom/main.ts new file mode 100644 index 0000000..f55f052 --- /dev/null +++ b/apps/desktop/src/intercom/main.ts @@ -0,0 +1,79 @@ +import { BrowserWindow, screen, ipcMain } from 'electron' +import path from 'path' +import type { WebSocket } from 'ws' + +// TODO: нельзя что бы это окно было открыто дважды !!! +export function createWindow( + ws: WebSocket, + id: string, + type: 'outgoing' | 'incoming', + remoteSDP?: RTCSessionDescription +) { + const windowWidth = 400 + + const window = new BrowserWindow({ + width: windowWidth, + height: 225, + frame: false, + // transparent: true, + minimizable: false, + maximizable: false, + alwaysOnTop: true, + skipTaskbar: true, + + // vibrancy: 'window', + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + }, + }) + + const display = screen.getPrimaryDisplay() + const left = display.bounds.width - windowWidth - 20 + window.setPosition(left, 0) + + window.setAspectRatio(16 / 9, { + width: 400, + height: 225, + }) + + window.setVisibleOnAllWorkspaces(true, { + visibleOnFullScreen: true, + }) + + if (INTERCOM_WINDOW_VITE_DEV_SERVER_URL) { + window.loadURL(INTERCOM_WINDOW_VITE_DEV_SERVER_URL) + } else { + window.loadFile( + path.join( + __dirname, + `../renderer/${INTERCOM_WINDOW_VITE_NAME}/index.html` + ) + ) + } + + window.webContents.on('did-finish-load', () => { + if (remoteSDP) { + window.webContents.send('remote-sdp', remoteSDP) + } + window.webContents.send('type', type) + }) + + // TODO: нужно получить оффер и передать его в ws вместе с id + ipcMain.on('local-sdp', (_, sdp) => { + console.log(sdp.type) + const eventData = { + eventName: sdp.type === 'offer' ? 'offer' : 'answer', + data: { + to: id, + sdp: sdp, + }, + } + ws.send(JSON.stringify(eventData)) + + // dialog.showErrorBox('hello', 'sdsds') + }) + + // window.setAlwaysOnTop(true, 'pop-up-menu') + + return window +} diff --git a/apps/desktop/src/picture/member.ts b/apps/desktop/src/intercom/member.ts similarity index 100% rename from apps/desktop/src/picture/member.ts rename to apps/desktop/src/intercom/member.ts diff --git a/apps/desktop/src/intercom/outgoingCall.ts b/apps/desktop/src/intercom/outgoingCall.ts new file mode 100644 index 0000000..d751efc --- /dev/null +++ b/apps/desktop/src/intercom/outgoingCall.ts @@ -0,0 +1,51 @@ +import { LitElement, css, html } from 'lit' +import { customElement } from 'lit/decorators.js' + +@customElement('bell-outgoing-call') +export class OutgoingCall extends LitElement { + render() { + return html` +
+ +
+

Connecting ...

+
+ ` + } + + static styles = css` + :host { + position: absolute; + top: 0; + left: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + box-sizing: border-box; + width: 100%; + height: 100%; + } + + .overlay { + width: 100%; + height: 100%; + background: #ffffff57; + backdrop-filter: blur(2px); + position: absolute; + top: 0; + left: 0; + } + + .info { + z-index: 1; + text-align: center; + + p { + font-size: 13px; + font-weight: 400; + margin: 0; + } + } + ` +} diff --git a/apps/desktop/src/intercom/renderer.ts b/apps/desktop/src/intercom/renderer.ts new file mode 100644 index 0000000..b1ae91d --- /dev/null +++ b/apps/desktop/src/intercom/renderer.ts @@ -0,0 +1,168 @@ +import { LitElement, html, css, nothing } from 'lit' +import { customElement, state } from 'lit/decorators.js' + +// Shoelace +import '@shoelace-style/shoelace/dist/themes/light.css' +import '@shoelace-style/shoelace/dist/themes/dark.css' +import '@shoelace-style/shoelace/dist/components/icon-button/icon-button.js' +import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js' +// TODO: temporary solution, as I don't want to drag all existing svg into the build +setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.0/cdn/') + +import './incomingCall' +import './outgoingCall' + +import '../components/button' + +import './member' +import '../features/control' +import '../features/inviteCode' +import '../features/responseCode' +import { IMember, IViewer } from '../types' +import { PeerController } from '../../../playground/src/components/webrtc/peer.controller' + +@customElement('bell-main') +export class Main extends LitElement { + @state() + private _viewer: IViewer = { + id: crypto.randomUUID(), + name: 'Me', + stream: new MediaStream(), + } + + @state() + private _members: IMember[] = [] + + // FIXME: надо понять, как отправить из main в renderer, до события callback + @state() + private _callType: 'outgoing' | 'incoming' + + // @state() + // private _currentCallStatus: 'connection' | 'connected' + + async connectedCallback(): Promise { + super.connectedCallback() + + const peerController = new PeerController(this, this._viewer.stream) + + window.electron.onType(async (value) => { + this._callType = value + + if (this._callType === 'outgoing') { + const offer = await peerController.peerConnection.createOffer() + peerController.peerConnection.setLocalDescription(offer) + + window.electron.sendLocalSDP(offer) + } + + this.requestUpdate() + }) + + window.electron.onRemoteSDP(async (value) => { + // TODO: возможно будут проблемы, так как он должен выполняться до создания answer + peerController.peerConnection.setRemoteDescription(value) + + if (this._callType === 'incoming') { + const answer = await peerController.peerConnection.createAnswer() + peerController.peerConnection.setLocalDescription(answer) + + window.electron.sendLocalSDP(answer) + } + + this.requestUpdate() + }) + + const member: IMember = { + id: crypto.randomUUID(), + name: `Someone else #1`, + peerController: peerController, + } + + const updatedMembers = [...this._members] + updatedMembers.push(member) + this._members = updatedMembers + } + + private _handleAcceptCall() { + // создать на этой стороне клиента peerconnection + // принять входящий оффер, и положить его в remoteDescription + // ------------ + // сгенерировать ответ и отправить его по ws + } + + private _handleRejectCall() {} + + render() { + const callStatus1 = + this._callType === 'incoming' + ? html`` + : html`` + + const callStatus = this._callType ? callStatus1 : nothing + + return html` + + + ${callStatus} + + window.close()}> + + + + + + + + ` + } + + static styles = css` + :host { + -webkit-app-region: drag; + + position: relative; + display: block; + } + + /* :host(:hover) > .bell-control { + display: flex; + } */ + + .close { + -webkit-app-region: no-drag; + + position: absolute; + top: 8px; + left: 8px; + } + + .bell-control { + -webkit-app-region: no-drag; + + position: absolute; + bottom: 8px; + /* left: 50%; */ + /* transform: translateX(-50%); */ + left: 8px; + } + ` +} diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts new file mode 100644 index 0000000..1b18886 --- /dev/null +++ b/apps/desktop/src/main.ts @@ -0,0 +1,41 @@ +import path from 'node:path' +import { Tray, app, systemPreferences } from 'electron' +import { createWindow as createWindowMenu } from './menu/main' + +import WebSocket from 'ws' +import { addContact, initializeDataFile } from './localDataManager' +import { initializeWebSocket } from './webSocketManager' + +function createTray(ws: WebSocket) { + const iconPath = app.isPackaged + ? path.join(process.resourcesPath, 'LogoTemplate@2x.png') + : path.resolve('./src/assets/LogoTemplate@2x.png') + + const tray = new Tray(iconPath) + + const trayMenuWindow = createWindowMenu(ws) + + function toggleMenuWindow() { + trayMenuWindow.isVisible() ? trayMenuWindow.hide() : trayMenuWindow.show() + } + + tray.addListener('click', (event, bounds) => { + trayMenuWindow.setPosition(bounds.x, bounds.y) + toggleMenuWindow() + }) + + systemPreferences.askForMediaAccess('microphone') + systemPreferences.askForMediaAccess('camera') +} + +app.on('window-all-closed', (e) => e.preventDefault()) + +app.on('ready', async () => { + await addContact('4ae45530-39a6-4417-8f58-50edc0a127fd', { name: 'Matvei' }) + await addContact('b45c46f5-1805-4dc9-aecd-d024e259e56a', { name: 'Polina' }) + const localData = await initializeDataFile() + + const ws = initializeWebSocket(localData) + + createTray(ws) +}) diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts deleted file mode 100644 index ed88aa4..0000000 --- a/apps/desktop/src/main/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { app, systemPreferences } from 'electron' -// import { initSystemTray } from './tray' -import { initSystemTray } from './tray2' - -systemPreferences.askForMediaAccess('microphone') -systemPreferences.askForMediaAccess('camera') - -app.on('window-all-closed', (e) => e.preventDefault()) - -app.on('ready', initSystemTray) diff --git a/apps/desktop/src/main/tray.ts b/apps/desktop/src/main/tray.ts deleted file mode 100644 index aa5cc55..0000000 --- a/apps/desktop/src/main/tray.ts +++ /dev/null @@ -1,67 +0,0 @@ -import path from 'node:path' -import { Menu, Tray } from 'electron' - -import { createWindow } from '../picture/main' - -export function initSystemTray() { - /** - * To make sure your icon isn't grainy on retina monitors, be sure your @2x image is 144dpi. - * If you are bundling your application (e.g., with webpack for development), be sure that the file names are not being mangled or hashed. The filename needs to end in Template, and the @2x image needs to have the same filename as the standard image, or MacOS will not magically invert your image's colors or use the high density image. - * 16x16 (72dpi) and 32x32@2x (144dpi) work well for most icons. - */ - const tray = new Tray(path.resolve('./src/assets/LogoTemplate@2x.png')) - - const contacts = [ - { - label: 'Raphael', - icon: path.resolve('./src/assets/Raphael.png'), - }, - { - label: 'Leonardo', - icon: path.resolve('./src/assets/UserPlusTemplate@2x.png'), - }, - { - label: 'Donatello', - icon: path.resolve('./src/assets/UserPlusTemplate@2x.png'), - }, - { - label: 'Michelangelo', - icon: path.resolve('./src/assets/UserPlusTemplate@2x.png'), - }, - { - label: 'Splinter', - icon: path.resolve('./src/assets/UserPlusTemplate@2x.png'), - }, - ] - // const contacts1 = [ - // { - // label: 'Contacts', - // type: 'submenu', - // submenu: contacts, - // }, - // ] - - const contacts2 = [{ label: 'Contacts', enabled: false }, ...contacts] - - const menu = Menu.buildFromTemplate([ - ...contacts2, - { type: 'separator' }, - { - label: 'Add New Contact', - icon: path.resolve('./src/assets/UserPlusTemplate@2x.png'), - }, - { type: 'separator' }, - { - label: 'Manual call', - icon: path.resolve('./src/assets/ArrowUpDownTemplate@2x.png'), - }, - { type: 'separator' }, - { label: 'Open Test Window', click: createWindow }, - { type: 'separator' }, - { role: 'about' }, - { type: 'separator' }, - { role: 'quit' }, - ]) - - tray.setContextMenu(menu) -} diff --git a/apps/desktop/src/main/tray2.ts b/apps/desktop/src/main/tray2.ts deleted file mode 100644 index 70a98ec..0000000 --- a/apps/desktop/src/main/tray2.ts +++ /dev/null @@ -1,22 +0,0 @@ -import path from 'node:path' -import { app, Tray } from 'electron' -import { createWindow } from '../menu/main' - -export function initSystemTray() { - const iconPath = app.isPackaged - ? path.join(process.resourcesPath, 'LogoTemplate@2x.png') - : path.resolve('./src/assets/LogoTemplate@2x.png') - - const tray = new Tray(iconPath) - - const trayMenuWindow = createWindow() - - function toggleMenuWindow() { - trayMenuWindow.isVisible() ? trayMenuWindow.hide() : trayMenuWindow.show() - } - - tray.addListener('click', (event, bounds) => { - trayMenuWindow.setPosition(bounds.x, bounds.y) - toggleMenuWindow() - }) -} diff --git a/apps/desktop/src/menu/main.ts b/apps/desktop/src/menu/main.ts index 6499485..6eb32da 100644 --- a/apps/desktop/src/menu/main.ts +++ b/apps/desktop/src/menu/main.ts @@ -1,13 +1,16 @@ -import { BrowserWindow, ipcMain } from 'electron' +// TODO: расширять окно, при добавлении контактов +import { BrowserWindow, app, ipcMain } from 'electron' import path from 'node:path' -import { createWindow as createPictureWindow } from '../picture/main' +import { createWindow as createWindowIntercom } from '../intercom/main' +import type { WebSocket } from 'ws' +import { loadData } from '../localDataManager' -export function createWindow() { +export function createWindow(ws: WebSocket) { const window = new BrowserWindow({ show: false, width: 300, - height: 166, + height: 300, frame: false, resizable: false, transparent: true, @@ -45,8 +48,35 @@ export function createWindow() { ) } - ipcMain.on('open-picture', () => { - createPictureWindow() + window.webContents.on('did-finish-load', async () => { + const localData = await loadData() + console.log(localData) + // window.webContents.openDevTools() + window.webContents.send('local-data', localData) + }) + + // ipcMain.on('call-to-contact', (_, contactID) => { + // // TODO: открывать picture in picture, получать оттуда offer -> передавать его в сокеты + + // const eventData = { + // eventName: 'offer', + // data: { + // to: contactID, + // sdp: '', + // }, + // } + // console.log(contactID) + // ws.send(JSON.stringify(eventData)) + + // // TODO: обработать ответ, что контакт не в сети + // }) + + ipcMain.on('open-intercom', (_, id) => { + createWindowIntercom(ws, id, 'outgoing') + }) + + ipcMain.on('quit', () => { + app.quit() }) return window diff --git a/apps/desktop/src/menu/renderer.ts b/apps/desktop/src/menu/renderer.ts index e3e540f..705ee32 100644 --- a/apps/desktop/src/menu/renderer.ts +++ b/apps/desktop/src/menu/renderer.ts @@ -1,5 +1,6 @@ import { LitElement, css, html } from 'lit' -import { customElement } from 'lit/decorators.js' +import { customElement, state } from 'lit/decorators.js' +import { repeat } from 'lit/directives/repeat.js' import '../components/menuButton' @@ -20,9 +21,43 @@ registerIconLibrary('lucide', { @customElement('bell-tray-menu') export class TrayMenu extends LitElement { + @state() + myID?: string + + @state() + contacts?: any[] + + connectedCallback(): void { + super.connectedCallback() + + window.electron.onLocalData((value) => { + this.myID = value.id + this.contacts = value.contacts.entries().reduce((acc, [id, value]) => { + acc.push({ id, ...value }) + + return acc + }, []) + }) + } + + private _callToContact(id: string): void { + window.electron.openIntercom(id) + } + + private _quit() { + window.electron.quit() + } + protected render() { return html` - window.electronAPI.openPicture()}> + +

ID

+ ${this.myID} +
+ +
+ + this._callToContact(this.myID)}> + ${repeat( + this.contacts, + (contact) => contact.id, + (contact) => html` + this._callToContact(contact.id)} + >${contact.name} + ` + )} + +
+ Settings About
- Quit + Quit ` } @@ -50,6 +97,29 @@ export class TrayMenu extends LitElement { background: #ffffff; } + .bio { + &::part(base) { + display: flex; + align-items: flex-start; + flex-direction: column; + gap: 0; + } + + p { + width: 100%; + text-align: left; + font-size: 11px; + font-weight: 500; + color: #9d9d9d; + margin: 0; + } + + span { + font-size: 13px; + font-weight: 500; + } + } + hr { height: 1px; background: #eeeeee; diff --git a/apps/desktop/src/picture/main.ts b/apps/desktop/src/picture/main.ts deleted file mode 100644 index 93e4def..0000000 --- a/apps/desktop/src/picture/main.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { BrowserWindow, screen } from 'electron' -import path from 'path' - -export function createWindow() { - const windowWidth = 400 - - const window = new BrowserWindow({ - width: windowWidth, - height: 225, - frame: false, - // transparent: true, - minimizable: false, - maximizable: false, - alwaysOnTop: true, - skipTaskbar: true, - - // vibrancy: 'window', - }) - - const display = screen.getPrimaryDisplay() - const left = display.bounds.width - windowWidth - 20 - window.setPosition(left, 0) - - window.setAspectRatio(16 / 9, { - width: 400, - height: 225, - }) - - window.setVisibleOnAllWorkspaces(true, { - visibleOnFullScreen: true, - }) - - if (PICTURE_WINDOW_VITE_DEV_SERVER_URL) { - window.loadURL(PICTURE_WINDOW_VITE_DEV_SERVER_URL) - } else { - window.loadFile( - path.join(__dirname, `../renderer/${PICTURE_WINDOW_VITE_NAME}/index.html`) - ) - } - - // window.setAlwaysOnTop(true, 'pop-up-menu') - - return window -} diff --git a/apps/desktop/src/picture/renderer.ts b/apps/desktop/src/picture/renderer.ts deleted file mode 100644 index 34988ad..0000000 --- a/apps/desktop/src/picture/renderer.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { LitElement, html, css } from 'lit' -import { customElement, state } from 'lit/decorators.js' - -// Shoelace -import '@shoelace-style/shoelace/dist/themes/light.css' -import '@shoelace-style/shoelace/dist/themes/dark.css' -import '@shoelace-style/shoelace/dist/components/icon-button/icon-button.js' -import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js' -// TODO: temporary solution, as I don't want to drag all existing svg into the build -setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.0/cdn/') - -import './member' -import '../features/control' -import '../features/inviteCode' -import '../features/responseCode' -import { IMember, IViewer } from '../types' - -@customElement('bell-main') -export class Main extends LitElement { - @state() - private _viewer: IViewer = { - id: crypto.randomUUID(), - name: 'Me', - stream: new MediaStream(), - } - - @state() - private _members: IMember[] = [] - - @state() - private _inviteOrResponse?: 'invite' | 'response' - - private _handleUpdateMembers(e) { - this._members = e.detail - } - - // TODO: решить, как выводить invite code или response code - render() { - return this._inviteOrResponse === undefined - ? html` - - - ` - : this._inviteOrResponse === 'invite' - ? html` - - ` - : html` - - ` - // : html` - // - - // - // - // - // - // - // - - // - // ` - } - - static styles = css` - :host { - /* -webkit-app-region: drag; */ - - position: relative; - display: block; - } - - /* :host(:hover) > .bell-control { - display: flex; - } */ - - .close { - -webkit-app-region: no-drag; - - position: absolute; - top: 8px; - left: 8px; - } - - .bell-control { - -webkit-app-region: no-drag; - - position: absolute; - bottom: 8px; - left: 50%; - transform: translateX(-50%); - - background: none; - transition: background 2s; - } - ` -} diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index 89e3ae5..d56a1ee 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -1,5 +1,17 @@ import { contextBridge, ipcRenderer } from 'electron' -contextBridge.exposeInMainWorld('electronAPI', { - openPicture: () => ipcRenderer.send('open-picture'), +contextBridge.exposeInMainWorld('electron', { + openIntercom: (id) => ipcRenderer.send('open-intercom', id), + callToContact: (id) => ipcRenderer.send('call-to-contact', id), + sendLocalSDP: (value: RTCSessionDescription) => + ipcRenderer.send('local-sdp', value), + + quit: () => ipcRenderer.send('quit'), + + onLocalData: (callback) => + ipcRenderer.on('local-data', (_event, value) => callback(value)), + onType: (callback) => + ipcRenderer.on('type', (_event, value) => callback(value)), + onRemoteSDP: (callback: (value: RTCSessionDescription) => void) => + ipcRenderer.on('remote-sdp', (_event, value) => callback(value)), }) diff --git a/apps/desktop/src/vite-env.d.ts b/apps/desktop/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/apps/desktop/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/desktop/src/webSocketManager.ts b/apps/desktop/src/webSocketManager.ts new file mode 100644 index 0000000..7fac5d0 --- /dev/null +++ b/apps/desktop/src/webSocketManager.ts @@ -0,0 +1,51 @@ +import WebSocket from 'ws' +import { createWindow as createWindowIntercom } from './intercom/main' // FIXME: + +export function initializeWebSocket(localData) { + let ws: WebSocket + + function connect() { + ws = new WebSocket('ws://192.168.1.101:3000', { + perMessageDeflate: false, + }) + + ws.on('open', () => { + const eventData = { + eventName: 'login', + data: { + id: localData.id, + }, + } + ws.send(JSON.stringify(eventData)) + }) + + ws.on('message', (e) => { + console.log('Received message') + const message = JSON.parse(e) + + // пользователю пришел звонок, необходимо открыть окно, и спросить, принимает он звонок или нет + // положить полученный sdp в remote, сгенерить ответ, отправить на сервер + if ('eventName' in message && message.eventName === 'receiveOffer') { + createWindowIntercom( + ws, + message.data.from, + 'incoming', + message.data.sdp + ) + } + }) + + ws.on('close', () => { + console.log('Connection lost, attempting to reconnect...') + setTimeout(connect, 5000) // Пауза перед повторной попыткой переподключения + }) + + ws.on('error', (err) => { + console.error('WebSocket error:', err.message) + ws.close() + }) + } + + connect() + return ws +} diff --git a/apps/desktop/vite.renderer.picture.config.ts b/apps/desktop/vite.renderer.intercom.config.ts similarity index 96% rename from apps/desktop/vite.renderer.picture.config.ts rename to apps/desktop/vite.renderer.intercom.config.ts index 2e0ac56..1748e0b 100644 --- a/apps/desktop/vite.renderer.picture.config.ts +++ b/apps/desktop/vite.renderer.intercom.config.ts @@ -11,7 +11,7 @@ export default defineConfig((env) => { const name = forgeConfigSelf.name ?? '' return { - root: 'src/picture', + root: 'src/intercom', mode, base: './', build: { diff --git a/package-lock.json b/package-lock.json index f8adb25..dd46dbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,11 +30,13 @@ "version": "0.0.0", "dependencies": { "electron-squirrel-startup": "^1.0.0", - "lucide": "^0.378.0" + "lucide": "^0.378.0", + "ws": "^8.17.0" }, "devDependencies": { "@electron-forge/cli": "^7.4.0", "@electron-forge/maker-deb": "^7.4.0", + "@electron-forge/maker-dmg": "^7.4.0", "@electron-forge/maker-rpm": "^7.4.0", "@electron-forge/maker-squirrel": "^7.4.0", "@electron-forge/maker-zip": "^7.4.0", @@ -46,6 +48,26 @@ "ts-node": "^10.9.2" } }, + "apps/desktop/node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "apps/playground": { "version": "0.0.0", "devDependencies": { @@ -286,6 +308,37 @@ "electron-installer-debian": "^3.2.0" } }, + "node_modules/@electron-forge/maker-dmg": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-dmg/-/maker-dmg-7.4.0.tgz", + "integrity": "sha512-xRCMNtnpvQNwrDYvwbVFegnErnIMpHGZANrjwushlH9+Fsu60DFvf5s3AVkgsYdQTqlY7wYRG1mziYZmRlPAIw==", + "dev": true, + "dependencies": { + "@electron-forge/maker-base": "7.4.0", + "@electron-forge/shared-types": "7.4.0", + "fs-extra": "^10.0.0" + }, + "engines": { + "node": ">= 16.4.0" + }, + "optionalDependencies": { + "electron-installer-dmg": "^4.0.0" + } + }, + "node_modules/@electron-forge/maker-dmg/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@electron-forge/maker-rpm": { "version": "7.4.0", "dev": true, @@ -1843,6 +1896,35 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/appdmg": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/appdmg/-/appdmg-0.6.6.tgz", + "integrity": "sha512-GRmFKlCG+PWbcYF4LUNonTYmy0GjguDy6Jh9WP8mpd0T6j80XIJyXBiWlD0U+MLNhqV9Nhx49Gl9GpVToulpLg==", + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "async": "^1.4.2", + "ds-store": "^0.1.5", + "execa": "^1.0.0", + "fs-temp": "^1.0.0", + "fs-xattr": "^0.3.0", + "image-size": "^0.7.4", + "is-my-json-valid": "^2.20.0", + "minimist": "^1.1.3", + "parse-color": "^1.0.0", + "path-exists": "^4.0.0", + "repeat-string": "^1.5.4" + }, + "bin": { + "appdmg": "bin/appdmg.js" + }, + "engines": { + "node": ">=8.5" + } + }, "node_modules/aproba": { "version": "2.0.0", "dev": true, @@ -1906,6 +1988,13 @@ "@types/glob": "^7.1.1" } }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true, + "optional": true + }, "node_modules/asynckit": { "version": "0.4.0", "dev": true, @@ -1942,6 +2031,16 @@ "dev": true, "license": "MIT" }, + "node_modules/base32-encode": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/base32-encode/-/base32-encode-1.2.0.tgz", + "integrity": "sha512-cHFU8XeRyx0GgmoWi5qHMCVRiqU6J3MHWxVgun7jggCBUpVzm1Ir7M9dYr2whjSNc3tFeXfQ/oZjQu/4u55h9A==", + "dev": true, + "optional": true, + "dependencies": { + "to-data-view": "^1.1.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "dev": true, @@ -2033,6 +2132,16 @@ "license": "MIT", "optional": true }, + "node_modules/bplist-creator": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.8.tgz", + "integrity": "sha512-Za9JKzD6fjLC16oX2wsXfc+qBEhJBJB1YPInoAQpMLhDuj5aVOv1baGeIQSq1Fr3OCqzvsoQcSBSwGId/Ja2PA==", + "dev": true, + "optional": true, + "dependencies": { + "stream-buffers": "~2.2.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -2774,6 +2883,18 @@ "node": ">=12" } }, + "node_modules/ds-store": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ds-store/-/ds-store-0.1.6.tgz", + "integrity": "sha512-kY21M6Lz+76OS3bnCzjdsJSF7LBpLYGCVfavW8TgQD2XkcqIZ86W0y9qUDZu6fp7SIZzqosMDW2zi7zVFfv4hw==", + "dev": true, + "optional": true, + "dependencies": { + "bplist-creator": "~0.0.3", + "macos-alias": "~0.2.5", + "tn1150": "^0.1.0" + } + }, "node_modules/duplexer": { "version": "0.1.2", "dev": true, @@ -2971,6 +3092,26 @@ "node": ">=10" } }, + "node_modules/electron-installer-dmg": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/electron-installer-dmg/-/electron-installer-dmg-4.0.0.tgz", + "integrity": "sha512-g3W6XnyUa7QGrAF7ViewHdt6bXV2KYU1Pm1CY3pZpp+H6mOjCHHAhf/iZAxtaX1ERCb+SQHz7xSsAHuNH9I8ZQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^4.3.2", + "minimist": "^1.1.1" + }, + "bin": { + "electron-installer-dmg": "bin/electron-installer-dmg.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "optionalDependencies": { + "appdmg": "^0.6.4" + } + }, "node_modules/electron-installer-redhat": { "version": "3.4.0", "dev": true, @@ -3203,6 +3344,13 @@ "dev": true, "license": "MIT" }, + "node_modules/encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", + "dev": true, + "optional": true + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -3970,6 +4118,16 @@ "node": ">=12" } }, + "node_modules/fmix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fmix/-/fmix-0.1.0.tgz", + "integrity": "sha512-Y6hyofImk9JdzU8k5INtTXX1cu8LDlePWDFU5sftm9H+zKCr5SGrVjdhkvsim646cw5zD0nADj8oHyXMZmCZ9w==", + "dev": true, + "optional": true, + "dependencies": { + "imul": "^1.0.0" + } + }, "node_modules/follow-redirects": { "version": "1.15.6", "dev": true, @@ -4049,6 +4207,30 @@ "node": ">= 8" } }, + "node_modules/fs-temp": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/fs-temp/-/fs-temp-1.2.1.tgz", + "integrity": "sha512-okTwLB7/Qsq82G6iN5zZJFsOfZtx2/pqrA7Hk/9fvy+c+eJS9CvgGXT2uNxwnI14BDY9L/jQPkaBgSvlKfSW9w==", + "dev": true, + "optional": true, + "dependencies": { + "random-path": "^0.1.0" + } + }, + "node_modules/fs-xattr": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fs-xattr/-/fs-xattr-0.3.1.tgz", + "integrity": "sha512-UVqkrEW0GfDabw4C3HOrFlxKfx0eeigfRne69FxSBdHIP8Qt5Sq6Pu3RM9KmMlkygtC4pPKkj5CiPO5USnj2GA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "dev": true, @@ -4124,6 +4306,26 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "optional": true, + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ==", + "dev": true, + "optional": true, + "dependencies": { + "is-property": "^1.0.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "dev": true, @@ -4570,6 +4772,19 @@ "node": ">= 4" } }, + "node_modules/image-size": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.7.5.tgz", + "integrity": "sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "dev": true, @@ -4585,6 +4800,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/imul": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/imul/-/imul-1.0.1.tgz", + "integrity": "sha512-WFAgfwPLAjU66EKt6vRdTlKj4nAgIDQzh29JonLa4Bqtl6D8JrIMvWjCnx7xEjVNmP3U0fM5o8ZObk7d0f62bA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "dev": true, @@ -4729,6 +4954,27 @@ "dev": true, "license": "MIT" }, + "node_modules/is-my-ip-valid": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz", + "integrity": "sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg==", + "dev": true, + "optional": true + }, + "node_modules/is-my-json-valid": { + "version": "2.20.6", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz", + "integrity": "sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==", + "dev": true, + "optional": true, + "dependencies": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^5.0.0", + "xtend": "^4.0.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "dev": true, @@ -4750,6 +4996,13 @@ "dev": true, "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "dev": true, + "optional": true + }, "node_modules/is-stream": { "version": "1.1.0", "dev": true, @@ -4903,6 +5156,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/junk": { "version": "3.1.0", "dev": true, @@ -5256,6 +5519,20 @@ "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.378.0.tgz", "integrity": "sha512-bwWXuZf2jZbCI0Y+MWyv5bedWIxYKtgAEzC2Yl87Nrt/KcG9qTwAQxVFcZ6IwBioML06QUQsG+qRjyQvYHdbBQ==" }, + "node_modules/macos-alias": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/macos-alias/-/macos-alias-0.2.11.tgz", + "integrity": "sha512-zIUs3+qpml+w3wiRuADutd7XIO8UABqksot10Utl/tji4UxZzLG4fWDC+yJZoO8/Ehg5RqsvSRE/6TS5AEOeWw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "nan": "^2.4.0" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5541,6 +5818,25 @@ "dev": true, "license": "MIT" }, + "node_modules/murmur-32": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/murmur-32/-/murmur-32-0.2.0.tgz", + "integrity": "sha512-ZkcWZudylwF+ir3Ld1n7gL6bI2mQAzXvSobPwVtu8aYi2sbXeipeSkdcanRLzIofLcM5F53lGaKm2dk7orBi7Q==", + "dev": true, + "optional": true, + "dependencies": { + "encode-utf8": "^1.0.3", + "fmix": "^0.1.0", + "imul": "^1.0.0" + } + }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "dev": true, + "optional": true + }, "node_modules/nanoid": { "version": "3.3.7", "dev": true, @@ -6017,6 +6313,23 @@ "node": ">=0.10.0" } }, + "node_modules/parse-color": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz", + "integrity": "sha512-fuDHYgFHJGbpGMgw9skY/bj3HL/Jrn4l/5rSspy00DoT4RyLnDcRvPxdZ+r6OFwIsgAuhDh4I09tAId4mI12bw==", + "dev": true, + "optional": true, + "dependencies": { + "color-convert": "~0.5.0" + } + }, + "node_modules/parse-color/node_modules/color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha512-RwBeO/B/vZR3dfKL1ye/vx8MHZ40ugzpyfeVG5GsiuGnrlMWe2o8wxBbLCpw9CsxV+wHuzYlCiWnybrIA0ling==", + "dev": true, + "optional": true + }, "node_modules/parse-json": { "version": "2.2.0", "dev": true, @@ -6418,6 +6731,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/random-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/random-path/-/random-path-0.1.2.tgz", + "integrity": "sha512-4jY0yoEaQ5v9StCl5kZbNIQlg1QheIDBrdkDn53EynpPb9FgO6//p3X/tgMnrC45XN6QZCzU1Xz/+pSSsJBpRw==", + "dev": true, + "optional": true, + "dependencies": { + "base32-encode": "^0.1.0 || ^1.0.0", + "murmur-32": "^0.1.0 || ^0.2.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -6583,6 +6907,16 @@ "node": ">= 10.13.0" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "dev": true, @@ -7176,6 +7510,16 @@ "node": ">= 0.8" } }, + "node_modules/stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "dev": true, @@ -7417,6 +7761,26 @@ "tmp": "^0.2.0" } }, + "node_modules/tn1150": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tn1150/-/tn1150-0.1.0.tgz", + "integrity": "sha512-DbplOfQFkqG5IHcDyyrs/lkvSr3mPUVsFf/RbDppOshs22yTPnSJWEe6FkYd1txAwU/zcnR905ar2fi4kwF29w==", + "dev": true, + "optional": true, + "dependencies": { + "unorm": "^1.4.1" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/to-data-view": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-1.1.0.tgz", + "integrity": "sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ==", + "dev": true, + "optional": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -7640,6 +8004,16 @@ "node": ">= 10.0.0" } }, + "node_modules/unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -7864,6 +8238,16 @@ "node": ">=8.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/xterm": { "version": "4.19.0", "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.19.0.tgz",