From a764d1f5d455f36bf831942605d7ab133e7c3952 Mon Sep 17 00:00:00 2001 From: Jared Date: Fri, 26 May 2023 22:15:34 -0400 Subject: [PATCH 1/9] Trying to brick this --- angular.json | 3 ++ package-lock.json | 30 +++++++------------ .../src/lib/providers/config.provider.ts | 6 ++-- src/app/app.component.html | 2 +- src/app/app.component.ts | 23 +++++++++++--- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/angular.json b/angular.json index c3eebff..80f1249 100644 --- a/angular.json +++ b/angular.json @@ -157,5 +157,8 @@ "@angular-eslint/schematics:library": { "setParserOptionsProject": true } + }, + "cli": { + "analytics": false } } diff --git a/package-lock.json b/package-lock.json index 4d1f646..dba628e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21974,8 +21974,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-15.1.0.tgz", "integrity": "sha512-MoPeJv4a1wSoFj8fVA01hFb+QQke2t74CSVuc6o4EqkWI0tYMM1Wg19fPtTZnj4spkGA82j2mf/tazKGRe/nrw==", - "dev": true, - "requires": {} + "dev": true }, "@angular-eslint/bundled-angular-compiler": { "version": "15.1.0", @@ -24699,8 +24698,7 @@ "version": "15.0.4", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.0.4.tgz", "integrity": "sha512-+1riOTohRHhN2N8Y+usHFtNz+Rt6q/44puj9rwjlKwWIA+6qxAv3kQhVHivVaU3bCAB9B/3jAxSuZTNHk0wgTg==", - "dev": true, - "requires": {} + "dev": true }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -25726,15 +25724,13 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "8.2.0", @@ -25886,8 +25882,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} + "dev": true }, "ansi-colors": { "version": "4.1.3", @@ -28244,8 +28239,7 @@ "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -30824,8 +30818,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} + "dev": true }, "ieee754": { "version": "1.2.1", @@ -34509,8 +34502,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -36940,8 +36932,7 @@ "version": "0.39.1", "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.39.1.tgz", "integrity": "sha512-CCc9cZhZbKoNizVM+K3Uqgit/go8GacjpqTv1cpwG/n2P0gB9GMoWZbxrUULDE9Wz26Lh86CGf6QyIPUVV1lnQ==", - "dev": true, - "requires": {} + "dev": true }, "tslib": { "version": "2.4.1", @@ -37536,8 +37527,7 @@ "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "requires": {} + "dev": true }, "xml2js": { "version": "0.4.23", diff --git a/projects/ngx-multi-window/src/lib/providers/config.provider.ts b/projects/ngx-multi-window/src/lib/providers/config.provider.ts index 2f74ab6..5297ec6 100644 --- a/projects/ngx-multi-window/src/lib/providers/config.provider.ts +++ b/projects/ngx-multi-window/src/lib/providers/config.provider.ts @@ -7,8 +7,8 @@ export const NGXMW_CONFIG = new InjectionToken('ngxmw_config' export const defaultMultiWindowConfig: MultiWindowConfig = { keyPrefix: 'ngxmw_', heartbeat: 1000, - newWindowScan: 5000, + newWindowScan: 1000, messageTimeout: 10000, - windowTimeout: 15000, - windowSaveStrategy: WindowSaveStrategy.NONE, + windowTimeout: 2000, + windowSaveStrategy: WindowSaveStrategy.SAVE_WHEN_EMPTY, }; diff --git a/src/app/app.component.html b/src/app/app.component.html index f86b2ce..34151c7 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -94,7 +94,7 @@

Send Message

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7e906c8..2318f17 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,7 @@ import { Component, HostListener, OnInit } from '@angular/core'; import { MultiWindowService, Message, KnownAppWindow } from 'ngx-multi-window'; import { NameGeneratorService } from './providers/name-generator.service'; +import {delay} from "rxjs"; @Component({ selector: 'app-root', @@ -24,6 +25,11 @@ export class AppComponent implements OnInit { constructor(private multiWindowService: MultiWindowService, private nameGenerator: NameGeneratorService) { } + public pause(milliseconds) { + var dt = new Date(); + while ((new Date().getTime()) - dt.getTime() <= milliseconds) { /* Do nothing */ } + } + ngOnInit(): void { this.ownId = this.multiWindowService.id; this.ownName = this.multiWindowService.name; @@ -34,14 +40,23 @@ export class AppComponent implements OnInit { } this.newName = this.ownName; this.windows = this.multiWindowService.getKnownWindows(); - - this.multiWindowService.onMessage().subscribe((value: Message) => { - this.logs.unshift('Received a message from ' + value.senderId + ': ' + value.data); - }); + if (this.multiWindowService.getKnownWindows().length > 0) { + this.multiWindowService.onMessage().subscribe((value: Message) => { + if (value.senderId != this.ownId) { + this.logs.unshift('Received a message from ' + value.senderId + ': ' + value.data); + } + }); + } this.multiWindowService.onWindows().subscribe(knownWindows => this.windows = knownWindows); } + public sendTonsOfMessages(recipientId: string, message: string) { + for (let i = 0; i < 5000; i++) { + this.sendMessage(recipientId, message); + } + } + public sendMessage(recipientId: string, message: string) { if (recipientId === this.ownId) { // Catch sending messages to itself. Trying to do so throws an error from multiWindowService.sendMessage() From 2476b0f81ee439b9a28653885a134d5885b081f7 Mon Sep 17 00:00:00 2001 From: Jared Date: Thu, 1 Jun 2023 21:53:25 -0400 Subject: [PATCH 2/9] Start new project off of this old code and make use of BroadcastChannel API rather than using storage for passing messages... --- README.md | 5 +- .../src/lib/providers/multi-window.service.ts | 501 +++--------------- .../src/lib/providers/storage.service.ts | 168 ------ .../lib/types/window-save-strategy.enum.ts | 23 - .../src/lib/types/window.type.ts | 23 +- src/app/app.component.html | 3 - src/app/app.component.ts | 70 +-- 7 files changed, 95 insertions(+), 698 deletions(-) delete mode 100644 projects/ngx-multi-window/src/lib/providers/storage.service.ts delete mode 100644 projects/ngx-multi-window/src/lib/types/window-save-strategy.enum.ts diff --git a/README.md b/README.md index 6e627a5..83a397e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# ngx-multi-window [![npm version](https://img.shields.io/npm/v/ngx-multi-window.svg?style=flat)](https://www.npmjs.com/package/ngx-multi-window) [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) +# ngx-multi-window-communication [![npm version](https://img.shields.io/npm/v/ngx-multi-window.svg?style=flat)](https://www.npmjs.com/package/ngx-multi-window) [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) -Pull-based cross-window communication for multi-window angular applications +Cross-window communication for multi-window angular applications [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/b175dcd8585a42bdbdb9c1ee2a313b3b)](https://www.codacy.com/app/sebastian-fuss/ngx-multi-window?utm_source=github.com&utm_medium=referral&utm_content=Nolanus/ngx-multi-window&utm_campaign=Badge_Grade) ## Features diff --git a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts index 149ca2b..e408138 100644 --- a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts +++ b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts @@ -1,119 +1,57 @@ -import { Inject, Injectable, Optional } from '@angular/core'; +import {Inject, Injectable, OnDestroy, Optional} from '@angular/core'; import { Location } from '@angular/common'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { ignoreElements } from 'rxjs/operators'; -import { StorageService } from './storage.service'; import { defaultMultiWindowConfig, NGXMW_CONFIG } from './config.provider'; import { MultiWindowConfig } from '../types/multi-window.config'; -import { AppWindow, KnownAppWindow, WindowData } from '../types/window.type'; -import { Message, MessageTemplate, MessageType } from '../types/message.type'; +import { KnownAppWindow } from '../types/window.type'; +import { Message } from '../types/message.type'; import { WindowRef } from '../providers/window.provider'; -import { WindowSaveStrategy } from '../types/window-save-strategy.enum'; @Injectable({ providedIn: 'root', }) -export class MultiWindowService { +export class MultiWindowService implements OnDestroy { private config: MultiWindowConfig; - private myWindow: WindowData; + private myWindow: KnownAppWindow; - private heartbeatId = null; - private windowScanId = null; + private knownWindows: {[broadcastId: string]: {[windowId: string]: {}}} = {}; - private knownWindows: KnownAppWindow[] = []; + private knownBroadcasters: {[broadcastId: string]: BroadcastChannel} = {}; - /** - * A hash that keeps track of subjects for all send messages - */ - private messageTracker: { [key: string]: Subject } = {}; - - /** - * A copy of the outbox that is regularly written to the local storage - */ - private outboxCache: { [key: string]: Message } = {}; + private knownListeners: string[] = []; /** - * A subject to subscribe to in order to get notified about messages send to this window + * A subject to subscribe to in order to get notified about messages sent to this window */ - private messageSubject: Subject = new Subject(); + private messageSubjects: Subject[] = []; /** * A subject to subscribe to in order to get notified about all known windows */ - private windowSubject: Subject = new BehaviorSubject(this.knownWindows); + //private windowSubject: Subject = new BehaviorSubject(this.knownWindows); - private static generateId(): string { + private generateId(): string { return new Date().getTime().toString(36).substr(-4) + Math.random().toString(36).substr(2, 9); } - private tryMatchWindowKey(source: string): string { - const nameRegex = new RegExp( - // Escape any potential regex-specific chars in the keyPrefix which may be changed by the dev - this.config.keyPrefix.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + 'w_([a-z0-9]+)' - ); - const match = source.match(nameRegex); - if (match !== null) { - return match[1]; - } - - return null; - } - - private generatePayloadKey({messageId}: Message): string { - return this.config.keyPrefix + 'payload_' + messageId; - } - - private generateWindowKey(windowId: string): string { - return this.config.keyPrefix + 'w_' + windowId; - } - - private isWindowKey(key: string): boolean { - return key.indexOf(this.config.keyPrefix + 'w_') === 0; - } - - constructor(@Inject(NGXMW_CONFIG) customConfig: MultiWindowConfig, @Optional() private location: Location, - private storageService: StorageService, private windowRef: WindowRef) { + constructor(@Inject(NGXMW_CONFIG) customConfig: MultiWindowConfig, @Optional() private location: Location, private windowRef: WindowRef) { this.config = {...defaultMultiWindowConfig, ...customConfig}; - - let windowId: string; - - if (this.location) { - // Try to extract the new window name from the location path - windowId = this.tryMatchWindowKey(this.location.path(true)); - } - // Only check the name save strategy if no id has been extracted from the path already - if (!windowId && this.config.windowSaveStrategy !== WindowSaveStrategy.NONE) { - // There might be window data stored in the window.name property, try restoring it - const storedWindowData = this.windowRef.nativeWindow.name; - - if (this.config.windowSaveStrategy === WindowSaveStrategy.SAVE_BACKUP) { - // There should be a JSON string in the window.name, try parsing it and set the values - try { - const storedJsonData = JSON.parse(storedWindowData); - windowId = storedJsonData.ngxmw_id; - this.windowRef.nativeWindow.name = storedJsonData.backup; - } catch (ex) { // Swallow JSON parsing exceptions, as we can't handle them anyway - } - } else { - windowId = this.tryMatchWindowKey(storedWindowData); - if (this.config.windowSaveStrategy === WindowSaveStrategy.SAVE_WHEN_EMPTY) { - this.windowRef.nativeWindow.name = ''; - } - } - } - this.init(windowId); - this.start(); } - get name(): string { - return this.myWindow.name; + public ngOnDestroy() { + this.handleServiceDestroyed(); + this.closeAllListeners(); } - set name(value: string) { - this.myWindow.name = value; + public getKnownBroadcasters(): BroadcastChannel[] { + let knownBroadcasters: BroadcastChannel[] = []; + for (const bcId of Object.keys(this.knownBroadcasters)) { + knownBroadcasters.push(this.knownBroadcasters[bcId]); + } + return knownBroadcasters; } get id(): string { @@ -125,8 +63,64 @@ export class MultiWindowService { * After a message has been emitted by this observable it is marked as read, so the sending window * gets informed about successful delivery. */ - public onMessage(): Observable { - return this.messageSubject.asObservable(); + public getBroadcastChannel(broadcastChannelId: string): BroadcastChannel { + for (const bcId of Object.keys(this.knownBroadcasters)) { + const bc = this.knownBroadcasters[bcId]; + if (bcId == broadcastChannelId) + return bc; + } + const bc = new BroadcastChannel(broadcastChannelId); + this.knownBroadcasters[bc.name] = bc; + this.messageSubjects[bc.name] = new Subject(); + return bc; + } + + public listen(broadcastChannelId: string): MultiWindowService { + const bc = this.getBroadcastChannel(broadcastChannelId); + if (this.knownListeners[bc.name] == undefined) { + bc.onmessage = (message: any) => { + if (message.type == 'WINDOW_KILLED') { + // We want to handle the window killed here and make sure we remove it as a known window + delete this.knownWindows[bc.name][message.senderId]; + return; + } else + if (message.type == 'WINDOW_CREATED') { + this.knownWindows[bc.name][message.senderId] = {}; + return; + } + this.messageSubjects[bc.name].next(message as Message); + } + } + // When we register a new listener, we want to tell all the windows listening that we have a new known window to add to their list... + this.sendMessageToAll({ + type: 'WINDOW_CREATED', + senderId: this.id + }) + return this; + } + + public closeListener(broadcastChannelId: string) { + if (this.knownListeners[broadcastChannelId] != undefined) { + this.getBroadcastChannel(broadcastChannelId).close(); + delete this.knownListeners[broadcastChannelId]; + } + } + public closeAllListeners() { + for (const bcId of this.knownListeners) { + this.getBroadcastChannel(bcId).close(); + } + this.knownListeners = []; + } + private handleServiceDestroyed() { + this.sendMessageToAll({ + type: "WINDOW_KILLED", + senderId: this.id + }); + this.closeAllListeners(); + } + + public onMessage(broadcastChannelId: string): Observable { + return this.messageSubjects[broadcastChannelId].asObservable(); } /** @@ -142,9 +136,11 @@ export class MultiWindowService { * @see {@link MultiWindowConfig#newWindowScan} * @returns */ + /** / public onWindows(): Observable { return this.windowSubject.asObservable(); } + /* */ /** * Get the latest list of known windows. @@ -156,335 +152,6 @@ export class MultiWindowService { return this.knownWindows; } - /** - * Start so that the current instance of the angular app/service/tab participates in the cross window communication. - * It starts the interval-based checking for messages and updating the heartbeat. - * - * Note: There should be no need to call this method in production apps. It will automatically be called internally - * during service construction (see {@link MultiWindowService} constructor) - */ - public start(): void { - if (!this.heartbeatId) { - this.heartbeatId = setInterval(this.heartbeat, this.config.heartbeat); - } - if (!this.windowScanId) { - this.windowScanId = setInterval(this.scanForWindows, this.config.newWindowScan); - } - } - - /** - * Stop the current instance of the angular app/service/tab from participating in the cross window communication. - * It stops the interval-based checking for messages and updating the heartbeat. - * - * Note: There should be no need to call this method in production apps. - */ - public stop(): void { - if (this.heartbeatId) { - clearInterval(this.heartbeatId); - this.heartbeatId = null; - } - if (this.windowScanId) { - clearInterval(this.windowScanId); - this.windowScanId = null; - } - } - - /** - * Remove the current window representation from the localstorage. - * - * Note: Unless you {@link stop}ped the service the window representation will be - * recreated after {@link MultiWindowConfig#heartbeat} milliseconds - */ - public clear(): void { - this.storageService.removeLocalItem(this.generateWindowKey(this.myWindow.id)); - } - - /** - * Send a message to another window. - * - * @param recipientId The ID of the recipient window. - * @param event Custom identifier to distinguish certain events - * @param data Custom data to contain the data for the event - * @param payload Further data to be passed to the recipient window via a separate entry in the localstorage - * @returns An observable that emits the messageId once the message has been put into - * the current windows outbox. It completes on successful delivery and fails if the delivery times out. - */ - public sendMessage(recipientId: string, event: string, data: any, payload?: any): Observable { - const messageId = this.pushToOutbox({ - recipientId, - type: MessageType.MESSAGE, - event, - data, - payload, - }); - this.messageTracker[messageId] = new Subject(); - - return this.messageTracker[messageId].asObservable(); - } - - /** - * Create a new window and get informed once it has been created. - * - * Note: The actual window "creation" of redirection needs to be performed by - * the library user. This method only returns helpful information on how to - * do that - * - * The returned object contains three properties: - * - * windowId: An id generated to be assigned to the newly created window - * - * urlString: This string must be included in the url the new window loads. - * It does not matter whether it is in the path, query or hash part - * - * created: An observable that will complete after new window was opened and was able - * to receive a message. If the window does not start consuming messages within - * {@link MultiWindowConfig#messageTimeout}, this observable will fail, although - * the window might become present after that. The Observable will never emit any elements. - */ - public newWindow(): { windowId: string, urlString: string, created: Observable } { - if (this.location === null) { - // Reading information from the URL is only possible with the Location provider. If - // this window does not have one, another one will have none as well and thus would - // not be able to read its new windowId from the url path - throw new Error('No Location Provider present'); - } - const newWindowId = MultiWindowService.generateId(); - - const messageId = this.pushToOutbox({ - recipientId: newWindowId, - type: MessageType.PING, - event: undefined, - data: undefined, - payload: undefined, - }); - - this.messageTracker[messageId] = new Subject(); - - return { - windowId: newWindowId, - urlString: this.generateWindowKey(newWindowId), - created: this.messageTracker[messageId].pipe(ignoreElements()), - }; - } - - public saveWindow(): void { - if (this.config.windowSaveStrategy !== WindowSaveStrategy.NONE) { - const windowId = this.generateWindowKey(this.id); - if ((this.config.windowSaveStrategy === WindowSaveStrategy.SAVE_WHEN_EMPTY && !this.windowRef.nativeWindow.name) - || this.config.windowSaveStrategy === WindowSaveStrategy.SAVE_FORCE) { - this.windowRef.nativeWindow.name = windowId; - } else if (this.config.windowSaveStrategy === WindowSaveStrategy.SAVE_BACKUP) { - this.windowRef.nativeWindow.name = JSON.stringify({ngxmw_id: windowId, backup: this.windowRef.nativeWindow.name}); - } - } - } - - private init(windowId?: string): void { - const windowKey = windowId - ? this.generateWindowKey(windowId) - : this.storageService.getWindowName(); - let windowData: WindowData | null = null; - if (windowKey && this.isWindowKey(windowKey)) { - windowData = this.storageService.getLocalObject(windowKey); - } - - if (windowData !== null) { - // Restore window information from storage - this.myWindow = { - id: windowData.id, - name: windowData.name, - heartbeat: windowData.heartbeat, - }; - } else { - const myWindowId = windowId || MultiWindowService.generateId(); - this.myWindow = { - id: myWindowId, - name: 'AppWindow ' + myWindowId, - heartbeat: -1, - }; - } - - this.storageService.setWindowName(windowKey); - - // Scan for already existing windows - this.scanForWindows(); - - // Trigger heartbeat for the first time - this.heartbeat(); - } - - private pushToOutbox({recipientId, type, event, data, payload}: MessageTemplate, - messageId: string = MultiWindowService.generateId()): string { - if (recipientId === this.id) { - throw new Error('Cannot send messages to self'); - } - this.outboxCache[messageId] = { - messageId, - recipientId, - senderId: this.myWindow.id, - sendTime: new Date().getTime(), - type, - event, - data, - payload, - send: false, - }; - - return messageId; - } - - private heartbeat = () => { - const now = new Date().getTime(); - // Check whether there are new messages for the current window in the other window's outboxes - - // Store the ids of all messages we receive in this iteration - const receivedMessages: string[] = []; - - this.knownWindows.forEach(windowData => { - // Load the window from the localstorage - const appWindow = this.storageService.getLocalObject(this.generateWindowKey(windowData.id)); - - if (appWindow === null) { - // No window found, it possibly got closed/removed since our last scanForWindow - return; - } - - if (appWindow.id === this.myWindow.id) { - // Ignore messages from myself (not done using Array.filter to reduce array iterations) - // but check for proper last heartbeat time - if (this.myWindow.heartbeat !== -1 && this.myWindow.heartbeat !== appWindow.heartbeat) { - // The heartbeat value in the localstorage is a different one than the one we wrote into localstorage - // during our last heartbeat. There are probably two app windows - // using the same window id => change the current windows id - this.myWindow.id = MultiWindowService.generateId(); - // eslint-disable-next-line no-console - console.warn('Window ' + appWindow.id + ' detected that there is probably another instance with' + - ' this id, changed id to ' + this.myWindow.id); - this.storageService.setWindowName(this.generateWindowKey(this.myWindow.id)); - } - - return; - } - - if (now - appWindow.heartbeat > this.config.windowTimeout) { - // The window seems to be dead, remove the entry from the localstorage - this.storageService.removeLocalItem(this.generateWindowKey(appWindow.id)); - } - - // Update the windows name and heartbeat value in the list of known windows (that's what we iterate over) - windowData.name = appWindow.name; - windowData.heartbeat = appWindow.heartbeat; - - if (appWindow.messages && appWindow.messages.length > 0) { - // This other window has messages, so iterate over the messages the other window has - appWindow.messages.forEach(message => { - if (message.recipientId !== this.myWindow.id) { - // The message is not targeted to the current window - return; - } - - if (message.type === MessageType.MESSAGE_RECEIVED) { - // It's a message to inform the current window that a previously sent message from the - // current window has been processed by the recipient window - - // Trigger the observable to complete and then remove it - if (this.messageTracker[message.messageId]) { - this.messageTracker[message.messageId].complete(); - } - delete this.messageTracker[message.messageId]; - - // Remove the message from the outbox, as the transfer is complete - delete this.outboxCache[message.messageId]; - } else { - receivedMessages.push(message.messageId); - // Check whether we already processed that message. If that's the case we've got a 'message_received' - // confirmation in our own outbox. - - if (!(this.outboxCache[message.messageId] && - this.outboxCache[message.messageId].type === MessageType.MESSAGE_RECEIVED)) { - // We did not process that message - - // Create a new message for the message sender in the current window's - // outbox that the message has been processed (reuse the message id for that) - this.pushToOutbox({ - recipientId: message.senderId, - type: MessageType.MESSAGE_RECEIVED, - event: undefined, - }, message.messageId); - - // Process it locally, unless it's just a PING message - if (message.type !== MessageType.PING) { - if (message.payload === true) { - // The message has a separate payload - const payloadKey = this.generatePayloadKey(message); - message.payload = this.storageService.getLocalObject(payloadKey); - this.storageService.removeLocalItem(payloadKey); - } - this.messageSubject.next(message); - } - } - } - }); - } - }); - - // Iterate over the outbox to clean it up, process timeouts and payloads - Object.keys(this.outboxCache).forEach(messageId => { - const message = this.outboxCache[messageId]; - if (message.type === MessageType.MESSAGE_RECEIVED && !receivedMessages.some(msgId => msgId === messageId)) { - // It's a message confirmation and we did not receive the 'original' method for that confirmation - // => the sender has received our confirmation and removed the message from it's outbox, thus we can - // safely remove the message confirmation as well - delete this.outboxCache[messageId]; - } else if (message.type !== MessageType.MESSAGE_RECEIVED && now - message.sendTime > this.config.messageTimeout) { - // Delivering the message has failed, as the target window did not pick it up in time - // The type of message doesn't matter for that - delete this.outboxCache[messageId]; - if (this.messageTracker[messageId]) { - this.messageTracker[messageId].error('Timeout'); - } - delete this.messageTracker[messageId]; - } else if (message.type === MessageType.MESSAGE && message.send === false) { - if (message.payload !== undefined && message.payload !== true) { - // Message has a payload that has not been moved yet, so move that in a separate localstorage key - this.storageService.setLocalObject(this.generatePayloadKey(message), message.payload); - message.payload = true; - } - this.messageTracker[message.messageId].next(message.messageId); - // Set property to undefined, as we do not need to encode "send:true" in the localstorage json multiple times - message.send = undefined; - } - }); - - this.storageService.setLocalObject(this.generateWindowKey(this.myWindow.id), { - heartbeat: now, - id: this.myWindow.id, - name: this.myWindow.name, - messages: Object.keys(this.outboxCache).map(key => this.outboxCache[key]), - }); - - if (this.myWindow.heartbeat === -1) { - // This was the first heartbeat run for the local window, so rescan for known windows to get - // the current (new) window in the list - this.scanForWindows(); - } - - // Store the new heartbeat value in the local windowData copy - this.myWindow.heartbeat = now; - } - - private scanForWindows = () => { - this.knownWindows = this.storageService.getLocalObjects( - this.storageService.getLocalItemKeys().filter((key) => this.isWindowKey(key))) - .map(({id, name, heartbeat}: WindowData) => { - return { - id, - name, - heartbeat, - stalled: new Date().getTime() - heartbeat > this.config.heartbeat * 2, - self: this.myWindow.id === id, - }; - }); - this.windowSubject.next(this.knownWindows); - } + public sendMessage(broadcastChannelId: string, message: any) {} + public sendMessageToAll(message: any) {} } diff --git a/projects/ngx-multi-window/src/lib/providers/storage.service.ts b/projects/ngx-multi-window/src/lib/providers/storage.service.ts deleted file mode 100644 index 46b502d..0000000 --- a/projects/ngx-multi-window/src/lib/providers/storage.service.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Injectable, Optional, SkipSelf } from '@angular/core'; - -@Injectable({ - providedIn: 'root', -}) -export class StorageService { - - private window: Window; - private localStorage: Storage; - private sessionStorage: Storage; - - constructor() { - this.window = window; - this.localStorage = window.localStorage; - this.sessionStorage = window.sessionStorage; - } - - /* - Write methods - */ - public setLocalObject(key: string, obj: any): boolean { - return this.setObject(this.localStorage, key, obj); - } - - public setLocalItem(key: string, obj: string): void { - this.setItem(this.localStorage, key, obj); - } - - public setSessionObject(key: string, obj: any): boolean { - return this.setObject(this.sessionStorage, key, obj); - } - - public setSessionItem(key: string, obj: string): void { - this.setItem(this.sessionStorage, key, obj); - } - - private setObject(storage: Storage, key: string, obj: any): boolean { - let jsonString: string; - try { - jsonString = JSON.stringify(obj); - } catch (ex) { - return false; - } - this.setItem(storage, key, jsonString); - - return true; - } - - private setItem(storage: Storage, key: string, obj: string): void { - storage.setItem(key, obj); - } - - public setWindowName(value: string): void { - this.window.name = value; - } - - /* - Read methods - */ - - public getLocalObject(key: string): T | null { - return this.getObject(this.localStorage, key); - } - - public getLocalObjects(keys: string[]): (T | null)[] { - return this.getObjects(this.localStorage, keys); - } - - public getLocalItem(key: string): string | null { - return this.getItem(this.localStorage, key); - } - - public getSessionObject(key: string): T | null { - return this.getObject(this.sessionStorage, key); - } - - public getSessionObjects(keys: string[]): (T | null)[] { - return this.getObjects(this.sessionStorage, keys); - } - - public getSessionItem(key: string): string | null { - return this.getItem(this.sessionStorage, key); - } - - public getObjects(storage: Storage, keys: string[]): (T | null)[] { - return keys.map(key => this.getObject(storage, key)); - } - - private getObject(storage: Storage, key: string): T | null { - const jsonString = this.getItem(storage, key); - if (jsonString === null) { - return null; - } - try { - return JSON.parse(jsonString) as T; - } catch (ex) { - return null; - } - } - - private getItem(storage: Storage, key: string): string | null { - return storage.getItem(key) || null; - } - - public getWindowName(): string { - return this.window.name; - } - - /* - Remove methods - */ - - public removeLocalItem(key: string): void { - this.removeItem(this.localStorage, key); - } - - public removeSessionItem(key: string): void { - this.removeItem(this.sessionStorage, key); - } - - private removeItem(storage: Storage, key: string): void { - storage.removeItem(key); - } - - public clearLocalStorage(): void { - this.clearStorage(this.localStorage); - } - - public clearSessionStorage(): void { - this.clearStorage(this.sessionStorage); - } - - private clearStorage(storage: Storage): void { - storage.clear(); - } - - /* - Inspection methods - */ - - public getLocalItemKeys(): string[] { - return this.getStorageItemKeys(this.localStorage); - } - - public getSessionItemKeys(): string[] { - return this.getStorageItemKeys(this.sessionStorage); - } - - private getStorageItemKeys(storage: Storage): string[] { - const keys = []; - for (let i = 0; i < storage.length; i++) { - keys.push(storage.key(i)); - } - - return keys; - } -} - -/* singleton pattern taken from https://github.com/angular/angular/issues/13854 */ -export const StorageServiceProviderFactory = (parentDispatcher: StorageService) => { - return parentDispatcher || new StorageService(); -}; - -export const StorageServiceProvider = { - provide: StorageService, - deps: [[new Optional(), new SkipSelf(), StorageService]], - useFactory: StorageServiceProviderFactory, -}; diff --git a/projects/ngx-multi-window/src/lib/types/window-save-strategy.enum.ts b/projects/ngx-multi-window/src/lib/types/window-save-strategy.enum.ts deleted file mode 100644 index 0d44545..0000000 --- a/projects/ngx-multi-window/src/lib/types/window-save-strategy.enum.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * A representation of a strategy on how to save the window id in the native window.name property. - */ -export enum WindowSaveStrategy { - /** - * Default behaviour. Window data will not be saved. - */ - NONE, - /** - * Only save the window data when the storage (native window.name property) is empty/undefined, meaning nothing else is - * probably utilizing it - */ - SAVE_WHEN_EMPTY, - /** - * Save the window data without checking whether the storage might be used by another script - */ - SAVE_FORCE, - /** - * Save window data, but backup probable existing data in the storage and restore the original data after reading the window data. - * Restoring the original data might not happen "on time" for the script using it, which would require delaying it's execution. - */ - SAVE_BACKUP, -} diff --git a/projects/ngx-multi-window/src/lib/types/window.type.ts b/projects/ngx-multi-window/src/lib/types/window.type.ts index f13a772..91179f6 100644 --- a/projects/ngx-multi-window/src/lib/types/window.type.ts +++ b/projects/ngx-multi-window/src/lib/types/window.type.ts @@ -1,24 +1,3 @@ -import { Message } from './message.type'; - -export interface WindowData { - heartbeat: number; +export interface KnownAppWindow { id: string; - name: string; -} - -export interface KnownAppWindow extends WindowData { - stalled: boolean; - self: boolean; -} - -/** - * Represents a window with the current application how it is stored - * in the localstorage. It has an id, name, heartbeat represented in a timestamp - * and an array of messages that that window sent out. - * - * It's named "AppWindow" to avoid confusion with the type Window - * of the global variable "window". - */ -export interface AppWindow extends WindowData { - messages: Message[]; } diff --git a/src/app/app.component.html b/src/app/app.component.html index 34151c7..134135f 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -72,9 +72,6 @@
{{ window.name }}
Current window Other window, - Status: - Stalled - Active diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2318f17..ea68fd8 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,16 +12,10 @@ export class AppComponent implements OnInit { ownName: string; ownId: string; - windows: KnownAppWindow[] = []; logs: string[] = []; newName: string; - @HostListener('window:unload') - unloadHandler() { - this.multiWindowService.saveWindow(); - } - constructor(private multiWindowService: MultiWindowService, private nameGenerator: NameGeneratorService) { } @@ -32,72 +26,24 @@ export class AppComponent implements OnInit { ngOnInit(): void { this.ownId = this.multiWindowService.id; - this.ownName = this.multiWindowService.name; - if (this.ownName.indexOf(this.ownId) >= 0) { - // This window still has the automatic given name, so generate a fake one for demo reasons - // Generate a random name for the current window, just for fun - this.multiWindowService.name = this.ownName = this.nameGenerator.getRandomFakeName(); - } this.newName = this.ownName; - this.windows = this.multiWindowService.getKnownWindows(); - if (this.multiWindowService.getKnownWindows().length > 0) { - this.multiWindowService.onMessage().subscribe((value: Message) => { - if (value.senderId != this.ownId) { - this.logs.unshift('Received a message from ' + value.senderId + ': ' + value.data); - } - }); - } - - this.multiWindowService.onWindows().subscribe(knownWindows => this.windows = knownWindows); - } - - public sendTonsOfMessages(recipientId: string, message: string) { - for (let i = 0; i < 5000; i++) { - this.sendMessage(recipientId, message); - } + this.multiWindowService.onMessage('data').subscribe((value: Message) => { + if (value.senderId != this.ownId) { + this.logs.unshift('Received a message from ' + value.senderId + ': ' + value.data); + } + }); } - public sendMessage(recipientId: string, message: string) { - if (recipientId === this.ownId) { - // Catch sending messages to itself. Trying to do so throws an error from multiWindowService.sendMessage() - this.logs.unshift('Can\'t send messages to itself. Select another window.'); - - return; - } - this.multiWindowService.sendMessage(recipientId, 'customEvent', message).subscribe( - (messageId: string) => { - this.logs.unshift('Message send, ID is ' + messageId); - }, - (error) => { - this.logs.unshift('Message sending failed, error: ' + error); - }, - () => { - this.logs.unshift('Message successfully delivered'); - }); + public sendMessage(message: string) { + this.multiWindowService.sendMessage("data", message); } public removeLogMessage(index: number) { this.logs.splice(index, 1); } - public changeName() { - this.multiWindowService.name = this.ownName = this.newName; - } - public newWindow() { - const newWindowData = this.multiWindowService.newWindow(); - newWindowData.created.subscribe({ - next: () => { - }, - error: (err) => { - this.logs.unshift('An error occured while waiting for the new window to start consuming messages'); - }, - complete: () => { - this.logs.unshift('The new window with id ' + newWindowData.windowId + ' got created and starts consuming messages'); - } - } - ); - window.open('?' + newWindowData.urlString); + window.open('?'); } public windowTrackerFunc(item, index) { From 63e05f8489dadb6204385721e9b88c9d191f0ee0 Mon Sep 17 00:00:00 2001 From: Jared Date: Fri, 2 Jun 2023 22:52:19 -0400 Subject: [PATCH 3/9] Constructing this to be the way I want it to be -- Improving library... (making my own basically) --- .../src/lib/providers/config.provider.ts | 14 ---- .../src/lib/providers/multi-window.service.ts | 76 +++++++++++-------- .../src/lib/types/message.type.ts | 22 +++--- .../src/lib/types/multi-window.config.ts | 42 ---------- .../src/lib/types/window.type.ts | 1 + src/app/app.component.ts | 20 +++-- 6 files changed, 69 insertions(+), 106 deletions(-) delete mode 100644 projects/ngx-multi-window/src/lib/providers/config.provider.ts delete mode 100644 projects/ngx-multi-window/src/lib/types/multi-window.config.ts diff --git a/projects/ngx-multi-window/src/lib/providers/config.provider.ts b/projects/ngx-multi-window/src/lib/providers/config.provider.ts deleted file mode 100644 index 5297ec6..0000000 --- a/projects/ngx-multi-window/src/lib/providers/config.provider.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { InjectionToken } from '@angular/core'; -import { MultiWindowConfig } from '../types/multi-window.config'; -import { WindowSaveStrategy } from '../types/window-save-strategy.enum'; - -export const NGXMW_CONFIG = new InjectionToken('ngxmw_config'); - -export const defaultMultiWindowConfig: MultiWindowConfig = { - keyPrefix: 'ngxmw_', - heartbeat: 1000, - newWindowScan: 1000, - messageTimeout: 10000, - windowTimeout: 2000, - windowSaveStrategy: WindowSaveStrategy.SAVE_WHEN_EMPTY, -}; diff --git a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts index e408138..ea23bdf 100644 --- a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts +++ b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts @@ -1,23 +1,23 @@ import {Inject, Injectable, OnDestroy, Optional} from '@angular/core'; -import { Location } from '@angular/common'; -import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import {Location} from '@angular/common'; +import {Observable, Subject} from 'rxjs'; -import { defaultMultiWindowConfig, NGXMW_CONFIG } from './config.provider'; -import { MultiWindowConfig } from '../types/multi-window.config'; -import { KnownAppWindow } from '../types/window.type'; -import { Message } from '../types/message.type'; -import { WindowRef } from '../providers/window.provider'; +import {KnownAppWindow} from '../types/window.type'; +import {EventType, Message, MessageTemplate, MessageType} from '../types/message.type'; +import {WindowRef} from '../providers/window.provider'; @Injectable({ providedIn: 'root', }) export class MultiWindowService implements OnDestroy { - private config: MultiWindowConfig; - private myWindow: KnownAppWindow; - private knownWindows: {[broadcastId: string]: {[windowId: string]: {}}} = {}; + public getMyWindow(): KnownAppWindow { + return this.myWindow; + } + + private knownWindows: {[broadcastId: string]: KnownAppWindow[]} = {}; private knownBroadcasters: {[broadcastId: string]: BroadcastChannel} = {}; @@ -37,8 +37,9 @@ export class MultiWindowService implements OnDestroy { return new Date().getTime().toString(36).substr(-4) + Math.random().toString(36).substr(2, 9); } - constructor(@Inject(NGXMW_CONFIG) customConfig: MultiWindowConfig, @Optional() private location: Location, private windowRef: WindowRef) { - this.config = {...defaultMultiWindowConfig, ...customConfig}; + constructor() { + this.myWindow.id = this.generateId(); + this.myWindow.name = window.name; } public ngOnDestroy() { @@ -54,10 +55,6 @@ export class MultiWindowService implements OnDestroy { return knownBroadcasters; } - get id(): string { - return this.myWindow.id; - } - /** * An observable to subscribe to. It emits all messages the current window receives. * After a message has been emitted by this observable it is marked as read, so the sending window @@ -79,23 +76,26 @@ export class MultiWindowService implements OnDestroy { const bc = this.getBroadcastChannel(broadcastChannelId); if (this.knownListeners[bc.name] == undefined) { bc.onmessage = (message: any) => { - if (message.type == 'WINDOW_KILLED') { + const msg: Message = message as Message; + if (msg.type == MessageType.WINDOW_KILLED) { // We want to handle the window killed here and make sure we remove it as a known window - delete this.knownWindows[bc.name][message.senderId]; + this.knownWindows[bc.name] = this.knownWindows[bc.name].filter((win) => {return win.id != message.senderId}); return; } else - if (message.type == 'WINDOW_CREATED') { - this.knownWindows[bc.name][message.senderId] = {}; + if (msg.type == MessageType.WINDOW_CREATED) { + this.knownWindows[bc.name].push({name: message.senderName, id: message.senderId} as KnownAppWindow); return; } this.messageSubjects[bc.name].next(message as Message); } } // When we register a new listener, we want to tell all the windows listening that we have a new known window to add to their list... - this.sendMessageToAll({ - type: 'WINDOW_CREATED', - senderId: this.id - }) + this.sendMessage({ + type: MessageType.WINDOW_KILLED, + event: EventType.INTERNAL, + senderId: this.myWindow.id, + senderName: window.name + } as Message) return this; } @@ -112,10 +112,12 @@ export class MultiWindowService implements OnDestroy { this.knownListeners = []; } private handleServiceDestroyed() { - this.sendMessageToAll({ - type: "WINDOW_KILLED", - senderId: this.id - }); + this.sendMessage({ + type: MessageType.WINDOW_KILLED, + event: EventType.INTERNAL, + senderId: this.myWindow.id, + senderName: window.name + } as Message); this.closeAllListeners(); } @@ -152,6 +154,20 @@ export class MultiWindowService implements OnDestroy { return this.knownWindows; } - public sendMessage(broadcastChannelId: string, message: any) {} - public sendMessageToAll(message: any) {} + public sendMessage(message: MessageTemplate, broadcastId?: string) { + let msg: Message = message as Message; + msg.senderName = this.myWindow.name; + msg.senderId = this.myWindow.id; + switch (msg.type) { + case MessageType.ALL_LISTENERS: + case MessageType.SPECIFIC_WINDOW: + for (const bc of this.getKnownBroadcasters()) { + bc.postMessage(msg); + } + break; + case MessageType.SPECIFIC_LISTENER: + this.getBroadcastChannel(broadcastId).postMessage(msg); + break; + } + } } diff --git a/projects/ngx-multi-window/src/lib/types/message.type.ts b/projects/ngx-multi-window/src/lib/types/message.type.ts index 6f44f25..e26129c 100644 --- a/projects/ngx-multi-window/src/lib/types/message.type.ts +++ b/projects/ngx-multi-window/src/lib/types/message.type.ts @@ -1,20 +1,24 @@ export enum MessageType { - MESSAGE, - MESSAGE_RECEIVED, - PING, + WINDOW_CREATED, + WINDOW_KILLED, + SPECIFIC_WINDOW, + SPECIFIC_LISTENER, + ALL_LISTENERS +} + +export enum EventType { + CUSTOM_EVENT, + INTERNAL } export interface MessageTemplate { - recipientId: string; + recipientId?: string; type: MessageType; - event: string; + event: EventType; data?: any; - payload?: any; } export interface Message extends MessageTemplate { - send?: boolean; - messageId: string; - sendTime: number; senderId: string; + senderName: string; } diff --git a/projects/ngx-multi-window/src/lib/types/multi-window.config.ts b/projects/ngx-multi-window/src/lib/types/multi-window.config.ts deleted file mode 100644 index ebf2e92..0000000 --- a/projects/ngx-multi-window/src/lib/types/multi-window.config.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { WindowSaveStrategy } from './window-save-strategy.enum'; -/** - * Object representing the configuration options for the MultiWindow Module - */ -export interface MultiWindowConfig { - /** - * String to be used as prefix when storing data in the localstorage - */ - keyPrefix?: string; - - /** - * Time in milliseconds how often a heartbeat should be performed. During a heartbeat a window - * looks for new messages from other windows and processes these messages. - */ - heartbeat?: number; - - /** - * Time in milliseconds how often a scan for new windows should be performed. - */ - newWindowScan?: number; - - /** - * Time in milliseconds after which a message delivery is considered to have failed. - * If no "message_received" confirmation has arrived during the specified duration, - * the message will be removed from the outbox. - */ - messageTimeout?: number; - - /** - * Time in milliseconds after which a window is considered dead. - * - * Should be a multiple of {@link MultiWindowConfig#newWindowScan} - */ - windowTimeout?: number; - - /** - * A strategy to be used when it comes to saving the window name. - * - * Refer to the different enum values for detailed description about the possible values. - */ - windowSaveStrategy?: WindowSaveStrategy; -} diff --git a/projects/ngx-multi-window/src/lib/types/window.type.ts b/projects/ngx-multi-window/src/lib/types/window.type.ts index 91179f6..b87ff90 100644 --- a/projects/ngx-multi-window/src/lib/types/window.type.ts +++ b/projects/ngx-multi-window/src/lib/types/window.type.ts @@ -1,3 +1,4 @@ export interface KnownAppWindow { id: string; + name: string; } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ea68fd8..54c7a2f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,7 +1,6 @@ -import { Component, HostListener, OnInit } from '@angular/core'; -import { MultiWindowService, Message, KnownAppWindow } from 'ngx-multi-window'; -import { NameGeneratorService } from './providers/name-generator.service'; -import {delay} from "rxjs"; +import {Component, OnInit} from '@angular/core'; +import {Message, MessageType, MultiWindowService} from 'ngx-multi-window'; +import {NameGeneratorService} from './providers/name-generator.service'; @Component({ selector: 'app-root', @@ -25,7 +24,7 @@ export class AppComponent implements OnInit { } ngOnInit(): void { - this.ownId = this.multiWindowService.id; + this.ownId = this.multiWindowService.getMyWindow().id; this.newName = this.ownName; this.multiWindowService.onMessage('data').subscribe((value: Message) => { if (value.senderId != this.ownId) { @@ -34,8 +33,11 @@ export class AppComponent implements OnInit { }); } - public sendMessage(message: string) { - this.multiWindowService.sendMessage("data", message); + public sendMessageToAll(message: string) { + this.multiWindowService.sendMessage({ + data: message, + type: MessageType.ALL_LISTENERS + } as Message); } public removeLogMessage(index: number) { @@ -45,8 +47,4 @@ export class AppComponent implements OnInit { public newWindow() { window.open('?'); } - - public windowTrackerFunc(item, index) { - return item.id; - } } From 170b03b59a0d9ccd6ad77d8ac6cf42a875a4617a Mon Sep 17 00:00:00 2001 From: Jared Date: Sat, 3 Jun 2023 21:23:54 -0400 Subject: [PATCH 4/9] Most of the library is done... Just has some kinks here and there --- projects/ngx-multi-window/src/index.ts | 4 - .../src/lib/multi-window.module.ts | 16 +- .../src/lib/providers/multi-window.service.ts | 140 ++++++++++++++---- .../src/lib/types/message.type.ts | 3 + .../src/lib/types/window.type.ts | 6 + src/app/app.component.html | 16 +- src/app/app.component.ts | 44 ++++-- src/app/app.module.ts | 6 +- 8 files changed, 163 insertions(+), 72 deletions(-) diff --git a/projects/ngx-multi-window/src/index.ts b/projects/ngx-multi-window/src/index.ts index 6cfd0c3..15a0e23 100644 --- a/projects/ngx-multi-window/src/index.ts +++ b/projects/ngx-multi-window/src/index.ts @@ -4,12 +4,8 @@ export * from './lib/multi-window.module'; -export * from './lib/providers/config.provider'; export * from './lib/providers/multi-window.service'; -export * from './lib/providers/storage.service'; export * from './lib/providers/window.provider'; export * from './lib/types/message.type'; -export * from './lib/types/multi-window.config'; -export * from './lib/types/window-save-strategy.enum'; export * from './lib/types/window.type'; diff --git a/projects/ngx-multi-window/src/lib/multi-window.module.ts b/projects/ngx-multi-window/src/lib/multi-window.module.ts index 9d411c4..f8f81a9 100644 --- a/projects/ngx-multi-window/src/lib/multi-window.module.ts +++ b/projects/ngx-multi-window/src/lib/multi-window.module.ts @@ -2,25 +2,13 @@ import { CommonModule } from '@angular/common'; import { ModuleWithProviders, NgModule } from '@angular/core'; import { MultiWindowService } from './providers/multi-window.service'; -import { StorageService } from './providers/storage.service'; -import { NGXMW_CONFIG } from './providers/config.provider'; -import { MultiWindowConfig } from './types/multi-window.config'; import { WindowRef } from './providers/window.provider'; @NgModule({ imports: [CommonModule], providers: [ - StorageService, MultiWindowService, - WindowRef, - {provide: NGXMW_CONFIG, useValue: {}}, + WindowRef ], }) -export class MultiWindowModule { - static forRoot(config?: MultiWindowConfig): ModuleWithProviders { - return { - ngModule: MultiWindowModule, - providers: [MultiWindowService, {provide: NGXMW_CONFIG, useValue: config}], - }; - } -} +export class MultiWindowModule {} diff --git a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts index ea23bdf..97380ac 100644 --- a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts +++ b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts @@ -1,23 +1,35 @@ -import {Inject, Injectable, OnDestroy, Optional} from '@angular/core'; -import {Location} from '@angular/common'; -import {Observable, Subject} from 'rxjs'; +import {Injectable, OnDestroy} from '@angular/core'; +import {BehaviorSubject, fromEvent, Observable, Subject, Subscription, tap} from 'rxjs'; -import {KnownAppWindow} from '../types/window.type'; +import {KnownAppWindow, KnownWindows} from '../types/window.type'; import {EventType, Message, MessageTemplate, MessageType} from '../types/message.type'; -import {WindowRef} from '../providers/window.provider'; @Injectable({ providedIn: 'root', }) export class MultiWindowService implements OnDestroy { - private myWindow: KnownAppWindow; + private myWindow: KnownAppWindow = {} as KnownAppWindow; public getMyWindow(): KnownAppWindow { return this.myWindow; } - private knownWindows: {[broadcastId: string]: KnownAppWindow[]} = {}; + public setName(name: string): void { + this.myWindow.name = name; + this.sendMessage({ + event: EventType.INTERNAL, + type: MessageType.CHANGE_NAME + } as MessageTemplate); + for (const bcId of Object.keys(this.knownBroadcasters)) { + for (let win of this.knownWindows[bcId]) { + if (win.id == this.myWindow.id) + win.name = name; + } + } + } + + private knownWindows: KnownWindows = {}; private knownBroadcasters: {[broadcastId: string]: BroadcastChannel} = {}; @@ -31,7 +43,10 @@ export class MultiWindowService implements OnDestroy { /** * A subject to subscribe to in order to get notified about all known windows */ - //private windowSubject: Subject = new BehaviorSubject(this.knownWindows); + private windowSubject: Subject = new BehaviorSubject(this.knownWindows); + + private windowUnload$ = fromEvent(window, 'unload').pipe(tap(() => this.destroy())); + private unloadSub: Subscription = new Subscription(); private generateId(): string { return new Date().getTime().toString(36).substr(-4) + Math.random().toString(36).substr(2, 9); @@ -40,13 +55,19 @@ export class MultiWindowService implements OnDestroy { constructor() { this.myWindow.id = this.generateId(); this.myWindow.name = window.name; + this.myWindow.self = true; + this.unloadSub.add(this.windowUnload$.subscribe()); } - public ngOnDestroy() { + private destroy() { this.handleServiceDestroyed(); this.closeAllListeners(); } + ngOnDestroy() { + this.unloadSub.unsubscribe(); + } + public getKnownBroadcasters(): BroadcastChannel[] { let knownBroadcasters: BroadcastChannel[] = []; for (const bcId of Object.keys(this.knownBroadcasters)) { @@ -74,28 +95,67 @@ export class MultiWindowService implements OnDestroy { public listen(broadcastChannelId: string): MultiWindowService { const bc = this.getBroadcastChannel(broadcastChannelId); + if (this.knownWindows[bc.name] == undefined) + this.knownWindows[bc.name] = []; + this.knownWindows[bc.name].push({ + id: this.myWindow.id, + name: this.myWindow.name, + self: true + } as KnownAppWindow); if (this.knownListeners[bc.name] == undefined) { bc.onmessage = (message: any) => { - const msg: Message = message as Message; - if (msg.type == MessageType.WINDOW_KILLED) { - // We want to handle the window killed here and make sure we remove it as a known window - this.knownWindows[bc.name] = this.knownWindows[bc.name].filter((win) => {return win.id != message.senderId}); - return; - } else - if (msg.type == MessageType.WINDOW_CREATED) { - this.knownWindows[bc.name].push({name: message.senderName, id: message.senderId} as KnownAppWindow); - return; - } - this.messageSubjects[bc.name].next(message as Message); + const msg: Message = message.data as Message; + switch (msg.type) { + case MessageType.WINDOW_KILLED: + // We want to handle the window killed here and make sure we remove it as a known window + this.knownWindows[bc.name] = this.knownWindows[bc.name].filter((win) => { + return win.id != msg.senderId + }); + this.windowSubject.next(this.knownWindows); + break; + case MessageType.WINDOW_CREATED: + this.knownWindows[bc.name].push({name: msg.senderName, id: msg.senderId} as KnownAppWindow); + this.windowSubject.next(this.knownWindows); + break; + case MessageType.REQUEST_ALL_WINDOWS: + this.sendMessage({ + type: MessageType.REPORT_WINDOW, + event: EventType.INTERNAL, + recipientId: msg.senderId + } as Message, bc.name); + break; + case MessageType.REPORT_WINDOW: + if (msg.recipientId == this.myWindow.id) { + // They requested all the windows to report, give them the windows + this.knownWindows[bc.name].push({name: msg.senderName, id: msg.senderId} as KnownAppWindow); + this.windowSubject.next(this.knownWindows); + } + break; + case MessageType.CHANGE_NAME: + for (let win of this.knownWindows[bc.name]) { + if (msg.senderId == win.id) + win.name = msg.senderName; + } + break; + case MessageType.SPECIFIC_WINDOW: + if (msg.recipientId == this.myWindow.id) + this.messageSubjects[bc.name].next(msg); + break; + case MessageType.ALL_LISTENERS: + this.messageSubjects[bc.name].next(msg); + break; + } } } // When we register a new listener, we want to tell all the windows listening that we have a new known window to add to their list... this.sendMessage({ - type: MessageType.WINDOW_KILLED, - event: EventType.INTERNAL, - senderId: this.myWindow.id, - senderName: window.name - } as Message) + type: MessageType.WINDOW_CREATED, + event: EventType.INTERNAL + } as MessageTemplate); + this.sendMessage({ + type: MessageType.REQUEST_ALL_WINDOWS, + event: EventType.INTERNAL + } as MessageTemplate); return this; } @@ -116,7 +176,7 @@ export class MultiWindowService implements OnDestroy { type: MessageType.WINDOW_KILLED, event: EventType.INTERNAL, senderId: this.myWindow.id, - senderName: window.name + senderName: this.myWindow.name } as Message); this.closeAllListeners(); } @@ -125,6 +185,24 @@ export class MultiWindowService implements OnDestroy { return this.messageSubjects[broadcastChannelId].asObservable(); } + /** + * An observable to subscribe to. It emits the array of known windows + * whenever it is read again from the localstorage. + * + * This Observable emits the last list of known windows on subscription + * (refer to rxjs BehaviorSubject). + * + * Use {@link getKnownWindows} to get the current list of + * known windows or if you only need a snapshot of that list. + * + * @see {@link MultiWindowConfig#newWindowScan} + * @returns + */ + public onWindows(): Observable { + return this.windowSubject.asObservable(); + } + + /** * An observable to subscribe to. It emits the array of known windows * whenever it is read again from the localstorage. @@ -159,15 +237,13 @@ export class MultiWindowService implements OnDestroy { msg.senderName = this.myWindow.name; msg.senderId = this.myWindow.id; switch (msg.type) { - case MessageType.ALL_LISTENERS: - case MessageType.SPECIFIC_WINDOW: - for (const bc of this.getKnownBroadcasters()) { - bc.postMessage(msg); - } - break; case MessageType.SPECIFIC_LISTENER: this.getBroadcastChannel(broadcastId).postMessage(msg); break; + default: + for (const bc of this.getKnownBroadcasters()) { + bc.postMessage(msg); + } } } } diff --git a/projects/ngx-multi-window/src/lib/types/message.type.ts b/projects/ngx-multi-window/src/lib/types/message.type.ts index e26129c..f2e739e 100644 --- a/projects/ngx-multi-window/src/lib/types/message.type.ts +++ b/projects/ngx-multi-window/src/lib/types/message.type.ts @@ -1,6 +1,9 @@ export enum MessageType { WINDOW_CREATED, WINDOW_KILLED, + REPORT_WINDOW, + CHANGE_NAME, + REQUEST_ALL_WINDOWS, SPECIFIC_WINDOW, SPECIFIC_LISTENER, ALL_LISTENERS diff --git a/projects/ngx-multi-window/src/lib/types/window.type.ts b/projects/ngx-multi-window/src/lib/types/window.type.ts index b87ff90..5fa5141 100644 --- a/projects/ngx-multi-window/src/lib/types/window.type.ts +++ b/projects/ngx-multi-window/src/lib/types/window.type.ts @@ -1,4 +1,10 @@ export interface KnownAppWindow { id: string; name: string; + self: boolean; +} + + +export interface KnownWindows { + [broadcastId: string]: KnownAppWindow[]; } diff --git a/src/app/app.component.html b/src/app/app.component.html index 134135f..490fcdf 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -61,17 +61,17 @@

Registered Windows

- @@ -91,7 +91,7 @@

Send Message

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 54c7a2f..eb9f740 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,13 +1,14 @@ -import {Component, OnInit} from '@angular/core'; -import {Message, MessageType, MultiWindowService} from 'ngx-multi-window'; -import {NameGeneratorService} from './providers/name-generator.service'; +import {ChangeDetectorRef, Component, HostListener, OnDestroy, OnInit} from '@angular/core'; +import {EventType, KnownWindows, Message, MessageType, MultiWindowService} from 'ngx-multi-window'; +import {Subscription} from "rxjs"; +import {NameGeneratorService} from "./providers/name-generator.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) -export class AppComponent implements OnInit { +export class AppComponent implements OnInit, OnDestroy { ownName: string; ownId: string; @@ -15,31 +16,56 @@ export class AppComponent implements OnInit { newName: string; - constructor(private multiWindowService: MultiWindowService, private nameGenerator: NameGeneratorService) { - } + windows: KnownWindows = {}; + + private subs: Subscription = new Subscription(); + + constructor(private multiWindowService: MultiWindowService, private changeDetectorRef: ChangeDetectorRef, private nameGenerator: NameGeneratorService) {} public pause(milliseconds) { var dt = new Date(); while ((new Date().getTime()) - dt.getTime() <= milliseconds) { /* Do nothing */ } } + public changeName() { + this.ownName = this.newName; + this.multiWindowService.setName(this.newName); + } + ngOnInit(): void { this.ownId = this.multiWindowService.getMyWindow().id; + this.ownName = this.nameGenerator.getRandomFakeName(); + this.multiWindowService.setName(this.ownName); this.newName = this.ownName; - this.multiWindowService.onMessage('data').subscribe((value: Message) => { + this.multiWindowService.listen('data'); + this.subs.add(this.multiWindowService.onMessage('data').subscribe((value: Message) => { + console.log("[DEBUG] Received message:", value); if (value.senderId != this.ownId) { this.logs.unshift('Received a message from ' + value.senderId + ': ' + value.data); + this.changeDetectorRef.detectChanges(); } - }); + })); + this.windows = this.multiWindowService.getKnownWindows(); + this.subs.add(this.multiWindowService.onWindows().subscribe((knownWindows) => { + this.windows = knownWindows; + this.changeDetectorRef.detectChanges(); + })); + } + + ngOnDestroy(): void { + this.subs.unsubscribe(); } public sendMessageToAll(message: string) { this.multiWindowService.sendMessage({ data: message, - type: MessageType.ALL_LISTENERS + type: MessageType.ALL_LISTENERS, + event: EventType.CUSTOM_EVENT } as Message); } + public sendMessage(message: string, recipientId: string) {} + public removeLogMessage(index: number) { this.logs.splice(index, 1); } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b9b16ea..84ed92a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,9 +4,6 @@ import { Location, LocationStrategy, PathLocationStrategy } from '@angular/commo import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; -import { MultiWindowConfig, MultiWindowModule, WindowSaveStrategy } from 'ngx-multi-window'; - -const config: MultiWindowConfig = {windowSaveStrategy: WindowSaveStrategy.SAVE_WHEN_EMPTY}; @NgModule({ declarations: [ @@ -14,8 +11,7 @@ const config: MultiWindowConfig = {windowSaveStrategy: WindowSaveStrategy.SAVE_W ], imports: [ BrowserModule, - FormsModule, - MultiWindowModule.forRoot(config), + FormsModule ], providers: [Location, {provide: LocationStrategy, useClass: PathLocationStrategy}], bootstrap: [AppComponent], From 2a83737e8b2fde15bf3f4ac16f4216ab31b90c2e Mon Sep 17 00:00:00 2001 From: Jared Date: Sat, 3 Jun 2023 22:15:23 -0400 Subject: [PATCH 5/9] More polishing --- src/app/app.component.html | 10 ++++++++-- src/app/app.component.ts | 13 ++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 490fcdf..cb02ea9 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -62,6 +62,12 @@

Registered Windows

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index eb9f740..3c3c6aa 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -64,7 +64,18 @@ export class AppComponent implements OnInit, OnDestroy { } as Message); } - public sendMessage(message: string, recipientId: string) {} + public sendMessage(message: string, recipientId: string) { + if (recipientId != 'ALL') { + this.multiWindowService.sendMessage({ + data: message, + type: MessageType.SPECIFIC_WINDOW, + event: EventType.CUSTOM_EVENT, + recipientId: recipientId + } as Message); + } else { + this.sendMessageToAll(message); + } + } public removeLogMessage(index: number) { this.logs.splice(index, 1); From 4c4df7061c8aabfce47d0c24b27559ef77d452f8 Mon Sep 17 00:00:00 2001 From: Jared Date: Sat, 3 Jun 2023 22:46:53 -0400 Subject: [PATCH 6/9] More polishing x2 --- .../src/lib/providers/multi-window.service.ts | 125 +++++++++++------- .../src/lib/types/message.type.ts | 26 +++- 2 files changed, 95 insertions(+), 56 deletions(-) diff --git a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts index 97380ac..2f2c463 100644 --- a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts +++ b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts @@ -2,7 +2,13 @@ import {Injectable, OnDestroy} from '@angular/core'; import {BehaviorSubject, fromEvent, Observable, Subject, Subscription, tap} from 'rxjs'; import {KnownAppWindow, KnownWindows} from '../types/window.type'; -import {EventType, Message, MessageTemplate, MessageType} from '../types/message.type'; +import { + InternalMessage, InternalMessageTemplate, + InternalMessageType, + Message, + MessageTemplate, + MessageType +} from '../types/message.type'; @Injectable({ providedIn: 'root', @@ -17,10 +23,10 @@ export class MultiWindowService implements OnDestroy { public setName(name: string): void { this.myWindow.name = name; - this.sendMessage({ - event: EventType.INTERNAL, - type: MessageType.CHANGE_NAME - } as MessageTemplate); + this.sendInternalMessage({ + type: InternalMessageType.CHANGE_NAME, + isInternal: true + } as InternalMessageTemplate); for (const bcId of Object.keys(this.knownBroadcasters)) { for (let win of this.knownWindows[bcId]) { if (win.id == this.myWindow.id) @@ -104,39 +110,46 @@ export class MultiWindowService implements OnDestroy { } as KnownAppWindow); if (this.knownListeners[bc.name] == undefined) { bc.onmessage = (message: any) => { - const msg: Message = message.data as Message; - switch (msg.type) { - case MessageType.WINDOW_KILLED: - // We want to handle the window killed here and make sure we remove it as a known window - this.knownWindows[bc.name] = this.knownWindows[bc.name].filter((win) => { - return win.id != msg.senderId - }); - this.windowSubject.next(this.knownWindows); - break; - case MessageType.WINDOW_CREATED: - this.knownWindows[bc.name].push({name: msg.senderName, id: msg.senderId} as KnownAppWindow); - this.windowSubject.next(this.knownWindows); - break; - case MessageType.REQUEST_ALL_WINDOWS: - this.sendMessage({ - type: MessageType.REPORT_WINDOW, - event: EventType.INTERNAL, - recipientId: msg.senderId - } as Message, bc.name); - break; - case MessageType.REPORT_WINDOW: - if (msg.recipientId == this.myWindow.id) { - // They requested all the windows to report, give them the windows + let msg = message.data; + if (msg.isInternal != undefined) { + msg = message.data as InternalMessage; + switch (msg.type) { + case InternalMessageType.WINDOW_KILLED: + // We want to handle the window killed here and make sure we remove it as a known window + this.knownWindows[bc.name] = this.knownWindows[bc.name].filter((win) => { + return win.id != msg.senderId + }); + this.windowSubject.next(this.knownWindows); + break; + case InternalMessageType.WINDOW_CREATED: this.knownWindows[bc.name].push({name: msg.senderName, id: msg.senderId} as KnownAppWindow); this.windowSubject.next(this.knownWindows); - } - break; - case MessageType.CHANGE_NAME: - for (let win of this.knownWindows[bc.name]) { - if (msg.senderId == win.id) - win.name = msg.senderName; - } - break; + break; + case InternalMessageType.REQUEST_ALL_WINDOWS: + this.sendInternalMessage({ + type: InternalMessageType.REPORT_WINDOW, + isInternal: true, + recipientId: msg.senderId + } as InternalMessageTemplate, bc.name); + break; + case InternalMessageType.REPORT_WINDOW: + if (msg.recipientId == this.myWindow.id) { + // They requested all the windows to report, give them the windows + this.knownWindows[bc.name].push({name: msg.senderName, id: msg.senderId} as KnownAppWindow); + this.windowSubject.next(this.knownWindows); + } + break; + case InternalMessageType.CHANGE_NAME: + for (let win of this.knownWindows[bc.name]) { + if (msg.senderId == win.id) + win.name = msg.senderName; + } + break; + } + return; + } + msg = message.data as Message; + switch (msg.type) { case MessageType.SPECIFIC_WINDOW: if (msg.recipientId == this.myWindow.id) this.messageSubjects[bc.name].next(msg); @@ -148,14 +161,14 @@ export class MultiWindowService implements OnDestroy { } } // When we register a new listener, we want to tell all the windows listening that we have a new known window to add to their list... - this.sendMessage({ - type: MessageType.WINDOW_CREATED, - event: EventType.INTERNAL - } as MessageTemplate); - this.sendMessage({ - type: MessageType.REQUEST_ALL_WINDOWS, - event: EventType.INTERNAL - } as MessageTemplate); + this.sendInternalMessage({ + type: InternalMessageType.WINDOW_CREATED, + isInternal: true + } as InternalMessageTemplate); + this.sendInternalMessage({ + type: InternalMessageType.REQUEST_ALL_WINDOWS, + isInternal: true + } as InternalMessageTemplate); return this; } @@ -172,12 +185,10 @@ export class MultiWindowService implements OnDestroy { this.knownListeners = []; } private handleServiceDestroyed() { - this.sendMessage({ - type: MessageType.WINDOW_KILLED, - event: EventType.INTERNAL, - senderId: this.myWindow.id, - senderName: this.myWindow.name - } as Message); + this.sendInternalMessage({ + type: InternalMessageType.WINDOW_KILLED, + isInternal: true + } as InternalMessageTemplate); this.closeAllListeners(); } @@ -232,6 +243,20 @@ export class MultiWindowService implements OnDestroy { return this.knownWindows; } + private sendInternalMessage(message: InternalMessageTemplate, broadcastId?: string) { + let msg: InternalMessage = message as InternalMessage; + msg.senderName = this.myWindow.name; + msg.senderId = this.myWindow.id; + switch (msg.type) { + case InternalMessageType.SPECIFIC_LISTENER: + this.getBroadcastChannel(broadcastId).postMessage(msg); + break; + default: + for (const bc of this.getKnownBroadcasters()) { + bc.postMessage(msg); + } + } + } public sendMessage(message: MessageTemplate, broadcastId?: string) { let msg: Message = message as Message; msg.senderName = this.myWindow.name; diff --git a/projects/ngx-multi-window/src/lib/types/message.type.ts b/projects/ngx-multi-window/src/lib/types/message.type.ts index f2e739e..687954b 100644 --- a/projects/ngx-multi-window/src/lib/types/message.type.ts +++ b/projects/ngx-multi-window/src/lib/types/message.type.ts @@ -1,26 +1,40 @@ -export enum MessageType { +export enum InternalMessageType { + /** + * INTERNAL + */ WINDOW_CREATED, WINDOW_KILLED, REPORT_WINDOW, CHANGE_NAME, REQUEST_ALL_WINDOWS, + SPECIFIC_LISTENER +} +export enum MessageType { + /** + * EXTERNAL + */ SPECIFIC_WINDOW, SPECIFIC_LISTENER, ALL_LISTENERS } -export enum EventType { - CUSTOM_EVENT, - INTERNAL +export interface InternalMessageTemplate { + recipientId?: string; + type: InternalMessageType; + isInternal: boolean; + data?: any; } - export interface MessageTemplate { recipientId?: string; type: MessageType; - event: EventType; data?: any; } +export interface InternalMessage extends InternalMessageTemplate { + senderId: string; + senderName: string; +} + export interface Message extends MessageTemplate { senderId: string; senderName: string; From 6ce474ff9c18821a5674c57530c844bd3b5d56df Mon Sep 17 00:00:00 2001 From: Jared Date: Sun, 4 Jun 2023 20:48:08 -0400 Subject: [PATCH 7/9] More polishing x3 --- src/app/app.component.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3c3c6aa..4359025 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ import {ChangeDetectorRef, Component, HostListener, OnDestroy, OnInit} from '@angular/core'; -import {EventType, KnownWindows, Message, MessageType, MultiWindowService} from 'ngx-multi-window'; +import {KnownWindows, Message, MessageType, MultiWindowService} from 'ngx-multi-window'; import {Subscription} from "rxjs"; import {NameGeneratorService} from "./providers/name-generator.service"; @@ -37,9 +37,8 @@ export class AppComponent implements OnInit, OnDestroy { this.ownName = this.nameGenerator.getRandomFakeName(); this.multiWindowService.setName(this.ownName); this.newName = this.ownName; - this.multiWindowService.listen('data'); - this.subs.add(this.multiWindowService.onMessage('data').subscribe((value: Message) => { - console.log("[DEBUG] Received message:", value); + this.multiWindowService.listen('channel'); + this.subs.add(this.multiWindowService.onMessage('channel').subscribe((value: Message) => { if (value.senderId != this.ownId) { this.logs.unshift('Received a message from ' + value.senderId + ': ' + value.data); this.changeDetectorRef.detectChanges(); @@ -60,7 +59,6 @@ export class AppComponent implements OnInit, OnDestroy { this.multiWindowService.sendMessage({ data: message, type: MessageType.ALL_LISTENERS, - event: EventType.CUSTOM_EVENT } as Message); } @@ -69,7 +67,6 @@ export class AppComponent implements OnInit, OnDestroy { this.multiWindowService.sendMessage({ data: message, type: MessageType.SPECIFIC_WINDOW, - event: EventType.CUSTOM_EVENT, recipientId: recipientId } as Message); } else { From acd496736be294f998bad7f3eff91db765696ed5 Mon Sep 17 00:00:00 2001 From: Jared Date: Mon, 5 Jun 2023 21:14:24 -0400 Subject: [PATCH 8/9] Added back in name generator service --- .../providers/name-generator.service.spec.ts | 16 ++++++++++++ .../lib/providers/name-generator.service.ts | 26 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 projects/ngx-multi-window/src/lib/providers/name-generator.service.spec.ts create mode 100644 projects/ngx-multi-window/src/lib/providers/name-generator.service.ts diff --git a/projects/ngx-multi-window/src/lib/providers/name-generator.service.spec.ts b/projects/ngx-multi-window/src/lib/providers/name-generator.service.spec.ts new file mode 100644 index 0000000..c7eed67 --- /dev/null +++ b/projects/ngx-multi-window/src/lib/providers/name-generator.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { NameGeneratorService } from './name-generator.service'; + +describe('NameGeneratorService', () => { + let service: NameGeneratorService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(NameGeneratorService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/projects/ngx-multi-window/src/lib/providers/name-generator.service.ts b/projects/ngx-multi-window/src/lib/providers/name-generator.service.ts new file mode 100644 index 0000000..f1e5f8e --- /dev/null +++ b/projects/ngx-multi-window/src/lib/providers/name-generator.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class NameGeneratorService { + + constructor() { + } + + private static name1 = ['Black', 'White', 'Gray', 'Brown', 'Red', 'Pink', 'Crimson', 'Carnelian', 'Orange', 'Yellow', 'Ivory', 'Cream', 'Green', 'Viridian', 'Aquamarine', 'Cyan', 'Blue', 'Cerulean', 'Azure', 'Indigo', 'Navy', 'Violet', 'Purple', 'Lavender', 'Magenta', 'Rainbow', 'Iridescent', 'Spectrum', 'Prism', 'Bold', 'Vivid', 'Pale', 'Clear', 'Glass', 'Translucent', 'Misty', 'Dark', 'Light', 'Gold', 'Silver', 'Copper', 'Bronze', 'Steel', 'Iron', 'Brass', 'Mercury', 'Zinc', 'Chrome', 'Platinum', 'Titanium', 'Nickel', 'Lead', 'Pewter', 'Rust', 'Metal', 'Stone', 'Quartz', 'Granite', 'Marble', 'Alabaster', 'Agate', 'Jasper', 'Pebble', 'Pyrite', 'Crystal', 'Geode', 'Obsidian', 'Mica', 'Flint', 'Sand', 'Gravel', 'Boulder', 'Basalt', 'Ruby', 'Beryl', 'Scarlet', 'Citrine', 'Sulpher', 'Topaz', 'Amber', 'Emerald', 'Malachite', 'Jade', 'Abalone', 'Lapis', 'Sapphire', 'Diamond', 'Peridot', 'Gem', 'Jewel', 'Bevel', 'Coral', 'Jet', 'Ebony', 'Wood', 'Tree', 'Cherry', 'Maple', 'Cedar', 'Branch', 'Bramble', 'Rowan', 'Ash', 'Fir', 'Pine', 'Cactus', 'Alder', 'Grove', 'Forest', 'Jungle', 'Palm', 'Bush', 'Mulberry', 'Juniper', 'Vine', 'Ivy', 'Rose', 'Lily', 'Tulip', 'Daffodil', 'Honeysuckle', 'Fuschia', 'Hazel', 'Walnut', 'Almond', 'Lime', 'Lemon', 'Apple', 'Blossom', 'Bloom', 'Crocus', 'Rose', 'Buttercup', 'Dandelion', 'Iris', 'Carnation', 'Fern', 'Root', 'Branch', 'Leaf', 'Seed', 'Flower', 'Petal', 'Pollen', 'Orchid', 'Mangrove', 'Cypress', 'Sequoia', 'Sage', 'Heather', 'Snapdragon', 'Daisy', 'Mountain', 'Hill', 'Alpine', 'Chestnut', 'Valley', 'Glacier', 'Forest', 'Grove', 'Glen', 'Tree', 'Thorn', 'Stump', 'Desert', 'Canyon', 'Dune', 'Oasis', 'Mirage', 'Well', 'Spring', 'Meadow', 'Field', 'Prairie', 'Grass', 'Tundra', 'Island', 'Shore', 'Sand', 'Shell', 'Surf', 'Wave', 'Foam', 'Tide', 'Lake', 'River', 'Brook', 'Stream', 'Pool', 'Pond', 'Sun', 'Sprinkle', 'Shade', 'Shadow', 'Rain', 'Cloud', 'Storm', 'Hail', 'Snow', 'Sleet', 'Thunder', 'Lightning', 'Wind', 'Hurricane', 'Typhoon', 'Dawn', 'Sunrise', 'Morning', 'Noon', 'Twilight', 'Evening', 'Sunset', 'Midnight', 'Night', 'Sky', 'Star', 'Stellar', 'Comet', 'Nebula', 'Quasar', 'Solar', 'Lunar', 'Planet', 'Meteor', 'Sprout', 'Pear', 'Plum', 'Kiwi', 'Berry', 'Apricot', 'Peach', 'Mango', 'Pineapple', 'Coconut', 'Olive', 'Ginger', 'Root', 'Plain', 'Fancy', 'Stripe', 'Spot', 'Speckle', 'Spangle', 'Ring', 'Band', 'Blaze', 'Paint', 'Pinto', 'Shade', 'Tabby', 'Brindle', 'Patch', 'Calico', 'Checker', 'Dot', 'Pattern', 'Glitter', 'Glimmer', 'Shimmer', 'Dull', 'Dust', 'Dirt', 'Glaze', 'Scratch', 'Quick', 'Swift', 'Fast', 'Slow', 'Clever', 'Fire', 'Flicker', 'Flash', 'Spark', 'Ember', 'Coal', 'Flame', 'Chocolate', 'Vanilla', 'Sugar', 'Spice', 'Cake', 'Pie', 'Cookie', 'Candy', 'Caramel', 'Spiral', 'Round', 'Jelly', 'Square', 'Narrow', 'Long', 'Short', 'Small', 'Tiny', 'Big', 'Giant', 'Great', 'Atom', 'Peppermint', 'Mint', 'Butter', 'Fringe', 'Rag', 'Quilt', 'Truth', 'Lie', 'Holy', 'Curse', 'Noble', 'Sly', 'Brave', 'Shy', 'Lava', 'Foul', 'Leather', 'Fantasy', 'Keen', 'Luminous', 'Feather', 'Sticky', 'Gossamer', 'Cotton', 'Rattle', 'Silk', 'Satin', 'Cord', 'Denim', 'Flannel', 'Plaid', 'Wool', 'Linen', 'Silent', 'Flax', 'Weak', 'Valiant', 'Fierce', 'Gentle', 'Rhinestone', 'Splash', 'North', 'South', 'East', 'West', 'Summer', 'Winter', 'Autumn', 'Spring', 'Season', 'Equinox', 'Solstice', 'Paper', 'Motley', 'Torch', 'Ballistic', 'Rampant', 'Shag', 'Freckle', 'Wild', 'Free', 'Chain', 'Sheer', 'Crazy', 'Mad', 'Candle', 'Ribbon', 'Lace', 'Notch', 'Wax', 'Shine', 'Shallow', 'Deep', 'Bubble', 'Harvest', 'Fluff', 'Venom', 'Boom', 'Slash', 'Rune', 'Cold', 'Quill', 'Love', 'Hate', 'Garnet', 'Zircon', 'Power', 'Bone', 'Void', 'Horn', 'Glory', 'Cyber', 'Nova', 'Hot', 'Helix', 'Cosmic', 'Quark', 'Quiver', 'Holly', 'Clover', 'Polar', 'Regal', 'Ripple', 'Ebony', 'Wheat', 'Phantom', 'Dew', 'Chisel', 'Crack', 'Chatter', 'Laser', 'Foil', 'Tin', 'Clever', 'Treasure', 'Maze', 'Twisty', 'Curly', 'Fortune', 'Fate', 'Destiny', 'Cute', 'Slime', 'Ink', 'Disco', 'Plume', 'Time', 'Psychadelic', 'Relic', 'Fossil', 'Water', 'Savage', 'Ancient', 'Rapid', 'Road', 'Trail', 'Stitch', 'Button', 'Bow', 'Nimble', 'Zest', 'Sour', 'Bitter', 'Phase', 'Fan', 'Frill', 'Plump', 'Pickle', 'Mud', 'Puddle', 'Pond', 'River', 'Spring', 'Stream', 'Battle', 'Arrow', 'Plume', 'Roan', 'Pitch', 'Tar', 'Cat', 'Dog', 'Horse', 'Lizard', 'Bird', 'Fish', 'Saber', 'Scythe', 'Sharp', 'Soft', 'Razor', 'Neon', 'Dandy', 'Weed', 'Swamp', 'Marsh', 'Bog', 'Peat', 'Moor', 'Muck', 'Mire', 'Grave', 'Fair', 'Just', 'Brick', 'Puzzle', 'Skitter', 'Prong', 'Fork', 'Dent', 'Dour', 'Warp', 'Luck', 'Coffee', 'Split', 'Chip', 'Hollow', 'Heavy', 'Legend', 'Hickory', 'Mesquite', 'Nettle', 'Rogue', 'Charm', 'Prickle', 'Bead', 'Sponge', 'Whip', 'Bald', 'Frost', 'Fog', 'Oil', 'Veil', 'Cliff', 'Volcano', 'Rift', 'Maze', 'Proud', 'Dew', 'Mirror', 'Shard', 'Salt', 'Pepper', 'Honey', 'Thread', 'Bristle', 'Ripple', 'Glow', 'Zenith']; + private static name2 = ['head', 'crest', 'crown', 'tooth', 'fang', 'horn', 'frill', 'skull', 'bone', 'tongue', 'throat', 'voice', 'nose', 'snout', 'chin', 'eye', 'sight', 'seer', 'speaker', 'singer', 'song', 'chanter', 'howler', 'chatter', 'shrieker', 'shriek', 'jaw', 'bite', 'biter', 'neck', 'shoulder', 'fin', 'wing', 'arm', 'lifter', 'grasp', 'grabber', 'hand', 'paw', 'foot', 'finger', 'toe', 'thumb', 'talon', 'palm', 'touch', 'racer', 'runner', 'hoof', 'fly', 'flier', 'swoop', 'roar', 'hiss', 'hisser', 'snarl', 'dive', 'diver', 'rib', 'chest', 'back', 'ridge', 'leg', 'legs', 'tail', 'beak', 'walker', 'lasher', 'swisher', 'carver', 'kicker', 'roarer', 'crusher', 'spike', 'shaker', 'charger', 'hunter', 'weaver', 'crafter', 'binder', 'scribe', 'muse', 'snap', 'snapper', 'slayer', 'stalker', 'track', 'tracker', 'scar', 'scarer', 'fright', 'killer', 'death', 'doom', 'healer', 'saver', 'friend', 'foe', 'guardian', 'thunder', 'lightning', 'cloud', 'storm', 'forger', 'scale', 'hair', 'braid', 'nape', 'belly', 'thief', 'stealer', 'reaper', 'giver', 'taker', 'dancer', 'player', 'gambler', 'twister', 'turner', 'painter', 'dart', 'drifter', 'sting', 'stinger', 'venom', 'spur', 'ripper', 'swallow', 'devourer', 'knight', 'lady', 'lord', 'queen', 'king', 'master', 'mistress', 'prince', 'princess', 'duke', 'dutchess', 'samurai', 'ninja', 'knave', 'slave', 'servant', 'sage', 'wizard', 'witch', 'warlock', 'warrior', 'jester', 'paladin', 'bard', 'trader', 'sword', 'shield', 'knife', 'dagger', 'arrow', 'bow', 'fighter', 'bane', 'follower', 'leader', 'scourge', 'watcher', 'cat', 'panther', 'tiger', 'cougar', 'puma', 'jaguar', 'ocelot', 'lynx', 'lion', 'leopard', 'ferret', 'weasel', 'wolverine', 'bear', 'raccoon', 'dog', 'wolf', 'kitten', 'puppy', 'cub', 'fox', 'hound', 'terrier', 'coyote', 'hyena', 'jackal', 'pig', 'horse', 'donkey', 'stallion', 'mare', 'zebra', 'antelope', 'gazelle', 'deer', 'buffalo', 'bison', 'boar', 'elk', 'whale', 'dolphin', 'shark', 'fish', 'minnow', 'salmon', 'ray', 'fisher', 'otter', 'gull', 'duck', 'goose', 'crow', 'raven', 'bird', 'eagle', 'raptor', 'hawk', 'falcon', 'moose', 'heron', 'owl', 'stork', 'crane', 'sparrow', 'robin', 'parrot', 'cockatoo', 'carp', 'lizard', 'gecko', 'iguana', 'snake', 'python', 'viper', 'boa', 'condor', 'vulture', 'spider', 'fly', 'scorpion', 'heron', 'oriole', 'toucan', 'bee', 'wasp', 'hornet', 'rabbit', 'bunny', 'hare', 'brow', 'mustang', 'ox', 'piper', 'soarer', 'flasher', 'moth', 'mask', 'hide', 'hero', 'antler', 'chill', 'chiller', 'gem', 'ogre', 'myth', 'elf', 'fairy', 'pixie', 'dragon', 'griffin', 'unicorn', 'pegasus', 'sprite', 'fancier', 'chopper', 'slicer', 'skinner', 'butterfly', 'legend', 'wanderer', 'rover', 'raver', 'loon', 'lancer', 'glass', 'glazer', 'flame', 'crystal', 'lantern', 'lighter', 'cloak', 'bell', 'ringer', 'keeper', 'centaur', 'bolt', 'catcher', 'whimsey', 'quester', 'rat', 'mouse', 'serpent', 'wyrm', 'gargoyle', 'thorn', 'whip', 'rider', 'spirit', 'sentry', 'bat', 'beetle', 'burn', 'cowl', 'stone', 'gem', 'collar', 'mark', 'grin', 'scowl', 'spear', 'razor', 'edge', 'seeker', 'jay', 'ape', 'monkey', 'gorilla', 'koala', 'kangaroo', 'yak', 'sloth', 'ant', 'roach', 'weed', 'seed', 'eater', 'razor', 'shirt', 'face', 'goat', 'mind', 'shift', 'rider', 'face', 'mole', 'vole', 'pirate', 'llama', 'stag', 'bug', 'cap', 'boot', 'drop', 'hugger', 'sargent', 'snagglefoot', 'carpet', 'curtain']; + + private static capFirst(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + } + + private static getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min; + } + + public getRandomFakeName(): string { + return NameGeneratorService.capFirst(NameGeneratorService.name1[NameGeneratorService.getRandomInt(0, NameGeneratorService.name1.length + 1)]) + ' ' + NameGeneratorService.capFirst(NameGeneratorService.name2[NameGeneratorService.getRandomInt(0, NameGeneratorService.name2.length + 1)]); + } + +} From 9e506381306d0073e428b7f83aa0da6b9b1d01f5 Mon Sep 17 00:00:00 2001 From: Jared Date: Mon, 5 Jun 2023 21:57:14 -0400 Subject: [PATCH 9/9] Updated README and patch some of code discrepancies --- README.md | 122 +++--------------- .../src/lib/providers/multi-window.service.ts | 20 --- src/app/app.component.ts | 6 +- 3 files changed, 21 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 83a397e..6001ea8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ For older angular versions you may install previous versions of this library: | ngx-multi-window version | compatible angular version | |--------------------------|----------------------------| +| `1.0` | `15` | | `0.6` | `15` | | `0.5` | `14` | | `0.4.1` | `8 - 13` | @@ -58,49 +59,26 @@ export class AppComponent { } ``` -### Configuration - -You may inject a custom `MultiWindowConfig` object when importing the `MultiWindowModule` into your application. - -```typescript -@NgModule({ - imports: [ - ... - MultiWindowModule.forRoot({ heartbeat: 542 }) - ], - }) -``` - -Check the description of the [MultiWindowConfig interface](https://github.com/Nolanus/ngx-multi-window/blob/master/projects/ngx-multi-window/src/lib/types/multi-window.config.ts) properties for options. -The [default options](https://github.com/Nolanus/ngx-multi-window/blob/master/projects/ngx-multi-window/src/lib/providers/config.provider.ts#L6) are -```typescript -{ - keyPrefix: 'ngxmw_', - heartbeat: 1000, - newWindowScan: 5000, - messageTimeout: 10000, - windowTimeout: 15000, - windowSaveStrategy: WindowSaveStrategy.NONE, -} -``` - ### Window ID and name -Every window has a unique, unchangeable id which can be accessed via `multiWindowService.id`. +Every window has a unique, unchangeable id which can be accessed via `multiWindowService.getMyWindow().id`. In addition to that every window as a changeable name which can be get/set -via `multiWindowService.name`. +via `multiWindowService.getMyWindow().name`. ### Receive messages +**IMPORTANT: To receive messages, you must run the method `multiWindowService.listen(broadcastChannelId: string)` for the multiWindowService to know it should listen to the channel.** + Receive messages addressed to the current window by subscribing to the observable returned from -`multiWindowService.onMessages()`: +`multiWindowService.onMessage(broadcastChannelId: string)`: ```typescript import { MultiWindowService, Message } from 'ngx-multi-window'; class App { constructor(private multiWindowService: MultiWindowService) { - multiWindowService.onMessage().subscribe((value: Message) => { + multiWindowService.listen("channelId"); + multiWindowService.onMessage("channelId").subscribe((value: Message) => { console.log('Received a message from ' + value.senderId + ': ' + value.data); }); } @@ -112,92 +90,32 @@ class App { Send a message by calling `multiWindowService.sendMessage()`: ```typescript -import { MultiWindowService, WindowData, Message } from 'ngx-multi-window'; +import { MultiWindowService, WindowData, MessageTemplate } from 'ngx-multi-window'; class App { constructor(private multiWindowService: MultiWindowService) { const recipientId: string; // TODO const message: string; // TODO - multiWindowService.sendMessage(recipientId, 'customEvent', message).subscribe( - (messageId: string) => { - console.log('Message send, ID is ' + messageId); - }, - (error) => { - console.log('Message sending failed, error: ' + error); - }, - () => { - console.log('Message successfully delivered'); - }); + multiWindowService.sendMessage({ + data: message, + type: MessageType.SPECIFIC_WINDOW, + recipientId: recipientId + } as MessageTemplate); } } -``` -The message returns an observable which will resolve with a message id once the message has been send (= written to local storage). -The receiving window will retrieve the message and respond with a `MessageType.MESSAGE_RECEIVED` typed message. -The sending window/app will be informed by finishing the observable. - -In case no `MessageType.MESSAGE_RECEIVED` message has been received by the sending window -within a certain time limit (`MultiWindowConfig.messageTimeout`, default is 10s) -the message submission will be canceled. The observable will be rejected and the -initial message will be removed from the current windows postbox. +``` ### Other windows To get the names and ids of other window/app instances the `MultiWindowService` offers two methods: -`multiWindowService.onWindows()` returns an observable to subscribe to in case you require periodic updates of the -fellow windows. The observable will emit a new value every time the local storage has been scanned for the windows. -This by default happens every 5 seconds (`MultiWindowConfig.newWindowScan`). +`multiWindowService.onWindows()` returns an observable to subscribe to in case you require periodic updates of the fellow windows. The observable will emit a new value every time a new window has been created or a window has been destroyed... -Use `multiWindowService.getKnownWindows` to return an array of `WindowData`. +Use `multiWindowService.getKnownWindows()` to return an array of `KnownAppWindow`. ### New windows -No special handling is necessary to open new windows. Every new window/app will register itself -by writing to its key in the local storage. Existing windows will identify new windows -after `MultiWindowConfig.newWindowScan` at the latest. - -The `MultiWindowService` offers a convenience method `newWindow()` which provides details for the -new window's start url. If used the returned observable can be utilized to get notified -once the new window is ready to consume/receive message. - -### Save window name - -The library comes with a mechanism to save the window id using the browser's `window.name` property. This -property is persisted on page reloads, resulting in the same tab/window running your angular application to keep -the ngx-multi-window id even when reloading the page. -Note: Only the window id is persisted, the customizable window name and messages are kept in the local storage, -but are automatically rediscovered by the new window once it starts consuming messages. - -To save the window id, set the respective config property `nameSafeStrategy` to the desired value. Additionally -one needs to call `saveWindow()` function e.g. during window unloading by attaching a `HostListener` in your -main AppComponent. - -```typescript -@HostListener('window:unload') -unloadHandler() { - this.multiWindowService.saveWindow(); -} -``` - -## Communication strategy - -This library is based on "pull-based" communication. Every window periodically checks the local storage for messages addressed to itself. -For that reason every window has its own key in the local storage, whose contents/value looks like: - -```json -{"heartbeat":1508936270103,"id":"oufi90mui5u5n","name":"AppWindow oufi90mui5u5n","messages":[]} -``` - -The heartbeat is updated every time the window performed a reading check on the other window's local storage keys. - -Sending message from sender A to recipient B involves the following steps: -- The sender A writes the initial message (including the text and recipient id of B) into the "messages" array located at its own local storage key -- The recipient window B reads the messages array of the other windows and picks up a message addressed to itself -- B places a "MESSAGE_RECEIVED" message addressed to A in its own messages array -- A picks up the "MESSAGE_RECEIVED" message in B's message array and removes the initial message from its own messages array -- B identifies that the initial message has been removed from A's messages array and removes the receipt message from its own messages array - -![Communication Strategy showcase](communication.gif) +No special handling is necessary to open new windows. Every new window/app will register itself. Existing windows will identify new windows. ## Example App @@ -208,10 +126,6 @@ Run the demo app by checking out that repository and execute the following comma npm install ng serve ``` - -## TODO - -- Tests and cross browser testing ## License diff --git a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts index 2f2c463..b6e873f 100644 --- a/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts +++ b/projects/ngx-multi-window/src/lib/providers/multi-window.service.ts @@ -213,26 +213,6 @@ export class MultiWindowService implements OnDestroy { return this.windowSubject.asObservable(); } - - /** - * An observable to subscribe to. It emits the array of known windows - * whenever it is read again from the localstorage. - * - * This Observable emits the last list of known windows on subscription - * (refer to rxjs BehaviorSubject). - * - * Use {@link getKnownWindows} to get the current list of - * known windows or if you only need a snapshot of that list. - * - * @see {@link MultiWindowConfig#newWindowScan} - * @returns - */ - /** / - public onWindows(): Observable { - return this.windowSubject.asObservable(); - } - /* */ - /** * Get the latest list of known windows. * diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4359025..c6e6789 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ import {ChangeDetectorRef, Component, HostListener, OnDestroy, OnInit} from '@angular/core'; -import {KnownWindows, Message, MessageType, MultiWindowService} from 'ngx-multi-window'; +import {KnownWindows, Message, MessageTemplate, MessageType, MultiWindowService} from 'ngx-multi-window'; import {Subscription} from "rxjs"; import {NameGeneratorService} from "./providers/name-generator.service"; @@ -59,7 +59,7 @@ export class AppComponent implements OnInit, OnDestroy { this.multiWindowService.sendMessage({ data: message, type: MessageType.ALL_LISTENERS, - } as Message); + } as MessageTemplate); } public sendMessage(message: string, recipientId: string) { @@ -68,7 +68,7 @@ export class AppComponent implements OnInit, OnDestroy { data: message, type: MessageType.SPECIFIC_WINDOW, recipientId: recipientId - } as Message); + } as MessageTemplate); } else { this.sendMessageToAll(message); }