Skip to content

Commit

Permalink
Merge pull request #48 from harald78/develop
Browse files Browse the repository at this point in the history
Implemented push notifications for web push
  • Loading branch information
harald78 authored Jun 25, 2024
2 parents d6dc6f5 + 170e24f commit a964fd0
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/app/core/auth/service/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ import {interval} from "rxjs";
}

async logout(): Promise<void> {
await this.logUserOut();
await this.router.navigate(['login']);
await this.logUserOut();
}

async showSettings(): Promise<void> {
Expand Down
5 changes: 1 addition & 4 deletions src/app/core/interceptor/unauthorized.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ export const unauthorizedInterceptor: HttpInterceptorFn = (req: HttpRequest<unkn

const handle401Error = (request: HttpRequest<unknown>, next: HttpHandlerFn, authService: AuthService)=> {

if (request.url.includes('refreshToken')){
return next(request);

} else if (request.url.includes('logout')) {
if (request.url.includes('logout')) {
return throwError(() => new Error('Logged out'));

} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { TestBed } from '@angular/core/testing';
import {MockProvider} from "ng-mocks";
import {SwPush} from "@angular/service-worker";
import {provideHttpClientTesting} from "@angular/common/http/testing";
import {HttpClient} from "@angular/common/http";

describe('PushNotificationService', () => {

beforeEach(() => {
TestBed.configureTestingModule({
providers: [MockProvider(SwPush),
provideHttpClientTesting(),
MockProvider(HttpClient)]
});
// service = TestBed.inject(PushNotificationService);
// swPush = TestBed.inject(SwPush);
});

// TODO: Fix and implement tests
it('should be created', () => {
// jest.spyOn(swPush.subscription, 'subscribe').mockReturnValue(null);
expect(true).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {inject, Injectable, signal, WritableSignal} from '@angular/core';
import {SwPush} from "@angular/service-worker";
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {BehaviorSubject, firstValueFrom} from "rxjs";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {AppSettingsState} from "../../../core/app-settings/+state/app-settings.state";
import {Toast, ToastService} from "../../../shared/components/toast/toast.service";
import {mdiAlert, mdiCheck} from "@mdi/js";
import {AuthState} from "../../../core/auth/+state/auth.state";

@Injectable({
providedIn: 'root'
})
export class PushNotificationService {

private vapidPublicKey = '';
public pushSubscription$: BehaviorSubject<PushSubscription | null> = new BehaviorSubject<PushSubscription | null>(null);
public activatedPush: WritableSignal<boolean> = signal(false);

private readonly swPush: SwPush = inject(SwPush);
private readonly httpClient: HttpClient = inject(HttpClient);
private readonly settingsState: AppSettingsState = inject(AppSettingsState);
private readonly toastService: ToastService = inject(ToastService);
private readonly authState: AuthState = inject(AuthState);

constructor() {
this.swPush.subscription
.pipe(takeUntilDestroyed())
.subscribe(sub => {
if (sub) {
this.activatedPush.set(true);
} else {
this.activatedPush.set(false);
}
console.log("Push subscription: ", sub);
this.pushSubscription$.next(sub);
});
}

public async subscribeToNotifications() {
if (!this.activatedPush()) {

await this.getVapidPublicKey().catch(err => this.showError(err));
this.swPush.requestSubscription({
serverPublicKey: this.vapidPublicKey
}).then(async (sub) => {
if (sub) {
await this.sendSubscriptionToServer(sub);
this.showSuccess();
}
}).catch(err => this.showError(err));
}
}

public sendSubscriptionToServer(sub: PushSubscription) {
return firstValueFrom(this.httpClient.post<string>(`${this.settingsState.baseUrl()}/notifications/subscribe/${this.authState.user().id}`, sub, {responseType: 'text' as 'json'}))
}

public async getVapidPublicKey(): Promise<void> {
this.vapidPublicKey = await firstValueFrom(this.httpClient.get<string>(`${this.settingsState.baseUrl()}/notifications/vapidPublicKey`, {responseType: 'text' as 'json'}));
}

private showError(err: any): void {
const errorToast: Toast = {classname: "bg-danger text-light", header: 'Could not subscribe to push notifications',
body: `${err.message}`, icon: mdiAlert, iconColor: "white"};
this.toastService.show(errorToast);
}

private showSuccess(): void {
const successToast: Toast = {classname: "bg-success text-light", icon: mdiCheck, header: '',
body: `Successfully subscribed to push notifications`, iconColor: "white"};
this.toastService.show(successToast);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
}
}

.trash-button-container {
.trash-button-container, .push-notification-container {
display: flex;
justify-content: center;
width: 100%;
}

#notification-trash-button {
#notification-trash-button, #push-notification-button {
width: 80%;
max-width: 500px;
height: 40px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';

import { NotificationsHomeComponent } from './notifications-home.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import {MockProvider} from "ng-mocks";
import {PushNotificationService} from "../../service/push-notification.service";

describe('NotificationsHomeComponent', () => {
let component: NotificationsHomeComponent;
let fixture: ComponentFixture<NotificationsHomeComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NotificationsHomeComponent, HttpClientTestingModule]
imports: [NotificationsHomeComponent, HttpClientTestingModule],
providers: [MockProvider(PushNotificationService)]
})
.compileComponents();

fixture = TestBed.createComponent(NotificationsHomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, inject, OnInit, signal, Signal, WritableSignal } from '@angular/core';
import {FormsModule} from "@angular/forms";
import { mdiTrashCanOutline } from '@mdi/js';
import {mdiTrashCanOutline} from '@mdi/js';
import {IconComponent} from "../../../../shared/components/icon/icon/icon.component";
import {NgClass, NgIf} from "@angular/common";
import {DeleteModalComponent} from "../delete-modal/delete-modal.component";
Expand All @@ -10,7 +10,7 @@ import {NotificationService} from "../../service/notification.service";
import {AuthState} from "../../../../core/auth/+state/auth.state";
import { NotificationTileComponent } from '../notification-tile/notification-tile.component';
import { IconButtonComponent } from '../../../../shared/components/icon-button/icon-button.component';

import {PushNotificationService} from "../../service/push-notification.service";

@Component({
selector: 'app-notifications-home',
Expand All @@ -34,12 +34,13 @@ export class NotificationsHomeComponent implements OnInit {
protected readonly trashIcon = mdiTrashCanOutline;
private readonly modalService = inject(NgbModal);
private readonly authState = inject(AuthState);
private readonly pushService = inject(PushNotificationService);

notifications: WritableSignal<Notification[]> = signal([]);


async ngOnInit() {
await this.loadNotifications();
await this.subscribeToNotifications();
}

async readNotification(id: number): Promise<void> {
Expand All @@ -65,6 +66,10 @@ export class NotificationsHomeComponent implements OnInit {
}
});
}

async subscribeToNotifications() {
await this.pushService.subscribeToNotifications();
}
}


0 comments on commit a964fd0

Please sign in to comment.