Compare commits

...
This repository has been archived on 2023-08-16. You can view files and clone it, but cannot push or open issues or pull requests.

10 commits

Author SHA1 Message Date
Unknown
f96dd0bfe2 WIP 2018-05-14 16:52:33 +01:00
Unknown
5fb33cffbf amended package lock json 2018-03-13 16:37:57 +00:00
Unknown
dc1e740889 Merge remote-tracking branch 'origin/theslby/pushapi' into theslby/pushapi 2018-03-13 16:37:03 +00:00
Unknown
9997cf7ee5 Merge remote-tracking branch 'origin/development' into theslby/pushapi 2018-03-13 16:33:59 +00:00
piratefinn
aa8763d5a6 Merge branch 'development' into theslby/pushapi 2018-03-07 13:09:14 +00:00
piratefinn
700ee3c155 fixes 2018-03-07 12:27:16 +00:00
Unknown
ff0fe0202f /pushapi: Auto stash before merge of "theslby/pushapi" and "development"
Push Frontend
2018-03-06 15:41:13 +00:00
Unknown
59cbc920c2 Merge branch 'development' into theslby/pushapi 2018-03-06 12:25:58 +00:00
Unknown
175c2decdd Merge remote-tracking branch 'origin/master' into theslby/pushapi 2018-01-19 13:40:17 +00:00
Unknown
6e0b059241 push stuff 2018-01-19 13:39:54 +00:00
22 changed files with 3014 additions and 2336 deletions

View file

