Skip to content

Commit ee088a7

Browse files
committed
chore: add cache and timeout
1 parent b61bed0 commit ee088a7

13 files changed

+3585
-3263
lines changed

WeChat/FeatureProbe.ts

+102-34
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@ import { TinyEmitter } from "tiny-emitter";
22
import { Base64 } from "js-base64";
33
import wefetch from "wefetch";
44
import { FPUser } from "./FPUser";
5-
import { FPToggleDetail, IParams, IOption } from "./types";
5+
import StorageProvider from "./localstorage";
6+
import { FPToggleDetail, IParams, IOption, IStorageProvider } from "./types";
67

7-
const PKG_VERSION = 'SDK_VERSION';
8+
const PKG_VERSION = "SDK_VERSION";
89
const UA = "WECHAT_MINIPROGRAM/" + PKG_VERSION;
10+
const KEY = "repository";
911

1012
const STATUS = {
1113
PENDING: "pending",
1214
READY: "ready",
15+
ERROR: "error",
1316
}
1417

1518
const EVENTS = {
1619
READY: "ready",
1720
ERROR: "error",
1821
UPDATE: "update",
22+
CACHE_READY: "cache_ready"
1923
};
2024

2125
class FeatureProbe extends TinyEmitter {
@@ -24,28 +28,34 @@ class FeatureProbe extends TinyEmitter {
2428
private refreshInterval: number;
2529
private clientSdkKey: string;
2630
private user: FPUser;
27-
private toggles: { [key: string]: FPToggleDetail } | null;
31+
private toggles: { [key: string]: FPToggleDetail } | undefined;
2832
private timer?: NodeJS.Timeout;
33+
private timeoutTimer?: NodeJS.Timeout;
2934
private readyPromise: Promise<void>;
3035
private status: string;
36+
private storage: IStorageProvider;
37+
private timeoutInterval: number;
3138

3239
constructor() {
3340
super();
3441

35-
this.togglesUrl = '';
36-
this.eventsUrl = '';
42+
this.togglesUrl = "";
43+
this.eventsUrl = "";
3744
this.user = new FPUser();
38-
this.clientSdkKey = '';
39-
this.refreshInterval = 0;
40-
this.toggles = {};
45+
this.clientSdkKey = "";
46+
this.refreshInterval = 1000;
47+
this.timeoutInterval = 10000;
48+
this.toggles = undefined;
49+
this.status = STATUS.PENDING;
50+
this.storage = new StorageProvider();
51+
4152
this.readyPromise = new Promise((resolve) => {
4253
const onReadyCallback = () => {
4354
this.off(EVENTS.READY, onReadyCallback);
4455
resolve();
4556
};
4657
this.on(EVENTS.READY, onReadyCallback);
4758
});
48-
this.status = STATUS.PENDING;
4959
}
5060

5161
public init({
@@ -55,45 +65,61 @@ class FeatureProbe extends TinyEmitter {
5565
clientSdkKey,
5666
user,
5767
refreshInterval = 1000,
68+
timeoutInterval = 10000,
5869
}: IOption) {
5970
if (!clientSdkKey) {
6071
throw new Error("clientSdkKey is required");
6172
}
62-
6373
if (refreshInterval <= 0) {
6474
throw new Error("refreshInterval is invalid");
6575
}
66-
76+
if (timeoutInterval <= 0) {
77+
throw new Error("timeoutInterval is invalid");
78+
}
79+
if (!remoteUrl && !togglesUrl && !eventsUrl) {
80+
throw new Error("remoteUrl is required");
81+
}
6782
if (!remoteUrl && !togglesUrl) {
6883
throw new Error("remoteUrl or togglesUrl is required");
6984
}
70-
7185
if (!remoteUrl && !eventsUrl) {
7286
throw new Error("remoteUrl or eventsUrl is required");
7387
}
7488

75-
if (!remoteUrl && !togglesUrl && !eventsUrl) {
76-
throw new Error("remoteUrl is required");
77-
}
78-
7989
this.togglesUrl = togglesUrl || (remoteUrl + "/api/client-sdk/toggles");
8090
this.eventsUrl = eventsUrl || (remoteUrl + "/api/events");
8191
this.user = user;
8292
this.clientSdkKey = clientSdkKey;
8393
this.refreshInterval = refreshInterval;
94+
this.timeoutInterval = timeoutInterval;
8495
}
8596

8697
public async start() {
87-
const interval = this.refreshInterval;
98+
this.timeoutTimer = setTimeout(() => {
99+
if (this.status === STATUS.PENDING) {
100+
this.errorInitialized();
101+
}
102+
}, this.timeoutInterval);
103+
88104
try {
105+
// Emit `cache_ready` event if toggles exist in LocalStorage
106+
const toggles = await this.storage.getItem(KEY);
107+
if (toggles) {
108+
this.toggles = JSON.parse(toggles);
109+
this.emit(EVENTS.CACHE_READY);
110+
}
111+
89112
await this.fetchToggles();
90113
} finally {
91-
this.timer = setInterval(() => this.fetchToggles(), interval);
114+
this.timer = setInterval(() => this.fetchToggles(), this.refreshInterval);
92115
}
93116
}
94117

95118
public stop() {
96119
clearInterval(this.timer);
120+
clearTimeout(this.timeoutTimer);
121+
this.timeoutTimer = undefined;
122+
this.timer = undefined;
97123
}
98124

99125
public waitUntilReady(): Promise<void> {
@@ -149,12 +175,13 @@ class FeatureProbe extends TinyEmitter {
149175
this.identifyUser(user);
150176
}
151177

152-
static newForTest(toggles: { [key: string]: any }): FeatureProbe {
178+
static newForTest(toggles?: { [key: string]: any }): FeatureProbe {
153179
const fp = new FeatureProbe();
154180
fp.init({
155181
remoteUrl: "http://127.0.0.1:4000",
156182
clientSdkKey: "_",
157183
user: new FPUser(),
184+
timeoutInterval: 1000,
158185
});
159186
fp.toggles = toggles;
160187
fp.successInitialized();
@@ -235,13 +262,29 @@ class FeatureProbe extends TinyEmitter {
235262
data: {
236263
user: userParam
237264
}
238-
}).then((response: any) => {
239-
this.toggles = response.data;
240-
this.successInitialized();
241-
this.emit(EVENTS.UPDATE);
242-
}).catch((e: any) => {
243-
console.error(e);
244-
//this.emit(EVENTS.ERROR, e);
265+
})
266+
.then(response => {
267+
if (response.statusCode >= 200 && response.statusCode < 300) {
268+
return response;
269+
} else {
270+
const error: Error = new Error(response.data.error);
271+
throw error;
272+
}
273+
})
274+
.then(response => {
275+
if (this.status !== STATUS.ERROR) {
276+
this.toggles = response.data;
277+
278+
if (this.status === STATUS.PENDING) {
279+
this.successInitialized();
280+
} else if (this.status === STATUS.READY) {
281+
this.emit(EVENTS.UPDATE);
282+
}
283+
284+
this.storage.setItem(KEY, JSON.stringify(response.data));
285+
}
286+
}).catch(e => {
287+
console.error("FeatureProbe MiniProgram SDK: Error getting toggles: ", e);
245288
});
246289
}
247290

@@ -275,23 +318,48 @@ class FeatureProbe extends TinyEmitter {
275318
UA: UA,
276319
},
277320
data: JSON.stringify(payload),
278-
}).catch(() => {
279-
// TODO:
321+
})
322+
.then(response => {
323+
if (response.statusCode >= 200 && response.statusCode < 300) {
324+
return response;
325+
} else {
326+
const error: Error = new Error(response.data.error);
327+
throw error;
328+
}
329+
})
330+
.catch(e => {
331+
console.error("FeatureProbe MiniProgram SDK: Error reporting events: ", e);
280332
});
281333
}
282334
}
283335

