###### tags: `會議紀錄` ###### Thunder: ###### CodeReview:`SignalR 2.0` `JSDC2020_弄錯非同步_服務還是會卡住` ###### Announcement:`In-App Purchase #行動版內購` `Google Calendar #OAuth驗證` ###### Attendee: 政儀、偉恩、政儒、宜臻 # 📌[R]2020/10/21前端會議 ## 踩雷事件 ### ⚡無踩雷事件 ## 寫法交流 ### ⛏SignalR 2.0 #### ==需求.== 因為目前的系統公告跟後端通知都會顯示在`通知`內,久而久之系統公告可能會被當成垃圾訊息,所以希望可以把他們分開來,把系統公告做在專案上方。 ![](https://i.imgur.com/QV6RZ7m.png) #### ==寫法.== - 換寫法主要是因為後端說,`SignalR 2.0`不像原本的一樣斷線還需要自己連線,`SignalR 2.0`會自動重連,所以我們就調整寫法試試看。 - A1 主要寫在 `A1noticeService.ts`這支作業內。 ```typescript= import { concatMap, tap, delay, catchError } from 'rxjs/operators'; import { Observable } from 'rxjs/Observable'; import { A1NoticeController } from '@a1/service/a1-notice-message-controller'; import { LocalStorageService } from '@a1/service/local-storage.service'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { HubConnection, HttpTransportType } from '@aspnet/signalr'; import { from, of, throwError, Subject } from 'rxjs'; import { NoticeEventName, IA1NoticeRespone, NoticeBroadCast, A1SystemOperation } from '@a1/interfaces/service/a1notice-service.interface'; import { environment } from './../../environments/environment'; import * as signalR from '@aspnet/signalr'; @Injectable() export class A1noticeService { private hubConnection: HubConnection; private a1NoticeController: A1NoticeController; private a1BroadCastMessageEvent: Subject<NoticeBroadCast> = new Subject(); private a1SystemOperationEvent: Subject<A1SystemOperation> = new Subject(); /** 連線否*/private isConnection = false; private retryCount = 10; constructor( private router: Router, private localStorageService: LocalStorageService, ) { this.a1NoticeController = new A1NoticeController(this.router); } /**連線&註冊&加入群組 */ startSiganlR() { this.initConnection(); const user = JSON.parse(localStorage.getItem('auth_user')); this.startConnect().pipe( concatMap(() => this.invoke(NoticeEventName.Registered, `${user.TenantID}-${user.ID}`)), ).subscribe(() => { console.log('A1noticeService connect & joinGroup Success'); }, (error) => { console.log('error', error); }); } /**send to signalR */ invoke(eventName: NoticeEventName, data = ''): Observable<any> { const isConnection = this.getIsConnection(); const area = environment.Name; if (!isConnection) { return of(null); } switch (eventName) { case NoticeEventName.Registered: return from(this.hubConnection.invoke(eventName, data, area)); case NoticeEventName.PutPostion: return from(this.hubConnection.invoke(eventName, data)); case NoticeEventName.SendGroup: return from(this.hubConnection.invoke(eventName, data, area)); case NoticeEventName.Echo: return from(this.hubConnection.invoke(eventName)); } } /**斷線 */ disConnect(): Observable<void> { if (this.hubConnection) { return from(this.hubConnection.stop()); } else { return of(); } } /** 廣播事件 */ getBroadCastMessage() { return this.a1BroadCastMessageEvent; } /** 系統操作事件 */ getA1SystemOperation() { return this.a1SystemOperationEvent; } /**開始連線 */ private startConnect(): Observable<any> { return from(this.hubConnection.start()).pipe( tap(() => { this.setIsConnection(true); }), catchError((error) => { this.setIsConnection(false); this.retryCount += 1; if (this.retryCount < 5) { return of(this.startConnect()).pipe( delay(5000 * this.retryCount), concatMap(() => this.startConnect()) ); } else { throwError(error); } }) ); } /**初始化連線 */ private initConnection() { if (!this.localStorageService.getNoticeHub()) { return; } const hubConnectionUrl = `${this.localStorageService.getNoticeHub()}/a1notice` || 'https://a1devnoticecore.azurewebsites.net/a1notice'; const token = this.localStorageService.getSimpleAuthorization(); this.hubConnection = new signalR.HubConnectionBuilder() .withUrl(hubConnectionUrl, { accessTokenFactory: () => token, transport: HttpTransportType.WebSockets }) .configureLogging(signalR.LogLevel.Information) .build(); /** SendGroup */ this.hubConnection.on(NoticeEventName.SendGroup, (res: IA1NoticeRespone) => { console.log(`${NoticeEventName.SendGroup}:`, res); if (res) { this.a1NoticeController.controlMessage(res); } }); /** A1BroadCast */ this.hubConnection.on(NoticeEventName.A1BroadCast, (res: NoticeBroadCast) => { console.log(`${NoticeEventName.A1BroadCast}:`, res); this.a1BroadCastMessageEvent.next(res); }); /** A1SystemOperation */ this.hubConnection.on(NoticeEventName.A1SystemOperation, (res: A1SystemOperation) => { console.log(`${NoticeEventName.A1SystemOperation}:`, res); this.a1SystemOperationEvent.next(res); }); } /**取得連線狀態 */ private getIsConnection() { return this.isConnection; } /**設定連線狀態 */ private setIsConnection(v: boolean): void { this.isConnection = v; } } ``` #### ==補充.== - 之前寫的時候有用編碼來判斷要用哪個Subject來訂閱,但現在改用一個來用。(不各自編號 用一個Subject來subscribe) - A1SystemOperation 有做更新刷新之類的特殊處理。 ### ⛏JSDC2020-JavaScript 中鼓勵用非同步的 API,但用了就會提升效能避免阻塞嗎? #陳柏融(PJCHENder) #### ==相關連結.== *影片.* [前端Google Drive](https://drive.google.com/drive/folders/1eL8TOogM6eI80gTghowlWCIuB1EDx2E0) *PPT.* [JSDC_20201017_弄錯非同步_服務還是會卡住](https://docs.google.com/presentation/d/14tuekWY4I1BSJbsFnOGdsZP1c0ocR6gXiuiH7s5yHeM/edit#slide=id.p) *共筆.* [HackMD](https://hackmd.io/dqbjtdvcQQCdGqdcF4K-iQ?both) #### ==討論.== 行動版不知道有沒有支援這樣的功能,而且目前前端大部分沒有這種狀況,正常都是 等完後端>資料整理>上傳資料>等待回傳,好像沒有會卡住執行的部分,目前會轉的部分都是後端API在執行。 但有機會我們也是可以來試試看相關的寫法,關鍵字`MDN web worker` ## 事情公告 ### 📢In-App Purchase #行動版內購 > 在年初的時候,@wayne已經有先研究過IAP。 > [(📖#) IAP app內購功能 (IOS)](https://hackmd.io/@wayntu8662/BJ_6mW-GI) > [name=Wayne] #### ==原因.== 手機流量每個月有不少的固定流量(400~500人),但不知道這些人的聯絡訊息,因此希望可以加上內購,看能不能增加購買量。 #### ==目前.== - 內購有分四種型態(我沒有查證,也不確定有沒有聽錯) 1. 消耗 2. 非消耗 3. 訂閱 4. 非訂閱 - 如果需要的話,偉恩是覺得應該是要用`消耗`類型,但政儀是說應該用`訂閱` - 目前這些都是需要給`Apple`抽大約30%的手續費 - 聽說如果直接導到訂購頁是違法的? => 微信,打賞功能會抽費,但功能明明是APPLE做的 #### ==決定.== - 目前覺得這樣做的效益不大(花很多時間,且還會被抽成),所以看看能不能不做內購 - 請SD先去請行銷問問是不是真的不行使用導到訂購頁的方式 - 另一方面我們也可以直接先送審核試試看 ### 📢Google Calendar #OAuth驗證 > 目前是暫定此功能會在`會員載具`功能後出去(可能11、12月以後的事了) #### ==目前.== 目前驗證是通過的 #API範圍是用,非敏感且沒被限制的 ```typescript SCOPES = 'https://www.googleapis.com/auth/calendar.app.created'; ``` 但是驗證後發現,這API的使用範圍跟想的不一樣,他上面是寫`新增、修改.. 次要的日曆`,但完全找不到次要的日曆,所以想要重新驗證。 下面是要重新驗證的範圍,那他是敏感範圍的API所以重新驗證的時間會比較久一點(最久4~6周)。 ```typescript SCOPES = 'https://www.googleapis.com/auth/calendar'; ```