@@ -2,20 +2,24 @@ import { TinyEmitter } from "tiny-emitter";
2
2
import { Base64 } from "js-base64" ;
3
3
import wefetch from "wefetch" ;
4
4
import { FPUser } from "./FPUser" ;
5
- import { FPToggleDetail , IParams , IOption } from "./types" ;
5
+ import StorageProvider from "./localstorage" ;
6
+ import { FPToggleDetail , IParams , IOption , IStorageProvider } from "./types" ;
6
7
7
- const PKG_VERSION = ' SDK_VERSION' ;
8
+ const PKG_VERSION = " SDK_VERSION" ;
8
9
const UA = "WECHAT_MINIPROGRAM/" + PKG_VERSION ;
10
+ const KEY = "repository" ;
9
11
10
12
const STATUS = {
11
13
PENDING : "pending" ,
12
14
READY : "ready" ,
15
+ ERROR : "error" ,
13
16
}
14
17
15
18
const EVENTS = {
16
19
READY : "ready" ,
17
20
ERROR : "error" ,
18
21
UPDATE : "update" ,
22
+ CACHE_READY : "cache_ready"
19
23
} ;
20
24
21
25
class FeatureProbe extends TinyEmitter {
@@ -24,28 +28,34 @@ class FeatureProbe extends TinyEmitter {
24
28
private refreshInterval : number ;
25
29
private clientSdkKey : string ;
26
30
private user : FPUser ;
27
- private toggles : { [ key : string ] : FPToggleDetail } | null ;
31
+ private toggles : { [ key : string ] : FPToggleDetail } | undefined ;
28
32
private timer ?: NodeJS . Timeout ;
33
+ private timeoutTimer ?: NodeJS . Timeout ;
29
34
private readyPromise : Promise < void > ;
30
35
private status : string ;
36
+ private storage : IStorageProvider ;
37
+ private timeoutInterval : number ;
31
38
32
39
constructor ( ) {
33
40
super ( ) ;
34
41
35
- this . togglesUrl = '' ;
36
- this . eventsUrl = '' ;
42
+ this . togglesUrl = "" ;
43
+ this . eventsUrl = "" ;
37
44
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
+
41
52
this . readyPromise = new Promise ( ( resolve ) => {
42
53
const onReadyCallback = ( ) => {
43
54
this . off ( EVENTS . READY , onReadyCallback ) ;
44
55
resolve ( ) ;
45
56
} ;
46
57
this . on ( EVENTS . READY , onReadyCallback ) ;
47
58
} ) ;
48
- this . status = STATUS . PENDING ;
49
59
}
50
60
51
61
public init ( {
@@ -55,45 +65,61 @@ class FeatureProbe extends TinyEmitter {
55
65
clientSdkKey,
56
66
user,
57
67
refreshInterval = 1000 ,
68
+ timeoutInterval = 10000 ,
58
69
} : IOption ) {
59
70
if ( ! clientSdkKey ) {
60
71
throw new Error ( "clientSdkKey is required" ) ;
61
72
}
62
-
63
73
if ( refreshInterval <= 0 ) {
64
74
throw new Error ( "refreshInterval is invalid" ) ;
65
75
}
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
+ }
67
82
if ( ! remoteUrl && ! togglesUrl ) {
68
83
throw new Error ( "remoteUrl or togglesUrl is required" ) ;
69
84
}
70
-
71
85
if ( ! remoteUrl && ! eventsUrl ) {
72
86
throw new Error ( "remoteUrl or eventsUrl is required" ) ;
73
87
}
74
88
75
- if ( ! remoteUrl && ! togglesUrl && ! eventsUrl ) {
76
- throw new Error ( "remoteUrl is required" ) ;
77
- }
78
-
79
89
this . togglesUrl = togglesUrl || ( remoteUrl + "/api/client-sdk/toggles" ) ;
80
90
this . eventsUrl = eventsUrl || ( remoteUrl + "/api/events" ) ;
81
91
this . user = user ;
82
92
this . clientSdkKey = clientSdkKey ;
83
93
this . refreshInterval = refreshInterval ;
94
+ this . timeoutInterval = timeoutInterval ;
84
95
}
85
96
86
97
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
+
88
104
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
+
89
112
await this . fetchToggles ( ) ;
90
113
} finally {
91
- this . timer = setInterval ( ( ) => this . fetchToggles ( ) , interval ) ;
114
+ this . timer = setInterval ( ( ) => this . fetchToggles ( ) , this . refreshInterval ) ;
92
115
}
93
116
}
94
117
95
118
public stop ( ) {
96
119
clearInterval ( this . timer ) ;
120
+ clearTimeout ( this . timeoutTimer ) ;
121
+ this . timeoutTimer = undefined ;
122
+ this . timer = undefined ;
97
123
}
98
124
99
125
public waitUntilReady ( ) : Promise < void > {
@@ -149,12 +175,13 @@ class FeatureProbe extends TinyEmitter {
149
175
this . identifyUser ( user ) ;
150
176
}
151
177
152
- static newForTest ( toggles : { [ key : string ] : any } ) : FeatureProbe {
178
+ static newForTest ( toggles ? : { [ key : string ] : any } ) : FeatureProbe {
153
179
const fp = new FeatureProbe ( ) ;
154
180
fp . init ( {
155
181
remoteUrl : "http://127.0.0.1:4000" ,
156
182
clientSdkKey : "_" ,
157
183
user : new FPUser ( ) ,
184
+ timeoutInterval : 1000 ,
158
185
} ) ;
159
186
fp . toggles = toggles ;
160
187
fp . successInitialized ( ) ;
@@ -235,13 +262,29 @@ class FeatureProbe extends TinyEmitter {
235
262
data : {
236
263
user : userParam
237
264
}
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 ) ;
245
288
} ) ;
246
289
}
247
290
@@ -275,23 +318,48 @@ class FeatureProbe extends TinyEmitter {
275
318
UA : UA ,
276
319
} ,
277
320
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 ) ;
280
332
} ) ;
281
333
}
282
334
}
283
335
336
+ // Emit `ready` event if toggles are successfully returned from server
284
337
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 ;
290
359
}
291
360
}
292
361
}
293
362
294
363
const featureProbeClient = new FeatureProbe ( ) ;
295
364
296
-
297
365
export { FeatureProbe , featureProbeClient } ;
0 commit comments