mirror of
https://github.com/fergalmoran/podnoms.git
synced 2026-01-04 07:35:02 +00:00
Service worker push working
This commit is contained in:
@@ -7,7 +7,12 @@
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": ["assets", "favicon.ico"],
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico",
|
||||
"firebase-messaging-sw.js",
|
||||
"manifest.json"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
@@ -15,7 +20,6 @@
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"serviceWorker": true,
|
||||
"styles": [
|
||||
"../node_modules/font-awesome/css/font-awesome.css",
|
||||
"../node_modules/simple-line-icons/css/simple-line-icons.css",
|
||||
@@ -58,6 +62,5 @@
|
||||
"defaults": {
|
||||
"styleExt": "css",
|
||||
"component": {}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
1042
client/package-lock.json
generated
1042
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,6 @@
|
||||
"@angular/platform-browser": "^5.2.7",
|
||||
"@angular/platform-browser-dynamic": "^5.2.7",
|
||||
"@angular/router": "^5.2.7",
|
||||
"@angular/service-worker": "^5.2.9",
|
||||
"@aspnet/signalr": "^1.0.0-preview3-30392",
|
||||
"@ngrx/effects": "^5.1.0",
|
||||
"@ngrx/store": "^5.1.0",
|
||||
@@ -32,12 +31,14 @@
|
||||
"@qontu/ngx-inline-editor": "^0.2.0-alpha.12",
|
||||
"angular2-jwt": "^0.2.3",
|
||||
"angular2-moment": "^1.8.0",
|
||||
"angularfire2": "^5.0.0-rc.6",
|
||||
"applicationinsights-js": "^1.0.15",
|
||||
"auth0": "^2.9.1",
|
||||
"auth0-lock": "^11.4.0",
|
||||
"bootstrap": "4.0.0",
|
||||
"core-js": "^2.5.3",
|
||||
"dropzone": "^5.3.0",
|
||||
"firebase": "^4.12.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jquery": "^3.3.1",
|
||||
"lodash": "^4.17.5",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { GlobalsService } from './services/globals.service';
|
||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ToastyService } from 'ng2-toasty';
|
||||
import { AuthService } from 'app/services/auth.service';
|
||||
import { AppInsightsService } from 'app/services/app-insights.service';
|
||||
import { SignalRService } from 'app/services/signalr.service';
|
||||
import { ProfileService } from './services/profile.service';
|
||||
import { PushNotificationsService } from 'app/services/push-notifications.service';
|
||||
import { MessagingService } from 'app/services/messaging.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -15,9 +16,10 @@ import { PushNotificationsService } from 'app/services/push-notifications.servic
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(
|
||||
private _authService: AuthService,
|
||||
private _toastyService: ToastyService,
|
||||
private _signalrService: SignalRService,
|
||||
private _profileService: ProfileService,
|
||||
private _pushNotifications: PushNotificationsService,
|
||||
private _messagingService: MessagingService,
|
||||
_appInsights: AppInsightsService
|
||||
) {
|
||||
_authService.handleAuthentication();
|
||||
@@ -33,6 +35,8 @@ export class AppComponent implements OnInit {
|
||||
if (this.loggedIn()) {
|
||||
const user = this._profileService.getProfile().subscribe(u => {
|
||||
if (u) {
|
||||
this._messagingService.getPermission();
|
||||
this._messagingService.receiveMessage();
|
||||
const chatterChannel = `${u.uid}_chatter`;
|
||||
this._signalrService
|
||||
.init('chatter')
|
||||
@@ -45,10 +49,7 @@ export class AppComponent implements OnInit {
|
||||
this._signalrService.connection.on(
|
||||
chatterChannel,
|
||||
result => {
|
||||
this._pushNotifications.createNotification(
|
||||
'PodNoms',
|
||||
result
|
||||
);
|
||||
this._toastyService.info(result);
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
@@ -9,6 +9,9 @@ import { HttpModule, Http, RequestOptions } from '@angular/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ToastyModule } from 'ng2-toasty';
|
||||
import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
|
||||
import { AngularFireDatabaseModule } from 'angularfire2/database';
|
||||
import { AngularFireAuthModule } from 'angularfire2/auth';
|
||||
import { AngularFireModule } from 'angularfire2';
|
||||
|
||||
import { ModalModule } from 'ngx-bootstrap/modal';
|
||||
import { AuthGuard } from './services/auth.guard';
|
||||
@@ -23,7 +26,6 @@ import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
import { ClipboardModule } from 'ngx-clipboard';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { HomeComponent } from './components/home/home.component';
|
||||
@@ -51,9 +53,9 @@ import { ProfileComponent } from './components/profile/profile.component';
|
||||
import { AboutComponent } from './components/about/about.component';
|
||||
import { FooterComponent } from './components/footer/footer.component';
|
||||
import { JobsService } from 'app/services/jobs.service';
|
||||
import { PushService } from 'app/services/push.service';
|
||||
import { PushRegistrationService } from 'app/services/push-registration.service';
|
||||
import { AppInsightsService } from 'app/services/app-insights.service';
|
||||
import { PushNotificationsService } from './services/push-notifications.service';
|
||||
import { MessagingService } from './services/messaging.service';
|
||||
|
||||
import { environment } from 'environments/environment';
|
||||
|
||||
@@ -95,7 +97,17 @@ export function authHttpServiceFactory(http: Http, options: RequestOptions) {
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
ServiceWorkerModule.register('/ngsw-worker.js', {enabled: environment.production}),
|
||||
AngularFireModule.initializeApp({
|
||||
apiKey: 'AIzaSyAaIm8LTB0ZgJ-g7RXEjtVa1EOQB381QLI',
|
||||
authDomain: 'podnoms-797e3.firebaseapp.com',
|
||||
databaseURL: 'https://podnoms-797e3.firebaseio.com',
|
||||
projectId: 'podnoms-797e3',
|
||||
storageBucket: 'podnoms-797e3.appspot.com',
|
||||
messagingSenderId: '777042345082'
|
||||
}),
|
||||
AngularFireDatabaseModule,
|
||||
AngularFireAuthModule,
|
||||
|
||||
AppRoutingModule,
|
||||
HttpModule,
|
||||
FormsModule,
|
||||
@@ -130,9 +142,9 @@ export function authHttpServiceFactory(http: Http, options: RequestOptions) {
|
||||
ProfileService,
|
||||
PodcastService,
|
||||
ImageService,
|
||||
PushService,
|
||||
PushRegistrationService,
|
||||
DebugService,
|
||||
PushNotificationsService,
|
||||
MessagingService,
|
||||
ChatterService,
|
||||
AppInsightsService,
|
||||
JobsService,
|
||||
|
||||
@@ -7,35 +7,31 @@
|
||||
Desktop Notifications
|
||||
</div>
|
||||
<div class="block-content">
|
||||
<input type="text" id="text" [(ngModel)]="notificationMessage">
|
||||
<button class="btn btn-primary" (click)="sendDesktopNotification()">Send Desktop Notification</button>
|
||||
<button class="btn btn-danger" (click)="subscribeToServerPush()">Subscribe to server push</button>
|
||||
<button class="btn btn-danger" (click)="sendServerPush()">Send server push</button>
|
||||
<br />
|
||||
<div id="realtime-results" *ngIf="message$ | async as message">
|
||||
<h5>{{ message.notification.title}}</h5>
|
||||
<img [src]="message.notification.icon" width="100px;">
|
||||
<p>{{ message.notification.body}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="block">
|
||||
<div class="block-header">
|
||||
Realtime
|
||||
</div>
|
||||
<div class="block-header">Debug Info</div>
|
||||
<div class="block-content">
|
||||
<input type="text" id="text" [(ngModel)]="realtimeMessage">
|
||||
<button class="btn btn-primary" (click)="sendMessage()">Send Realtime</button>
|
||||
<ul class="list-unstyled">
|
||||
<li *ngFor="let message of messagesReceived">
|
||||
{{message}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="block">
|
||||
<div class="block-header">
|
||||
Chatter
|
||||
</div>
|
||||
<div class="block-content">
|
||||
<button class="btn btn-primary" (click)="sendChatter()">Send Chatter</button>
|
||||
<div class="row">
|
||||
<pre [innerHtml]="(debugInfo$ | async) | prettyprint">
|
||||
</pre>
|
||||
</div>
|
||||
<div class="row">
|
||||
API Host: {{apiHost}}
|
||||
<br /> SignalR Host: {{signalrHost}}
|
||||
<br /> Ping: {{pingPong}} </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,20 +48,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="block">
|
||||
<div class="block-header">Debug Info</div>
|
||||
<div class="block-content">
|
||||
<div class="row">
|
||||
<pre [innerHtml]="(debugInfo$ | async) | prettyprint">
|
||||
</pre>
|
||||
</div>
|
||||
<div class="row">
|
||||
API Host: {{apiHost}}
|
||||
<br /> SignalR Host: {{signalrHost}}
|
||||
<br /> Ping: {{pingPong}} </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,8 @@ import { DebugService } from 'app/services/debug.service';
|
||||
import { environment } from 'environments/environment';
|
||||
import { JobsService } from 'app/services/jobs.service';
|
||||
import { ChatterService } from 'app/services/chatter.service';
|
||||
import { PushNotificationsService } from 'app/services/push-notifications.service';
|
||||
import { MessagingService } from 'app/services/messaging.service';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
@Component({
|
||||
selector: 'app-debug',
|
||||
@@ -13,10 +14,7 @@ import { PushNotificationsService } from 'app/services/push-notifications.servic
|
||||
styleUrls: ['./debug.component.css']
|
||||
})
|
||||
export class DebugComponent implements OnInit {
|
||||
realtimeMessage: string;
|
||||
notificationMessage: string;
|
||||
|
||||
messagesReceived: string[] = [];
|
||||
message$: BehaviorSubject<any>;
|
||||
|
||||
debugInfo$: Observable<string>;
|
||||
apiHost = environment.API_HOST;
|
||||
@@ -27,52 +25,24 @@ export class DebugComponent implements OnInit {
|
||||
private _debugService: DebugService,
|
||||
private _chatterService: ChatterService,
|
||||
private _jobsService: JobsService,
|
||||
private _pushNotifications: PushNotificationsService,
|
||||
private _pushNotifications: MessagingService,
|
||||
private _signalrService: SignalRService
|
||||
) {}
|
||||
ngOnInit() {
|
||||
// this._signalrService
|
||||
// .init(`${environment.SIGNALR_HOST}hubs/debug`)
|
||||
// .then(() => {
|
||||
// this._signalrService.connection.on('Send', data => {
|
||||
// console.log('DebugService', 'signalr', data);
|
||||
// this.messagesReceived.push(data);
|
||||
// this.realtimeMessage = '';
|
||||
// });
|
||||
// this.debugInfo$ = this._debugService.getDebugInfo();
|
||||
// })
|
||||
// .catch(err =>
|
||||
// console.error('debug.component.ts', '_signalrService.init', err)
|
||||
// );
|
||||
this._debugService.ping().subscribe(r => (this.pingPong = r));
|
||||
}
|
||||
|
||||
sendMessage() {
|
||||
subscribeToServerPush() {
|
||||
this._pushNotifications.getPermission();
|
||||
this._pushNotifications.receiveMessage();
|
||||
this.message$ = this._pushNotifications.currentMessage;
|
||||
}
|
||||
sendServerPush() {
|
||||
this._debugService
|
||||
.sendRealtime(this.realtimeMessage)
|
||||
.subscribe(r => console.log(r));
|
||||
}
|
||||
doSomething() {
|
||||
alert('doSomething was did');
|
||||
}
|
||||
sendChatter() {
|
||||
this._chatterService.ping('Pong').subscribe(r => {
|
||||
this._pushNotifications.createNotification('PodNoms', r);
|
||||
});
|
||||
}
|
||||
sendDesktopNotification() {
|
||||
console.log(
|
||||
'debug.component',
|
||||
'sendDesktopFunction',
|
||||
this.notificationMessage
|
||||
);
|
||||
this._pushNotifications.createNotification(
|
||||
'PodNoms',
|
||||
this.notificationMessage
|
||||
);
|
||||
}
|
||||
subscribeToServerPush(){
|
||||
this._pushNotifications.subscribeToServerPush();
|
||||
.sendPush()
|
||||
.subscribe(r =>
|
||||
console.log('debug.component', 'sendServerPush', r)
|
||||
);
|
||||
}
|
||||
processOrphans() {
|
||||
this._jobsService
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { Component } from '@angular/core';
|
||||
import { PodcastModel, PodcastEntryModel } from 'app/models/podcasts.models';
|
||||
import { ToastyService } from 'ng2-toasty';
|
||||
import { PodcastService } from 'app/services/podcast.service';
|
||||
import { MessagingService } from 'app/services/messaging.service';
|
||||
import { AppComponent } from 'app/app.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ApplicationState } from 'app/store';
|
||||
@@ -13,8 +15,6 @@ import { UpdateAction, AddAction } from 'app/actions/entries.actions';
|
||||
import * as fromPodcast from 'app/reducers';
|
||||
import * as fromPodcastActions from 'app/actions/podcast.actions';
|
||||
import * as fromEntriesActions from 'app/actions/entries.actions';
|
||||
import { PodcastService } from 'app/services/podcast.service';
|
||||
import { PushNotificationsService } from 'app/services/push-notifications.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-podcast',
|
||||
@@ -102,12 +102,13 @@ export class PodcastComponent {
|
||||
}
|
||||
processPlaylist() {
|
||||
if (this.pendingEntry) {
|
||||
this._service.addPlaylist(this.pendingEntry)
|
||||
.subscribe(e => {
|
||||
if (e) {
|
||||
this._toasty.info('Playlist added, check back here (and on your device) for new episodes');
|
||||
}
|
||||
});
|
||||
this._service.addPlaylist(this.pendingEntry).subscribe(e => {
|
||||
if (e) {
|
||||
this._toasty.info(
|
||||
'Playlist added, check back here (and on your device) for new episodes'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
dismissPlaylist() {
|
||||
|
||||
@@ -16,6 +16,9 @@ export class DebugService {
|
||||
}
|
||||
|
||||
ping(): Observable<string> {
|
||||
return this._http.get(environment.API_HOST + '/debug/ping').map(r => r.text());
|
||||
return this._http.get(environment.API_HOST + '/ping').map(r => r.text());
|
||||
}
|
||||
sendPush(): Observable<string>{
|
||||
return this._http.get(environment.API_HOST + '/debug/serverpush').map(r => r.text());
|
||||
}
|
||||
}
|
||||
|
||||
65
client/src/app/services/messaging.service.ts
Normal file
65
client/src/app/services/messaging.service.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AngularFireDatabase } from 'angularfire2/database';
|
||||
import { AngularFireAuth } from 'angularfire2/auth';
|
||||
|
||||
import * as firebase from 'firebase';
|
||||
import 'rxjs/add/operator/take';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { PushRegistrationService } from 'app/services/push-registration.service';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingService {
|
||||
messaging = firebase.messaging();
|
||||
currentMessage = new BehaviorSubject(null);
|
||||
|
||||
constructor(
|
||||
private db: AngularFireDatabase,
|
||||
private afAuth: AngularFireAuth,
|
||||
private _pushRegistrationServer: PushRegistrationService
|
||||
) {
|
||||
this.messaging.usePublicVapidKey(
|
||||
'BKyhUqIVZLauKNA-DXPXbIVLj5XiWurHbRV_0Rd3BOjY5cU9GOrd5ptXVJ2CNExxdveKYzZevrep2CflKeqkyqo'
|
||||
);
|
||||
}
|
||||
|
||||
private updateToken(token) {
|
||||
this.afAuth.authState.take(1).subscribe(user => {
|
||||
if (!user) return;
|
||||
|
||||
const data = { [user.uid]: token };
|
||||
this.db.object('fcmTokens/').update(data);
|
||||
});
|
||||
const registration = {
|
||||
endpoint: token,
|
||||
keys: {
|
||||
p256dh: token
|
||||
}
|
||||
};
|
||||
this._pushRegistrationServer.addSubscriber(registration)
|
||||
.subscribe(e => console.log('messaging.service', 'updateToken', 'addSubscriber', e));
|
||||
}
|
||||
|
||||
public getPermission() {
|
||||
this.messaging
|
||||
.requestPermission()
|
||||
.then(() => {
|
||||
console.log('Notification permission granted.');
|
||||
const token = this.messaging.getToken();
|
||||
return token;
|
||||
})
|
||||
.then(token => {
|
||||
console.log(token);
|
||||
this.updateToken(token);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Unable to get permission to notify', err);
|
||||
});
|
||||
}
|
||||
|
||||
receiveMessage() {
|
||||
this.messaging.onMessage(payload => {
|
||||
console.log('Message received', payload);
|
||||
this.currentMessage.next(payload);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SwPush } from '@angular/service-worker';
|
||||
import { PushService } from 'app/services/push.service';
|
||||
|
||||
export type Permission = 'denied' | 'granted' | 'default';
|
||||
|
||||
@Injectable()
|
||||
export class PushNotificationsService {
|
||||
permission: Permission;
|
||||
pushSupported: boolean = 'serviceWorker' in navigator && 'PushManager' in window;
|
||||
vapidPublicKey: string = 'BKrxiuL9AJo5rrKEzMQIOvIacDnbg6JI8hJiT00JfKytur395xL8CROvR_zC2XM9f5oxGiMLxpUyjgLWlEPeSbU';
|
||||
|
||||
constructor(private _pushService: PushService, private _pushServiceWorker: SwPush) {}
|
||||
|
||||
private _urlBase64ToUint8Array(base64String) {
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/\-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
requestPermissions() {
|
||||
if ('Notification' in window) {
|
||||
Notification.requestPermission((status: any) => {
|
||||
console.log(
|
||||
'push-notifications.service',
|
||||
'requestPermissions',
|
||||
status
|
||||
);
|
||||
this.permission = status;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isSupported() {
|
||||
return 'Notification' in window && this.permission == 'granted';
|
||||
}
|
||||
|
||||
subscribeToServerPush() {
|
||||
console.log('PushNotificationsService', 'subscribeToServerPush', this.vapidPublicKey);
|
||||
this._pushServiceWorker.requestSubscription({
|
||||
serverPublicKey: this.vapidPublicKey
|
||||
}).then(pushSubscription => {
|
||||
console.log('PushNotificationsService', 'subscribeToServerPush', pushSubscription);
|
||||
this._pushService.addSubscriber(pushSubscription)
|
||||
.subscribe(res => {
|
||||
console.log('PushNotificationsService', 'subscribeToServerPush', res);
|
||||
}, err => {
|
||||
console.error('PushNotificationsService', 'subscribeToServerPush', err);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('PushNotificationsService', 'subscribeToServerPush', err);
|
||||
});
|
||||
}
|
||||
|
||||
createNotification(subject: string, text: string) {
|
||||
console.log('PushNotificationsService', 'createNotification', subject, text);
|
||||
if (this.isSupported()) {
|
||||
const options = {
|
||||
body: text,
|
||||
icon: 'https://podnoms.com/assets/img/logo-icon.png'
|
||||
};
|
||||
const n = new Notification(subject, options);
|
||||
} else {
|
||||
console.error(
|
||||
'push-notifications.service',
|
||||
'createNotification',
|
||||
'Notifications are not supported'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'rxjs/add/observable/throw';
|
||||
import { environment } from 'environments/environment';
|
||||
|
||||
@Injectable()
|
||||
export class PushService {
|
||||
export class PushRegistrationService {
|
||||
private API_URL: string;
|
||||
constructor(private http: AuthHttp) {
|
||||
this.API_URL = environment.API_HOST;
|
||||
@@ -27,14 +27,11 @@ export class PushService {
|
||||
}
|
||||
|
||||
addSubscriber(subscription) {
|
||||
|
||||
const url = `${this.API_URL}/webpush/subscribe`;
|
||||
console.log('[Push Service] Adding subscriber')
|
||||
|
||||
const url = `${this.API_URL}/webpush/subscribe`;
|
||||
return this.http
|
||||
.post(url, subscription)
|
||||
.catch(this.handleError);
|
||||
|
||||
}
|
||||
|
||||
deleteSubscriber(subscription) {
|
||||
@@ -1,10 +1,13 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
API_HOST: 'https://dev.podnoms.com:5001',
|
||||
SIGNALR_HOST: 'https://dev.podnoms.com:5001/',
|
||||
AUTH0_REDIRECT_URL: 'http://dev.podnoms.com:4200/callback',
|
||||
BASE_URL: 'http://dev.podnoms.com:4200/',
|
||||
API_HOST: 'http://localhost:5000',
|
||||
SIGNALR_HOST: 'http://localhost:5000/',
|
||||
AUTH0_REDIRECT_URL: 'http://localhost:4200/callback',
|
||||
BASE_URL: 'http://localhost:4200/',
|
||||
appInsights: {
|
||||
instrumentationKey: '020b002a-bd3d-4b25-8a74-cab16fd39dfc'
|
||||
},
|
||||
messaging: {
|
||||
endpoint: 'https://fcm.googleapis.com/fcm/send'
|
||||
}
|
||||
};
|
||||
|
||||
8
client/src/firebase-messaging-sw.js
Normal file
8
client/src/firebase-messaging-sw.js
Normal file
@@ -0,0 +1,8 @@
|
||||
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
|
||||
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');
|
||||
|
||||
firebase.initializeApp({
|
||||
messagingSenderId: '777042345082'
|
||||
});
|
||||
|
||||
const messaging = firebase.messaging();
|
||||
@@ -5,10 +5,10 @@
|
||||
<!-- Google Analytics -->
|
||||
<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)
|
||||
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-115328404-1', 'auto');
|
||||
@@ -18,22 +18,14 @@
|
||||
<meta charset="utf-8">
|
||||
<title>PodNoms.Web</title>
|
||||
<base href="/">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1">
|
||||
<link rel="icon"
|
||||
type="image/x-icon"
|
||||
compiler-c
|
||||
href="favicon.ico">
|
||||
<meta property="og:url"
|
||||
content="https://podnoms.com" />
|
||||
<meta property="og:type"
|
||||
content="website" />
|
||||
<meta property="og:title"
|
||||
content="PodNoms" />
|
||||
<meta property="og:description"
|
||||
content="Robot Powered Podcasts" />
|
||||
<meta property="og:image"
|
||||
content="https://podnomscdn.blob.core.windows.net/static/images/robothand.jpg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" compiler-c href="favicon.ico">
|
||||
<meta property="og:url" content="https://podnoms.com" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="PodNoms" />
|
||||
<meta property="og:description" content="Robot Powered Podcasts" />
|
||||
<meta property="og:image" content="https://podnomscdn.blob.core.windows.net/static/images/robothand.jpg" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
6
client/src/manifest.json
Normal file
6
client/src/manifest.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"short_name": "FirebaseMessenger",
|
||||
"name": "Angular4 + Firebase Starter App",
|
||||
"start_url": "/?utm_source=homescreen",
|
||||
"gcm_sender_id": "103953800507"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"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/**"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
31
server/Controllers/AuthController.cs
Normal file
31
server/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PodNoms.Api.Models;
|
||||
using PodNoms.Api.Persistence;
|
||||
|
||||
namespace PodNoms.Api.Controllers {
|
||||
[Authorize]
|
||||
public class AuthController : Controller {
|
||||
protected IUserRepository _userRepository { get; }
|
||||
|
||||
public AuthController(IUserRepository repository) {
|
||||
this._userRepository = repository;
|
||||
}
|
||||
protected async Task<User> GetUserAsync() {
|
||||
var identifier = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
|
||||
var user = await this._userRepository.GetAsync(identifier);
|
||||
return user;
|
||||
}
|
||||
protected async Task<string> GetUserUidAsync() {
|
||||
var user = await GetUserAsync();
|
||||
return user.Uid;
|
||||
}
|
||||
protected async Task<int> GetUserIdAsync() {
|
||||
var user = await GetUserAsync();
|
||||
return user.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,17 +12,20 @@ using PodNoms.Api.Persistence;
|
||||
using PodNoms.Api.Services.Downloader;
|
||||
using PodNoms.Api.Services.Hubs;
|
||||
using PodNoms.Api.Services.Jobs;
|
||||
using PodNoms.Api.Services.Push;
|
||||
using PodNoms.Api.Services.Realtime;
|
||||
using WebPush = Lib.Net.Http.WebPush;
|
||||
|
||||
namespace PodNoms.Api.Controllers {
|
||||
[Route("[controller]")]
|
||||
public class DebugController : Controller {
|
||||
public class DebugController : AuthController {
|
||||
private readonly StorageSettings _storageSettings;
|
||||
private readonly AudioFileStorageSettings _audioFileStorageSettings;
|
||||
private readonly ApplicationsSettings _applicationsSettings;
|
||||
private readonly ImageFileStorageSettings _imageFileStorageSettings;
|
||||
private readonly HubLifetimeManager<DebugHub> _hubManager;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IPushSubscriptionStore _subscriptionStore;
|
||||
private readonly IPushNotificationService _notificationService;
|
||||
|
||||
public AppSettings _appSettings { get; }
|
||||
|
||||
@@ -30,14 +33,17 @@ namespace PodNoms.Api.Controllers {
|
||||
HubLifetimeManager<DebugHub> hubManager, IUserRepository userRepository,
|
||||
IOptions<ApplicationsSettings> applicationsSettings,
|
||||
IOptions<AudioFileStorageSettings> audioFileStorageSettings,
|
||||
IOptions<ImageFileStorageSettings> imageFileStorageSettings) {
|
||||
IOptions<ImageFileStorageSettings> imageFileStorageSettings,
|
||||
IPushSubscriptionStore subscriptionStore,
|
||||
IPushNotificationService notificationService) : base(userRepository) {
|
||||
this._appSettings = appSettings.Value;
|
||||
this._storageSettings = settings.Value;
|
||||
this._applicationsSettings = applicationsSettings.Value;
|
||||
this._audioFileStorageSettings = audioFileStorageSettings.Value;
|
||||
this._imageFileStorageSettings = imageFileStorageSettings.Value;
|
||||
this._hubManager = hubManager;
|
||||
this._userRepository = userRepository;
|
||||
this._subscriptionStore = subscriptionStore;
|
||||
this._notificationService = notificationService;
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
@@ -55,16 +61,6 @@ namespace PodNoms.Api.Controllers {
|
||||
return new OkObjectResult(config);
|
||||
}
|
||||
|
||||
[HttpGet("ping")]
|
||||
public string Ping() {
|
||||
return "Pong";
|
||||
}
|
||||
|
||||
[HttpGet("clear")]
|
||||
public IActionResult Clear() {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("realtime")]
|
||||
public async Task<IActionResult> Realtime([FromBody] string message) {
|
||||
@@ -72,5 +68,20 @@ namespace PodNoms.Api.Controllers {
|
||||
await _hubManager.SendAllAsync("Send", new string[] { $"All: {message}" });
|
||||
return Ok(message);
|
||||
}
|
||||
[Authorize]
|
||||
[HttpGet("serverpush")]
|
||||
public async Task<string> ServerPush() {
|
||||
WebPush.PushMessage pushMessage = new WebPush.PushMessage("Argle Bargle, Foo Ferra") {
|
||||
Topic = "Debug",
|
||||
Urgency = WebPush.PushMessageUrgency.Normal
|
||||
};
|
||||
var uid = await GetUserUidAsync();
|
||||
|
||||
await _subscriptionStore.ForEachSubscriptionAsync(uid, (subscription) => {
|
||||
_notificationService.SendNotificationAsync(subscription, pushMessage);
|
||||
});
|
||||
|
||||
return "Hello Sailor!";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@ using PodNoms.Api.Models;
|
||||
using PodNoms.Api.Models.ViewModels;
|
||||
using PodNoms.Api.Persistence;
|
||||
using PodNoms.Api.Services;
|
||||
using PodNoms.Api.Services.Jobs;
|
||||
using PodNoms.Api.Services.Processor;
|
||||
using PodNoms.Api.Services.Storage;
|
||||
|
||||
namespace PodNoms.Api.Controllers {
|
||||
|
||||
[Route("[controller]")]
|
||||
public class EntryController : Controller {
|
||||
public class EntryController : AuthController {
|
||||
private readonly IPodcastRepository _podcastRepository;
|
||||
private readonly IEntryRepository _repository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
@@ -29,10 +30,11 @@ namespace PodNoms.Api.Controllers {
|
||||
private readonly StorageSettings _storageSettings;
|
||||
|
||||
public EntryController(IEntryRepository repository,
|
||||
IUserRepository userRepository,
|
||||
IPodcastRepository podcastRepository,
|
||||
IUnitOfWork unitOfWork, IMapper mapper, IOptions<StorageSettings> storageSettings,
|
||||
IOptions<AudioFileStorageSettings> audioFileStorageSettings,
|
||||
IUrlProcessService processor, ILoggerFactory logger) {
|
||||
IUrlProcessService processor, ILoggerFactory logger) : base(userRepository) {
|
||||
this._logger = logger.CreateLogger<EntryController>();
|
||||
this._podcastRepository = podcastRepository;
|
||||
this._repository = repository;
|
||||
@@ -47,8 +49,11 @@ namespace PodNoms.Api.Controllers {
|
||||
try {
|
||||
var extractJobId = BackgroundJob.Enqueue<IUrlProcessService>(
|
||||
service => service.DownloadAudio(entry.Id));
|
||||
var upload = BackgroundJob.ContinueWith<IAudioUploadProcessService>(
|
||||
var uploadJobId = BackgroundJob.ContinueWith<IAudioUploadProcessService>(
|
||||
extractJobId, service => service.UploadAudio(entry.Id, entry.AudioUrl));
|
||||
var notify = BackgroundJob.ContinueWith<INotifyJobCompleteService>(
|
||||
uploadJobId, service => service.NotifyUser(entry.Podcast.User.Uid, "PodNoms", $"{entry.Title} has finished processing",
|
||||
entry.Podcast.ImageUrl));
|
||||
} catch (InvalidOperationException ex) {
|
||||
_logger.LogError($"Failed submitting job to processor\n{ex.Message}");
|
||||
entry.ProcessingStatus = ProcessingStatus.Failed;
|
||||
|
||||
10
server/Controllers/PingController.cs
Normal file
10
server/Controllers/PingController.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace server.Controllers {
|
||||
public class PingController : Controller {
|
||||
[HttpGet]
|
||||
public string Get() {
|
||||
return "Pong";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,10 @@ using PodNoms.Api.Services.Auth;
|
||||
using PodNoms.Api.Services.Processor;
|
||||
using PodNoms.Api.Utils.Extensions;
|
||||
#endregion
|
||||
namespace PodNoms.Api.Controllers
|
||||
{
|
||||
namespace PodNoms.Api.Controllers {
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
public class PodcastController : Controller
|
||||
{
|
||||
public class PodcastController : Controller {
|
||||
private readonly IPodcastRepository _repository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IOptions<AppSettings> _settings;
|
||||
@@ -29,8 +27,7 @@ namespace PodNoms.Api.Controllers
|
||||
private readonly IUnitOfWork _uow;
|
||||
|
||||
public PodcastController(IPodcastRepository repository, IUserRepository userRepository,
|
||||
IOptions<AppSettings> options, IMapper mapper, IUnitOfWork unitOfWork)
|
||||
{
|
||||
IOptions<AppSettings> options, IMapper mapper, IUnitOfWork unitOfWork) {
|
||||
this._uow = unitOfWork;
|
||||
this._repository = repository;
|
||||
this._userRepository = userRepository;
|
||||
@@ -39,11 +36,9 @@ namespace PodNoms.Api.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<PodcastViewModel>> Get()
|
||||
{
|
||||
public async Task<IEnumerable<PodcastViewModel>> Get() {
|
||||
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
|
||||
if (!string.IsNullOrEmpty(email))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(email)) {
|
||||
var podcasts = await _repository.GetAllAsync(email);
|
||||
var ret = _mapper.Map<List<Podcast>, List<PodcastViewModel>>(podcasts.ToList());
|
||||
return ret;
|
||||
@@ -52,11 +47,9 @@ namespace PodNoms.Api.Controllers
|
||||
}
|
||||
|
||||
[HttpGet("{slug}")]
|
||||
public async Task<IActionResult> GetBySlug(string slug)
|
||||
{
|
||||
public async Task<IActionResult> GetBySlug(string slug) {
|
||||
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
|
||||
if (!string.IsNullOrEmpty(email))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(email)) {
|
||||
var podcast = await _repository.GetAsync(email, slug);
|
||||
if (podcast == null)
|
||||
return NotFound();
|
||||
@@ -66,15 +59,13 @@ namespace PodNoms.Api.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromBody] PodcastViewModel vm)
|
||||
{
|
||||
public async Task<IActionResult> Post([FromBody] PodcastViewModel vm) {
|
||||
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
|
||||
var user = _userRepository.Get(email);
|
||||
if (string.IsNullOrEmpty(email) || user == null)
|
||||
return new BadRequestObjectResult("Unable to look up user profile");
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
if (ModelState.IsValid) {
|
||||
var item = _mapper.Map<PodcastViewModel, Podcast>(vm);
|
||||
item.User = user;
|
||||
|
||||
@@ -86,18 +77,15 @@ namespace PodNoms.Api.Controllers
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Put([FromBody] PodcastViewModel vm)
|
||||
{
|
||||
public async Task<IActionResult> Put([FromBody] PodcastViewModel vm) {
|
||||
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
|
||||
var user = _userRepository.Get(email);
|
||||
if (string.IsNullOrEmpty(email) || user == null)
|
||||
return new BadRequestObjectResult("Unable to look up user profile");
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
if (ModelState.IsValid) {
|
||||
var podcast = await _repository.GetAsync(vm.Id);
|
||||
if (podcast != null)
|
||||
{
|
||||
if (podcast != null) {
|
||||
var item = _mapper.Map<PodcastViewModel, Podcast>(vm, podcast);
|
||||
|
||||
await _uow.CompleteAsync();
|
||||
@@ -108,8 +96,7 @@ namespace PodNoms.Api.Controllers
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
public async Task<IActionResult> Delete(int id) {
|
||||
await this._repository.DeleteAsync(id);
|
||||
await _uow.CompleteAsync();
|
||||
return Ok();
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
using System.Threading.Tasks;
|
||||
using Lib.Net.Http.WebPush;
|
||||
using WebPush = Lib.Net.Http.WebPush;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PodNoms.Api.Services.Push;
|
||||
using PodNoms.Api.Services.Push.Models;
|
||||
using PodNoms.Api.Persistence;
|
||||
|
||||
namespace PodNoms.Api.Controllers {
|
||||
|
||||
// [Authorize]
|
||||
[Route("[controller]")]
|
||||
public class WebPushController : Controller {
|
||||
public class WebPushController : AuthController {
|
||||
private readonly IPushSubscriptionStore _subscriptionStore;
|
||||
public readonly IPushNotificationService _notificationService;
|
||||
|
||||
public WebPushController(IPushSubscriptionStore subscriptionStore, IPushNotificationService notificationService) {
|
||||
public WebPushController(IUserRepository userRepository, IPushSubscriptionStore subscriptionStore,
|
||||
IPushNotificationService notificationService) : base(userRepository) {
|
||||
this._subscriptionStore = subscriptionStore;
|
||||
this._notificationService = notificationService;
|
||||
}
|
||||
|
||||
[HttpPost("subscribe")]
|
||||
public async Task<IActionResult> StoreSubscription([FromBody]PushSubscription subscription) {
|
||||
public async Task<IActionResult> StoreSubscription([FromBody]WebPush.PushSubscription subscription) {
|
||||
subscription.Keys["auth"] = $"{await this.GetUserUidAsync()}";
|
||||
await _subscriptionStore.StoreSubscriptionAsync(subscription);
|
||||
return NoContent();
|
||||
}
|
||||
@@ -27,13 +30,13 @@ namespace PodNoms.Api.Controllers {
|
||||
// POST push-notifications-api/notifications
|
||||
[HttpPost("message")]
|
||||
public async Task<IActionResult> SendNotification([FromBody]PushMessageViewModel message) {
|
||||
PushMessage pushMessage = new PushMessage(message.Notification) {
|
||||
WebPush.PushMessage pushMessage = new WebPush.PushMessage(message.Notification) {
|
||||
Topic = message.Topic,
|
||||
Urgency = message.Urgency
|
||||
};
|
||||
|
||||
// TODO: This should be scheduled in background
|
||||
await _subscriptionStore.ForEachSubscriptionAsync((PushSubscription subscription) => {
|
||||
await _subscriptionStore.ForEachSubscriptionAsync((WebPush.PushSubscription subscription) => {
|
||||
// Fire-and-forget
|
||||
_notificationService.SendNotificationAsync(subscription, pushMessage);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Hangfire" Version="1.6.17" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
|
||||
<PackageReference Include="Lib.Net.Http.EncryptedContentEncoding" Version="1.2.0" />
|
||||
<PackageReference Include="Lib.Net.Http.WebPush" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-preview1-final" />
|
||||
<PackageReference Include="AutoMapper" Version="6.2.2" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
|
||||
|
||||
34
server/PodNoms.Api.sln
Normal file
34
server/PodNoms.Api.sln
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PodNoms.Api", "PodNoms.Api.csproj", "{FA7A1ACB-9EF1-437D-A32D-B6051700C180}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Release|x64.Build.0 = Release|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FA7A1ACB-9EF1-437D-A32D-B6051700C180}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -19,14 +19,14 @@ namespace PodNoms.Api {
|
||||
}
|
||||
public static IWebHostBuilder CreateWebHostBuilder (string[] args) =>
|
||||
WebHost.CreateDefaultBuilder (args)
|
||||
.UseStartup<Startup> ()
|
||||
.UseKestrel(options => {
|
||||
options.Listen(IPAddress.Any, 5000);
|
||||
if (isDevelopment){
|
||||
options.Listen(IPAddress.Any, 5001, listenOptions => {
|
||||
listenOptions.UseHttps("certs/dev2.podnoms.com.pfx", "secret");
|
||||
});
|
||||
}
|
||||
});
|
||||
.UseStartup<Startup> ();
|
||||
// .UseKestrel(options => {
|
||||
// options.Listen(IPAddress.Any, 5000);
|
||||
// if (isDevelopment){
|
||||
// options.Listen(IPAddress.Any, 5001, listenOptions => {
|
||||
// listenOptions.UseHttps("certs/dev2.podnoms.com.pfx", "secret");
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
7
server/Services/Jobs/INotifyJobCompleteService.cs
Normal file
7
server/Services/Jobs/INotifyJobCompleteService.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PodNoms.Api.Services.Jobs {
|
||||
public interface INotifyJobCompleteService {
|
||||
Task NotifyUser(string userId, string title, string body, string image);
|
||||
}
|
||||
}
|
||||
27
server/Services/Jobs/NotifyJobCompleteService.cs
Normal file
27
server/Services/Jobs/NotifyJobCompleteService.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Threading.Tasks;
|
||||
using Lib.Net.Http.WebPush;
|
||||
using PodNoms.Api.Services.Push;
|
||||
using WebPush = Lib.Net.Http.WebPush;
|
||||
|
||||
namespace PodNoms.Api.Services.Jobs {
|
||||
public class NotifyJobCompleteService : INotifyJobCompleteService {
|
||||
private readonly IPushSubscriptionStore _subscriptionStore;
|
||||
private readonly IPushNotificationService _notificationService;
|
||||
|
||||
public NotifyJobCompleteService(IPushSubscriptionStore subscriptionStore,
|
||||
IPushNotificationService notificationService) {
|
||||
this._notificationService = notificationService;
|
||||
this._subscriptionStore = subscriptionStore;
|
||||
|
||||
}
|
||||
public async Task NotifyUser(string userId, string title, string body, string image) {
|
||||
WebPush.PushMessage pushMessage = new WebPush.PushMessage(body) {
|
||||
Topic = title,
|
||||
Urgency = PushMessageUrgency.Normal
|
||||
};
|
||||
await _subscriptionStore.ForEachSubscriptionAsync(userId, (WebPush.PushSubscription subscription) => {
|
||||
_notificationService.SendNotificationAsync(subscription, pushMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Lib.Net.Http.WebPush;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -14,7 +15,9 @@ namespace PodNoms.Api.Services.Push.Data {
|
||||
}
|
||||
|
||||
public Task StoreSubscriptionAsync(PushSubscription subscription) {
|
||||
_context.Subscriptions.Add(new PushSubscriptionContext.PushSubscription(subscription));
|
||||
PushSubscriptionContext.PushSubscription entity = new PushSubscriptionContext.PushSubscription(subscription);
|
||||
var key = entity.Auth;
|
||||
_context.Subscriptions.Add(entity);
|
||||
|
||||
return _context.SaveChangesAsync();
|
||||
}
|
||||
@@ -26,7 +29,9 @@ namespace PodNoms.Api.Services.Push.Data {
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task ForEachSubscriptionAsync(string uid, Action<PushSubscription> action) {
|
||||
return _context.Subscriptions.Where(e => e.Auth == uid).AsNoTracking().ForEachAsync(action);
|
||||
}
|
||||
public Task ForEachSubscriptionAsync(Action<PushSubscription> action) {
|
||||
return _context.Subscriptions.AsNoTracking().ForEachAsync(action);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PodNoms.Api.Services.Push.Data;
|
||||
|
||||
namespace PodNoms.Api.Services.Push.Extensions {
|
||||
public static class ApplicationBuilderExtensions {
|
||||
public static IApplicationBuilder UseSqlitePushSubscriptionStore(this IApplicationBuilder app) {
|
||||
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
|
||||
PushSubscriptionContext context = serviceScope.ServiceProvider.GetService<PushSubscriptionContext>();
|
||||
context.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace PodNoms.Api.Services.Push.Extensions {
|
||||
public static IServiceCollection AddPushServicePushNotificationService(this IServiceCollection services) {
|
||||
services.AddMemoryCache();
|
||||
services.AddSingleton<IVapidTokenCache, MemoryVapidTokenCache>();
|
||||
services.AddSingleton<IPushNotificationService, PushServicePushNotificationService>();
|
||||
services.AddSingleton<IPushNotificationService, FirebasePushNotificationService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
43
server/Services/Push/FirebasePushNotificationService.cs
Normal file
43
server/Services/Push/FirebasePushNotificationService.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Threading.Tasks;
|
||||
using Lib.Net.Http.WebPush;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PodNoms.Api.Services.Push {
|
||||
public class FirebasePushNotificationService : IPushNotificationService {
|
||||
private readonly PushNotificationServiceOptions _options;
|
||||
private readonly ILogger<PushServicePushNotificationService> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
public string PublicKey => _options.PublicKey;
|
||||
public FirebasePushNotificationService(IOptions<PushNotificationServiceOptions> optionsAccessor,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILogger<PushServicePushNotificationService> logger) {
|
||||
_options = optionsAccessor.Value;
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
public async Task SendNotificationAsync(PushSubscription subscription, PushMessage message) {
|
||||
var fb_message = new {
|
||||
notification = new {
|
||||
title = message.Topic,
|
||||
body = message.Content,
|
||||
icon = _options.ImageUrl,
|
||||
click_action = _options.ClickUrl,
|
||||
},
|
||||
to = subscription.Endpoint
|
||||
};
|
||||
var data = JsonConvert.SerializeObject(fb_message);
|
||||
var content = new StringContent(data, Encoding.UTF8, "application/json");
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
|
||||
"key",
|
||||
$"={_options.PrivateKey}");
|
||||
var result = await client.PostAsync(_options.PushUrl, content);
|
||||
_logger.LogInformation("FCM: ", result.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Lib.Net.Http.WebPush;
|
||||
namespace PodNoms.Api.Services.Push {
|
||||
public interface IPushSubscriptionStore {
|
||||
Task StoreSubscriptionAsync(PushSubscription subscription);
|
||||
Task ForEachSubscriptionAsync(string uid, Action<PushSubscription> action);
|
||||
Task ForEachSubscriptionAsync(Action<PushSubscription> action);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
namespace PodNoms.Api.Services.Push {
|
||||
public class PushNotificationServiceOptions {
|
||||
|
||||
public string Subject { get; set; }
|
||||
|
||||
public string PushUrl { get; set; }
|
||||
public string ClickUrl { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public string PublicKey { get; set; }
|
||||
|
||||
public string PrivateKey { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,8 @@ namespace PodNoms.Api {
|
||||
e.AddProfile(new MappingProvider(Configuration));
|
||||
});
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
services.AddAuthentication(options => {
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
@@ -178,6 +180,7 @@ namespace PodNoms.Api {
|
||||
services.AddScoped<IPlaylistRepository, PlaylistRepository>();
|
||||
services.AddScoped<IUserRepository, UserRepository>();
|
||||
services.AddScoped<IUrlProcessService, UrlProcessService>();
|
||||
services.AddScoped<INotifyJobCompleteService, NotifyJobCompleteService>();
|
||||
services.AddScoped<IAudioUploadProcessService, AudioUploadProcessService>();
|
||||
services.AddScoped<IMailSender, MailgunSender>();
|
||||
|
||||
|
||||
BIN
server/pushsubscription.db
Normal file
BIN
server/pushsubscription.db
Normal file
Binary file not shown.
Reference in New Issue
Block a user