@ -28,7 +28,8 @@
"prod": "environments/environment.prod.ts", "prod": "environments/environment.prod.ts",
"local": "environments/environment.local.ts", "local": "environments/environment.local.ts",
"ci": "environments/environment.ci.ts" "ci": "environments/environment.ci.ts"
} },
"serviceWorker": true
} }
], ],
"e2e": { "e2e": {

3
.gitignore vendored
View file

@ -48,6 +48,9 @@ testem.log
/src/environments/environment.ci.ts /src/environments/environment.ci.ts
/src/environments/environments.tar /src/environments/environments.tar
# dist
/dist/*
# ========================= # =========================
# Operating System Files # Operating System Files
# ========================= # =========================

4913
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -20,31 +20,41 @@
"dependencies": { "dependencies": {
"@agm/core": "1.0.0-beta.2", "@agm/core": "1.0.0-beta.2",
"@agm/js-marker-clusterer": "1.0.0-beta.2", "@agm/js-marker-clusterer": "1.0.0-beta.2",
"@angular/animations": "5.2.1",
"@angular/cdk": "5.2.3",
"@angular/common": "5.2.0", "@angular/common": "5.2.0",
"@angular/compiler": "5.2.0", "@angular/compiler": "5.2.0",
"@angular/core": "5.2.0", "@angular/core": "5.2.0",
"@angular/forms": "5.2.0", "@angular/forms": "5.2.0",
"@angular/http": "5.2.7",
"@angular/material": "5.2.3",
"@angular/platform-browser": "5.2.0", "@angular/platform-browser": "5.2.0",
"@angular/platform-browser-dynamic": "5.2.0", "@angular/platform-browser-dynamic": "5.2.0",
"@angular/router": "5.2.0", "@angular/router": "5.2.0",
"@angular/service-worker": "5.2.4",
"@angular/upgrade": "5.2.0", "@angular/upgrade": "5.2.0",
"@types/moment": "2.13.0", "@types/moment": "2.13.0",
"chart.js": "2.7.1", "chart.js": "2.7.1",
"core-js": "2.5.1", "core-js": "2.5.1",
"http-server": "0.11.1",
"js-marker-clusterer": "1.0.0", "js-marker-clusterer": "1.0.0",
"moment": "^2.21.0", "moment": "^2.21.0",
"ng2-charts": "1.6.0", "ng2-charts": "1.6.0",
"ng2-validation-manager": "0.5.3", "ng2-validation-manager": "0.5.3",
"ngx-bootstrap": "2.0.0-beta.8", "ngx-bootstrap": "2.0.0-beta.8",
"ngx-pagination": "3.0.3", "ngx-pagination": "3.0.3",
"node-snackbar": "0.1.9",
"rxjs": "5.5.6", "rxjs": "5.5.6",
"ts-helpers": "1.1.2", "ts-helpers": "1.1.2",
"web-push": "3.2.5",
"webpack": "3.8.1",
"webpack-dev-server": "3.1.0", "webpack-dev-server": "3.1.0",
"zone.js": "0.8.18" "zone.js": "0.8.18"
}, },
"devDependencies": { "devDependencies": {
"@angular/cli": "1.7.3", "@angular/cli": "1.7.3",
"@angular/compiler-cli": "5.2.0", "@angular/compiler-cli": "5.2.0",
"@types/google.analytics": "0.0.39",
"@types/jasmine": "2.8.2", "@types/jasmine": "2.8.2",
"@types/jasminewd2": "2.0.3", "@types/jasminewd2": "2.0.3",
"@types/node": "8.0.52", "@types/node": "8.0.52",
@ -60,6 +70,6 @@
"protractor": "5.2.0", "protractor": "5.2.0",
"ts-node": "3.3.0", "ts-node": "3.3.0",
"tslint": "5.8.0", "tslint": "5.8.0",
"typescript": "2.6.x" "typescript": "2.5.3"
} }
} }

View file

@ -1,5 +1,10 @@
import { HttpModule } from '@angular/http';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { LocationStrategy, HashLocationStrategy } from '@angular/common'; import { LocationStrategy, HashLocationStrategy } from '@angular/common';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
@ -38,15 +43,20 @@ import { P500Component } from './pages/500.component';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { DashboardModule } from './dashboard/dashboard.module'; import { DashboardModule } from './dashboard/dashboard.module';
@NgModule({ @NgModule({
imports: [ imports: [
BrowserAnimationsModule,
BrowserModule, BrowserModule,
HttpClientModule, HttpClientModule,
HttpModule,
NgxPaginationModule, NgxPaginationModule,
BsDropdownModule.forRoot(), BsDropdownModule.forRoot(),
TabsModule.forRoot(), TabsModule.forRoot(),
AuthModule, AuthModule,
DashboardModule, DashboardModule,
ServiceWorkerModule.register('/ngsw-worker.js', {enabled: environment.production}),
// Loaded last to allow for 404 catchall // Loaded last to allow for 404 catchall
AppRoutingModule, AppRoutingModule,
], ],
@ -76,6 +86,15 @@ import { DashboardModule } from './dashboard/dashboard.module';
useClass: HashLocationStrategy useClass: HashLocationStrategy
} }
], ],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
bootstrap: [ AppComponent ] bootstrap: [ AppComponent ]
}) })
export class AppModule { } export class AppModule {
constructor () {
if (environment.enableAnalytics) {
(<any>window).ga('create', environment.analyticsKey, 'auto');
}
}
}

16
src/app/config.service.ts Normal file
View file

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { environment } from './../environments/environment';
@Injectable()
export class ConfigService {
private _config:any = environment.config;
constructor() {
}
get(key: any) {
return this._config[key];
}
}

View file

@ -175,3 +175,16 @@
</div> </div>
</div><!--/.row--> </div><!--/.row-->
</div> </div>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Push Notification Settings</strong>
<small>Would you like to recieve notfications from companys about their latests offers?</small>
<div class="card-body">
<app-push></app-push>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,9 +1,10 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Router } from '@angular/router'; import {Router, NavigationEnd} from "@angular/router";
import { GraphWidget } from '../widgets/graph-widget.component'; import { GraphWidget } from '../widgets/graph-widget.component';
import { OrgBarSnippetComponent } from '../snippets/org-snippet-bar.component'; import { OrgBarSnippetComponent } from '../snippets/org-snippet-bar.component';
import { GraphPanel } from '../panels/graph-panel.component'; import { GraphPanel } from '../panels/graph-panel.component';
import { DataType } from '../shared/data-types.enum'; import { DataType } from '../shared/data-types.enum';
import { environment } from '../../environments/environment';
@Component({ @Component({
templateUrl: 'dashboard.component.html' templateUrl: 'dashboard.component.html'
@ -50,6 +51,14 @@ export class DashboardComponent {
dataType: DataType.currency, dataType: DataType.currency,
}, },
]; ];
constructor(private router: Router) {
constructor() { } if (environment.enableAnalytics) {
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
(<any>window).ga('set', 'page', event.urlAfterRedirects);
(<any>window).ga('send', 'pageview');
}
});
}
}
} }

View file

@ -18,6 +18,8 @@ import { FeedbackComponent } from './feedback.component';
import { TransactionLogComponent } from './transaction-log.component'; import { TransactionLogComponent } from './transaction-log.component';
import { CategoryMonthComponent } from './category-month.component'; import { CategoryMonthComponent } from './category-month.component';
import { PayrollLogComponent } from './payroll-log.component'; import { PayrollLogComponent } from './payroll-log.component';
import { OrgPushComponent } from './org-push.component';
import { LeaderboardComponent } from './leaderboard.component'; import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component'; import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component'; import { TrailMapComponent } from './trail-map.component';
@ -35,6 +37,10 @@ import { TransactionResultComponent } from '../shared/transaction-result.compone
import { PayrollResultComponent } from '../shared/payroll-result.component'; import { PayrollResultComponent } from '../shared/payroll-result.component';
import { LeaderboardResultComponent } from '../shared/leaderboard-result.component'; import { LeaderboardResultComponent } from '../shared/leaderboard-result.component';
import { MatButtonModule } from '@angular/material/button';
import { ConfigService } from '../config.service';
import { PushComponent } from '../push/push.component';
// API key env variable import // API key env variable import
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
@ -42,6 +48,7 @@ import { environment } from '../../environments/environment';
imports: [ imports: [
// Angular imports // Angular imports
CommonModule, CommonModule,
MatButtonModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
ChartsModule, ChartsModule,
@ -56,6 +63,7 @@ import { environment } from '../../environments/environment';
], ],
declarations: [ declarations: [
DashboardComponent, DashboardComponent,
PushComponent,
DashboardCustomerComponent, DashboardCustomerComponent,
AccountEditComponent, AccountEditComponent,
AddDataComponent, AddDataComponent,
@ -66,6 +74,7 @@ import { environment } from '../../environments/environment';
TransactionResultComponent, TransactionResultComponent,
PayrollLogComponent, PayrollLogComponent,
PayrollResultComponent, PayrollResultComponent,
OrgPushComponent,
LeaderboardComponent, LeaderboardComponent,
LeaderboardResultComponent, LeaderboardResultComponent,
MapComponent, MapComponent,
@ -80,6 +89,7 @@ import { environment } from '../../environments/environment';
providers: [ providers: [
CurrencyPipe, CurrencyPipe,
GoogleMapsAPIWrapper, GoogleMapsAPIWrapper,
ConfigService
], ],
}) })
export class DashboardModule { } export class DashboardModule { }

View file

@ -14,6 +14,7 @@ import { FeedbackComponent } from './feedback.component';
import { TransactionLogComponent } from './transaction-log.component'; import { TransactionLogComponent } from './transaction-log.component';
import { CategoryMonthComponent } from './category-month.component'; import { CategoryMonthComponent } from './category-month.component';
import { PayrollLogComponent } from './payroll-log.component'; import { PayrollLogComponent } from './payroll-log.component';
import { OrgPushComponent } from './org-push.component';
import { LeaderboardComponent } from './leaderboard.component'; import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component'; import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component'; import { TrailMapComponent } from './trail-map.component';
@ -80,6 +81,12 @@ const routes: Routes = [
data: { title: 'Payroll Log' }, data: { title: 'Payroll Log' },
canActivate: [OrgGuard], canActivate: [OrgGuard],
}, },
{
path: 'org-push',
component: OrgPushComponent,
data: { title: 'Push Notifications' },
canActivate: [OrgGuard],
},
{ {
path: 'feedback', path: 'feedback',
component: FeedbackComponent, component: FeedbackComponent,

View file

@ -0,0 +1,47 @@
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Push Notifcations</strong>
<small>Here you can send push notifications about sales to users</small>
</div>
<form class="form-horizontal" [formGroup]="pushForm" (ngSubmit)="onSubmit()">
<div class="card-block">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Title</strong></label>
<div class="col-md-9">
<input type="text" class="form-control" formControlName="title">
</div>
</div>
</div>
<div class="card-block">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Body</strong></label>
<div class="col-md-9">
<input type="text" class="form-control" formControlName="body">
</div>
</div>
</div>
<div class="card-block">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Icon</strong></label>
<div class="col-md-9">
<input type="text" class="form-control" formControlName="icon">
<span class="help-block">Enter URL to Icon you would like to use.</span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Personalise Notifcations</strong></label>
<div class="col-md-9">
<input type="checkbox" class="form-control" formControlName="personalised" (change)="toggleEditable($event)" >
<span class="help-block">Tick if you want to personalise notifications adding the users name.</span>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-sm btn-primary"><i class="fa fa-dot-circle-o"></i> Submit</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,47 @@
import { Component} from '@angular/core';
import { ApiService } from '../providers/api-service';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
@Component({
templateUrl: './org-push.component.html'
})
export class OrgPushComponent{
pushForm: FormGroup;
personalised = false;
constructor(
private formBuilder: FormBuilder,
private api: ApiService,
) {
this.pushForm = this.formBuilder.group({
title: ['', [Validators.required]],
body: ['', [Validators.required]],
icon: ['', [Validators.required]],
personalised: [true, [Validators.required]],
});
}
onSubmit(){
console.log(this.pushForm.value)
console.log(this.personalised)
this.api.sendNotification(this.pushForm.value)
.subscribe(
res => {
console.log('Message Sent', res)
},
err => {
console.log('Message Failed to send', err)
}
)
}
toggleEditable(event) {
if ( event.target.checked ) {
this.personalised = true;
}
}
}

View file

@ -20,7 +20,6 @@
</li> </li>
</ul> </ul>
</header> </header>
<div class="app-body"> <div class="app-body">
<div class="sidebar"> <div class="sidebar">
<nav class="sidebar-nav"> <nav class="sidebar-nav">
@ -97,6 +96,14 @@
</div> </div>
</a> </a>
</li> </li>
<li *ngIf="accountType == 'organisation'" class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/org-push']">
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-envelope-letter"></i></div>
<div class="col-10">Push Notifications</div>
</div>
</a>
</li>
</ul> </ul>
</nav> </nav>
</div> </div>
@ -116,7 +123,6 @@
</main> </main>
</div> </div>
<footer class="app-footer"> <footer class="app-footer">
<a href="http://www.peartrade.org" target="_blank">&copy; 2017 Pear Trading Ltd.</a> <a href="http://www.peartrade.org" target="_blank">&copy; 2017 Pear Trading Ltd.</a>
<span class="float-right">Powered by <a href="http://coreui.io">CoreUI</a></span> <span class="float-right">Powered by <a href="http://coreui.io">CoreUI</a></span>

View file

@ -240,6 +240,14 @@ export class ApiService {
); );
} }
public sendNotification(data) {
data.session_key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/push/org_send_notification',
data
);
}
// Deletes account details on logout // Deletes account details on logout
public removeUserInfo() { public removeUserInfo() {
@ -273,6 +281,32 @@ export class ApiService {
localStorage.getItem('email'); localStorage.getItem('email');
} }
//Push Api
public addSubscriber(data) {
const key = this.sessionKey;
console.log(data)
return this.http.post<any>(
this.apiUrl + '/subscription/add',
{
session_key : key,
data : data
}
);
}
public deleteSubscriber(data) {
const key = this.sessionKey;
console.log(data)
return this.http.post<any>(
this.apiUrl + '/subscription/delete',
{
session_key : key,
data : data
}
);
}
// Leaderboard Api // Leaderboard Api
public leaderboard_fetch( public leaderboard_fetch(

View file

@ -0,0 +1,3 @@
.button{
padding: 15px 32px;
}

View file

@ -0,0 +1,2 @@
<button mat-raised-button (click)="subscribeToPush()" [disabled]=subscribeButton>Subscribe To Pushs</button>
<button mat-raised-button (click)="unsubscribeFromPush()" [disabled]=unsubscribeButton>Unsubscribe From Push</button>

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PushComponent } from './push.component';
describe('PushComponent', () => {
let component: PushComponent;
let fixture: ComponentFixture<PushComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PushComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PushComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,91 @@
import { Component, OnInit } from '@angular/core';
import { ConfigService } from './../config.service';
import { SwPush } from '@angular/service-worker';
import { ApiService } from '../providers/api-service';
import "rxjs/Rx";
@Component({
selector: 'app-push',
templateUrl: './push.component.html',
styleUrls: ['./push.component.css']
})
export class PushComponent implements OnInit {
subscribeButton = false;
unsubscribeButton = true;
private VAPID_PUBLIC_KEY: string;
tweets = []
constructor(private api: ApiService, private configService: ConfigService, private swPush: SwPush) {
}
ngOnInit() {
this.VAPID_PUBLIC_KEY = this.configService.get('VAPID_PUBLIC_KEY')
}
subscribeToPush() {
// Requesting messaging service to subscribe current client (browser)
this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
})
.then(pushSubscription => {
// Passing subscription object to our backend
console.log(pushSubscription.endpoint)
console.log(pushSubscription.getKey)
this.api.addSubscriber(pushSubscription)
.subscribe(
res => {
console.log('[App] Add subscriber request answer', res)
this.subscribeButton = true;
this.unsubscribeButton = false;
},
err => {
console.log('[App] Add subscriber request failed', err)
}
)
})
.catch(err => {
console.error(err);
})
}
unsubscribeFromPush(){
this.subscribeButton = false;
this.unsubscribeButton = true;
// Get active subscription
this.swPush.subscription
.take(1)
.subscribe(pushSubscription => {
console.log('[App] pushSubscription', pushSubscription)
// Delete the subscription from the backend
this.api.deleteSubscriber(pushSubscription)
.subscribe(
res => {
console.log('[App] Delete subscriber request answer', res)
// Unsubscribe current client (browser)
pushSubscription.unsubscribe()
.then(success => {
console.log('[App] Unsubscription successful', success)
})
.catch(err => {
console.log('[App] Unsubscription failed', err)
})
},
err => {
console.log('[App] Delete subscription request failed', err)
}
)
})
}
}

View file

@ -5,6 +5,11 @@
export const environment = { export const environment = {
production: false, production: false,
apiUrl: 'https://dev.peartrade.org/api', apiUrl: 'http://localhost:3000/api',
mapApiKey: 'CHANGEME', mapApiKey: 'AIzaSyBhm0iaIGG0Ko5IsfZx-CpLt01YHkp4Y1w',
config: {
"VAPID_PUBLIC_KEY": "BMDZ6FANqsYRF9iGo3Ki0LdltGZZksgIFbgxBr_otO0H7jTFgcm3v2bGSgnVGJ5bidvLvuKStirfDNl4khVBiok"
},
enableAnalytics: false,
analyticsKey: ''
}; };

View file

@ -18,7 +18,6 @@
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
</head> </head>
<body class="app header-fixed sidebar-fixed"> <body class="app header-fixed sidebar-fixed">
<!-- Enable bootstrap 4 theme --> <!-- Enable bootstrap 4 theme -->
<script>window.__theme = 'bs4';</script> <script>window.__theme = 'bs4';</script>
@ -123,6 +122,15 @@
<div class="sk-cube3 sk-cube"></div> <div class="sk-cube3 sk-cube"></div>
</div> </div>
</app-root> </app-root>
<app-root></app-root>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-119198369-1', 'auto');
</script>
</body> </body>
</html> </html>

31
src/ngsw-config.json Normal file
View file

@ -0,0 +1,31 @@
{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
]
}
}],
"push": {
"showNotifications": true,
"backgroundOnly": false
}
}

26
src/test.js Normal file
View file

@ -0,0 +1,26 @@
const webpush = require('web-push');
const options = {
vapidDetails: {
subject: 'http://127.0.0.1:8080',
publicKey: 'BMDZ6FANqsYRF9iGo3Ki0LdltGZZksgIFbgxBr_otO0H7jTFgcm3v2bGSgnVGJ5bidvLvuKStirfDNl4khVBiok',
privateKey: 'tZacTzAIA5W-B19SsaQ-4KGWrKPAAqth5HQfJsjyHYs'
},
TTL: 5000
}
// NgPushRegistration-Object from the browser
const pushSubscription = {"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/gAAAAABanrgBKoC1etG5UeixhypVGQhGJoV5VtY72jIxx7rp0Bh3O4LZ4OmeYvGQgl0RxYD0ENNIzzMYTe0pgaynEOysvkyo4ybM6mHu_xFYrc9imf2F7lIuBCILCEjANG1gDoAWVRmBEMk0ZlYE4mq0KcoBjxwQDKTZsM4gITPmJdFxaCWRSls","keys":{"auth":"0uFlXtWtJH4arEGej3L_dQ","p256dh":"BMi13v5aFEgFgTwlSVDBv4uaxLOAdZ50U_-ITaP_-Brt6X3WN9gsl4G4oxhxTIj25zECdLARItXTJZ-Sof-O5bU"}};
const payload = JSON.stringify({
notification: {
title: '*TITLE*',
body: '*BODY*',
}
});
webpush.sendNotification(
pushSubscription,
payload,
options
);