###### tags: `會議紀錄`
###### Thunder:
###### CodeReview:`SignalR 2.0` `JSDC2020_弄錯非同步_服務還是會卡住`
###### Announcement:`In-App Purchase #行動版內購` `Google Calendar #OAuth驗證`
###### Attendee: 政儀、偉恩、政儒、宜臻
# 📌[R]2020/10/21前端會議
## 踩雷事件
### ⚡無踩雷事件
## 寫法交流
### ⛏SignalR 2.0
#### ==需求.==
因為目前的系統公告跟後端通知都會顯示在`通知`內,久而久之系統公告可能會被當成垃圾訊息,所以希望可以把他們分開來,把系統公告做在專案上方。

#### ==寫法.==
- 換寫法主要是因為後端說,`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';
```