@@ -2,20 +2,24 @@ import { TinyEmitter } from "tiny-emitter";
22import { Base64 } from "js-base64" ;
33import wefetch from "wefetch" ;
44import { 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" ;
89const UA = "WECHAT_MINIPROGRAM/" + PKG_VERSION ;
10+ const KEY = "repository" ;
911
1012const STATUS = {
1113 PENDING : "pending" ,
1214 READY : "ready" ,
15+ ERROR : "error" ,
1316}
1417
1518const EVENTS = {
1619 READY : "ready" ,
1720 ERROR : "error" ,
1821 UPDATE : "update" ,
22+ CACHE_READY : "cache_ready"
1923} ;
2024
2125class 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
294363const featureProbeClient = new FeatureProbe ( ) ;
295364
296-
297365export { FeatureProbe , featureProbeClient } ;
0 commit comments