336+
// Emit `ready` event if toggles are successfully returned from server
284337
private successInitialized() {
285-
if(this.status === STATUS.PENDING) {
286-
this.status = STATUS.READY;
287-
setTimeout(() => {
288-
this.emit(EVENTS.READY);
289-
});
338+
this.status = STATUS.READY;
339+
setTimeout(() => {
340+
this.emit(EVENTS.READY);
341+
});
342+
343+
if (this.timeoutTimer) {
344+
clearTimeout(this.timeoutTimer);
345+
this.timeoutTimer = undefined;
346+
}
347+
}
348+
349+
// Emit `error` event if toggles are not available and timeout has been reached
350+
private errorInitialized() {
351+
this.status = STATUS.ERROR;
352+
setTimeout(() => {
353+
this.emit(EVENTS.ERROR);
354+
});
355+
356+
if (this.timer) {
357+
clearInterval(this.timer);
358+
this.timer = undefined;
290359
}
291360
}
292361
}
293362

294363
const featureProbeClient = new FeatureProbe();
295364

296-
297365
export { FeatureProbe, featureProbeClient };

WeChat/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { FPUser } from './FPUser';
2-
import { featureProbeClient, FeatureProbe } from './FeatureProbe';
1+
import { FPUser } from "./FPUser";
2+
import { featureProbeClient, FeatureProbe } from "./FeatureProbe";
33

44
export { FPUser, FeatureProbe, featureProbeClient };
55

@@ -8,7 +8,7 @@ declare global {
88
let getApp: () => any;
99
}
1010

11-
featureProbeClient.on('update', function() {
11+
featureProbeClient.on("update", function() {
1212
getApp().globalData.toggles = featureProbeClient.allToggles();
1313
});
1414

WeChat/localstorage.ts

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
11
declare class wx {
22
static getStorageSync(key: string): any;
33
static setStorageSync(key: string, data: any): void;
4-
static removeStorageSync(key: string): void;
54
}
65

7-
export default {
8-
getItem(key: string): string {
6+
export default class StorageProvider {
7+
public async getItem(key: string) {
98
try {
109
return wx.getStorageSync(key);
1110
} catch (e) {
1211
console.log(e);
13-
return '';
12+
return "";
1413
}
15-
},
16-
setItem(key: string, data: string) {
14+
}
15+
16+
public async setItem(key: string, data: string) {
1717
try {
1818
wx.setStorageSync(key, data);
1919
} catch (e) {
2020
console.log(e);
2121
}
22-
},
23-
removeItem(key: string) {
24-
try {
25-
wx.removeStorageSync(key);
26-
} catch (e) {
27-
console.log(e);
28-
}
2922
}
3023
}

WeChat/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,10 @@ export interface IOption {
3636
clientSdkKey: string;
3737
user: FPUser;
3838
refreshInterval?: number;
39+
timeoutInterval?: number;
40+
}
41+
42+
export interface IStorageProvider {
43+
setItem: (key: string, data: any) => Promise<void>;
44+
getItem: (key: string) => Promise<any>;
3945
}

0 commit comments

Comments
 (0)