@featbit/js-client-sdk 3.0.11 → 3.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +301 -301
  3. package/dist/esm/FbClientCore.d.ts.map +1 -1
  4. package/dist/esm/FbClientCore.js +1 -1
  5. package/dist/esm/FbClientCore.js.map +1 -1
  6. package/dist/esm/data-sources/DataSourceUpdates.d.ts +2 -2
  7. package/dist/esm/data-sources/DataSourceUpdates.d.ts.map +1 -1
  8. package/dist/esm/data-sources/DataSourceUpdates.js +55 -51
  9. package/dist/esm/data-sources/DataSourceUpdates.js.map +1 -1
  10. package/dist/esm/data-sources/createStreamListeners.d.ts +0 -9
  11. package/dist/esm/data-sources/createStreamListeners.d.ts.map +1 -1
  12. package/dist/esm/data-sources/createStreamListeners.js +10 -10
  13. package/dist/esm/data-sources/createStreamListeners.js.map +1 -1
  14. package/dist/esm/data-sync/IDataSynchronizer.d.ts +1 -1
  15. package/dist/esm/data-sync/IDataSynchronizer.d.ts.map +1 -1
  16. package/dist/esm/data-sync/NullDataSynchronizer.d.ts +1 -1
  17. package/dist/esm/data-sync/NullDataSynchronizer.d.ts.map +1 -1
  18. package/dist/esm/data-sync/NullDataSynchronizer.js +11 -0
  19. package/dist/esm/data-sync/NullDataSynchronizer.js.map +1 -1
  20. package/dist/esm/data-sync/PollingDataSynchronizer.d.ts +1 -1
  21. package/dist/esm/data-sync/PollingDataSynchronizer.d.ts.map +1 -1
  22. package/dist/esm/data-sync/PollingDataSynchronizer.js +42 -17
  23. package/dist/esm/data-sync/PollingDataSynchronizer.js.map +1 -1
  24. package/dist/esm/data-sync/WebSocketDataSynchronizer.d.ts +2 -1
  25. package/dist/esm/data-sync/WebSocketDataSynchronizer.d.ts.map +1 -1
  26. package/dist/esm/data-sync/WebSocketDataSynchronizer.js +20 -6
  27. package/dist/esm/data-sync/WebSocketDataSynchronizer.js.map +1 -1
  28. package/dist/esm/data-sync/types.d.ts +1 -1
  29. package/dist/esm/data-sync/types.d.ts.map +1 -1
  30. package/dist/esm/integrations/test_data/TestDataSynchronizer.d.ts +1 -1
  31. package/dist/esm/integrations/test_data/TestDataSynchronizer.d.ts.map +1 -1
  32. package/dist/esm/integrations/test_data/TestDataSynchronizer.js +3 -1
  33. package/dist/esm/integrations/test_data/TestDataSynchronizer.js.map +1 -1
  34. package/dist/esm/platform/browser/BrowserWebSocket.d.ts.map +1 -1
  35. package/dist/esm/platform/browser/BrowserWebSocket.js +4 -0
  36. package/dist/esm/platform/browser/BrowserWebSocket.js.map +1 -1
  37. package/dist/esm/store/IDataSourceUpdates.d.ts +2 -2
  38. package/dist/esm/store/IDataSourceUpdates.d.ts.map +1 -1
  39. package/dist/esm/version.d.ts +1 -1
  40. package/dist/esm/version.js +1 -1
  41. package/dist/umd/{featbit-js-client-sdk-3.0.11.js → featbit-js-client-sdk-3.0.13.js} +2 -2
  42. package/dist/umd/featbit-js-client-sdk-3.0.13.js.map +1 -0
  43. package/dist/umd/featbit-js-client-sdk.js +1 -1
  44. package/dist/umd/featbit-js-client-sdk.js.map +1 -1
  45. package/package.json +46 -46
  46. package/src/Configuration.ts +232 -232
  47. package/src/Context.ts +61 -61
  48. package/src/FbClientBuilder.ts +167 -167
  49. package/src/FbClientCore.ts +405 -401
  50. package/src/IContextProperty.ts +3 -3
  51. package/src/IDataKind.ts +11 -11
  52. package/src/IFbClient.ts +29 -29
  53. package/src/IFbClientCore.ts +290 -290
  54. package/src/IVersionedData.ts +18 -18
  55. package/src/bootstrap/IBootstrapProvider.ts +4 -4
  56. package/src/bootstrap/JsonBootstrapProvider.ts +34 -34
  57. package/src/bootstrap/NullBootstrapProvider.ts +20 -20
  58. package/src/bootstrap/index.ts +2 -2
  59. package/src/constants.ts +1 -1
  60. package/src/data-sources/DataSourceUpdates.ts +116 -116
  61. package/src/data-sources/createStreamListeners.ts +67 -66
  62. package/src/data-sources/index.ts +1 -1
  63. package/src/data-sync/DataSyncMode.ts +3 -3
  64. package/src/data-sync/IDataSynchronizer.ts +15 -15
  65. package/src/data-sync/IRequestor.ts +10 -10
  66. package/src/data-sync/NullDataSynchronizer.ts +14 -14
  67. package/src/data-sync/PollingDataSynchronizer.ts +125 -111
  68. package/src/data-sync/Requestor.ts +61 -61
  69. package/src/data-sync/WebSocketDataSynchronizer.ts +77 -73
  70. package/src/data-sync/index.ts +8 -8
  71. package/src/data-sync/types.ts +19 -19
  72. package/src/data-sync/utils.ts +31 -31
  73. package/src/errors.ts +47 -47
  74. package/src/evaluation/EvalResult.ts +35 -35
  75. package/src/evaluation/Evaluator.ts +26 -26
  76. package/src/evaluation/IEvalDetail.ts +23 -23
  77. package/src/evaluation/ReasonKinds.ts +9 -9
  78. package/src/evaluation/data/IFlag.ts +29 -29
  79. package/src/evaluation/index.ts +4 -4
  80. package/src/events/DefaultEventProcessor.ts +83 -83
  81. package/src/events/DefaultEventQueue.ts +49 -49
  82. package/src/events/DefaultEventSender.ts +73 -73
  83. package/src/events/DefaultEventSerializer.ts +11 -11
  84. package/src/events/EventDispatcher.ts +127 -127
  85. package/src/events/EventSerializer.ts +4 -4
  86. package/src/events/IEventProcessor.ts +8 -8
  87. package/src/events/IEventQueue.ts +16 -16
  88. package/src/events/IEventSender.ts +13 -13
  89. package/src/events/NullEventProcessor.ts +15 -15
  90. package/src/events/event.ts +129 -129
  91. package/src/events/index.ts +11 -11
  92. package/src/index.ts +21 -21
  93. package/src/integrations/TestLogger.ts +24 -24
  94. package/src/integrations/index.ts +1 -1
  95. package/src/integrations/test_data/FlagBuilder.ts +59 -59
  96. package/src/integrations/test_data/TestData.ts +57 -57
  97. package/src/integrations/test_data/TestDataSynchronizer.ts +49 -49
  98. package/src/integrations/test_data/index.ts +4 -4
  99. package/src/logging/BasicLogger.ts +108 -108
  100. package/src/logging/IBasicLoggerOptions.ts +46 -46
  101. package/src/logging/ILogger.ts +49 -49
  102. package/src/logging/LogLevel.ts +8 -8
  103. package/src/logging/SafeLogger.ts +69 -69
  104. package/src/logging/format.ts +154 -154
  105. package/src/logging/index.ts +5 -5
  106. package/src/options/ClientContext.ts +39 -39
  107. package/src/options/IClientContext.ts +53 -53
  108. package/src/options/IOptions.ts +123 -123
  109. package/src/options/IUser.ts +6 -6
  110. package/src/options/IValidatedOptions.ts +29 -29
  111. package/src/options/OptionMessages.ts +35 -35
  112. package/src/options/UserBuilder.ts +35 -35
  113. package/src/options/Validators.ts +300 -300
  114. package/src/options/index.ts +7 -7
  115. package/src/platform/IInfo.ts +102 -102
  116. package/src/platform/IPlatform.ts +20 -20
  117. package/src/platform/IStore.ts +112 -112
  118. package/src/platform/IWebSocket.ts +22 -22
  119. package/src/platform/browser/BrowserInfo.ts +24 -24
  120. package/src/platform/browser/BrowserPlatform.ts +19 -19
  121. package/src/platform/browser/BrowserRequests.ts +6 -6
  122. package/src/platform/browser/BrowserWebSocket.ts +147 -142
  123. package/src/platform/browser/FbClient.ts +65 -65
  124. package/src/platform/browser/LocalStorageStore.ts +59 -59
  125. package/src/platform/index.ts +11 -11
  126. package/src/platform/requests.ts +76 -76
  127. package/src/store/BaseStore.ts +125 -125
  128. package/src/store/DataKinds.ts +6 -6
  129. package/src/store/IDataSourceUpdates.ts +68 -68
  130. package/src/store/InMemoryStore.ts +36 -36
  131. package/src/store/index.ts +5 -5
  132. package/src/store/serialization.ts +52 -52
  133. package/src/store/store.ts +37 -37
  134. package/src/utils/Emits.ts +75 -75
  135. package/src/utils/EventEmitter.ts +128 -128
  136. package/src/utils/IEventEmitter.ts +14 -14
  137. package/src/utils/Regex.ts +21 -21
  138. package/src/utils/ValueConverters.ts +55 -55
  139. package/src/utils/canonicalizeUri.ts +3 -3
  140. package/src/utils/debounce.ts +33 -33
  141. package/src/utils/http.ts +40 -40
  142. package/src/utils/index.ts +5 -5
  143. package/src/utils/isNullOrUndefined.ts +2 -2
  144. package/src/utils/serializeUser.ts +27 -27
  145. package/src/utils/sleep.ts +5 -5
  146. package/src/version.ts +1 -1
  147. package/dist/umd/featbit-js-client-sdk-3.0.11.js.map +0 -1
@@ -1,111 +1,125 @@
1
- import { isHttpRecoverable, PollingError } from "../errors";
2
- import { IDataSynchronizer } from "./IDataSynchronizer";
3
- import { ILogger } from "../logging/ILogger";
4
- import Configuration from "../Configuration";
5
- import { EventName, PollingErrorHandler, ProcessStreamResponse, StreamResponseEventType } from "./types";
6
- import Requestor from "./Requestor";
7
- import { httpErrorMessage } from "../utils/http";
8
- import { IUser } from "../options/IUser";
9
-
10
- export default class PollingDataSynchronizer implements IDataSynchronizer {
11
- private stopped = false;
12
-
13
- private logger?: ILogger;
14
-
15
- private pollingInterval: number;
16
-
17
- private user: IUser | undefined;
18
-
19
- private timeoutHandle: any;
20
-
21
- constructor(
22
- config: Configuration,
23
- private readonly requestor: Requestor,
24
- private readonly getStoreTimestamp: () => number,
25
- private readonly listeners: Map<EventName, ProcessStreamResponse>,
26
- private readonly errorHandler?: PollingErrorHandler,
27
- ) {
28
- this.logger = config.logger;
29
- this.pollingInterval = config.pollingInterval;
30
- this.user = config.user;
31
- }
32
-
33
- private poll() {
34
- if (this.stopped) {
35
- return;
36
- }
37
-
38
- const startTime = Date.now();
39
- this.logger?.debug('Polling for feature flag and segments updates');
40
- this.requestor.requestData(this.getStoreTimestamp(), this.user, (err, body) => {
41
- const elapsed = Date.now() - startTime;
42
- const sleepFor = Math.max(this.pollingInterval - elapsed, 0);
43
-
44
- this.logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor);
45
- if (err) {
46
- const {status} = err;
47
- if (status && !isHttpRecoverable(status)) {
48
- const message = httpErrorMessage(err, 'polling request');
49
- this.logger?.error(message);
50
- this.errorHandler?.(new PollingError(message, status));
51
- // It is not recoverable, return and do not trigger another
52
- // poll.
53
- return;
54
- }
55
- this.logger?.warn(httpErrorMessage(err, 'polling request', 'will retry'));
56
- } else {
57
- let featureFlags = [];
58
- let userKeyId = this.user?.keyId!;
59
- let processStreamResponse: ProcessStreamResponse | undefined = this.listeners.get('patch');
60
-
61
- if (body) {
62
- const message = JSON.parse(body);
63
- if (message.messageType === 'data-sync') {
64
- switch (message.data.eventType) {
65
- case StreamResponseEventType.patch:
66
- processStreamResponse = this.listeners.get('patch');
67
- break;
68
- case StreamResponseEventType.full:
69
- processStreamResponse = this.listeners.get('put');
70
- break;
71
- }
72
-
73
- ({featureFlags, userKeyId} = message.data);
74
- }
75
- }
76
-
77
- const data = processStreamResponse?.deserializeData?.(featureFlags);
78
- processStreamResponse?.processJson?.(userKeyId, data);
79
- }
80
-
81
- // Falling through, there was some type of error and we need to trigger
82
- // a new poll.
83
- this.timeoutHandle = setTimeout(() => {
84
- this.poll();
85
- }, sleepFor);
86
- });
87
- }
88
-
89
- identify(user: IUser) {
90
- this.user = {...user};
91
- }
92
-
93
- close(): void {
94
- this.stop();
95
- }
96
-
97
- start(): void {
98
- this.poll();
99
- }
100
-
101
- stop(): void {
102
- if (this.timeoutHandle) {
103
- clearTimeout(this.timeoutHandle);
104
- this.timeoutHandle = undefined;
105
- }
106
- this.stopped = true;
107
- }
108
- }
109
-
110
-
111
-
1
+ import { isHttpRecoverable, PollingError } from "../errors";
2
+ import { IDataSynchronizer } from "./IDataSynchronizer";
3
+ import { ILogger } from "../logging/ILogger";
4
+ import Configuration from "../Configuration";
5
+ import { EventName, PollingErrorHandler, ProcessStreamResponse, StreamResponseEventType } from "./types";
6
+ import Requestor from "./Requestor";
7
+ import { httpErrorMessage } from "../utils/http";
8
+ import { IUser } from "../options/IUser";
9
+
10
+ export default class PollingDataSynchronizer implements IDataSynchronizer {
11
+ private stopped = false;
12
+
13
+ private logger?: ILogger;
14
+
15
+ private pollingInterval: number;
16
+
17
+ private user: IUser | undefined;
18
+
19
+ private timeoutHandle: any;
20
+
21
+ constructor(
22
+ config: Configuration,
23
+ private readonly requestor: Requestor,
24
+ private readonly getStoreTimestamp: () => number,
25
+ private readonly listeners: Map<EventName, ProcessStreamResponse>,
26
+ private readonly errorHandler?: PollingErrorHandler,
27
+ ) {
28
+ this.logger = config.logger;
29
+ this.pollingInterval = config.pollingInterval;
30
+ this.user = config.user;
31
+ }
32
+
33
+ private poll(resolve?: () => void, reject?: () => void) {
34
+ if (this.stopped) {
35
+ return;
36
+ }
37
+
38
+ const startTime = Date.now();
39
+ this.logger?.debug('Polling for feature flag and segments updates');
40
+ this.requestor.requestData(this.getStoreTimestamp(), this.user, async (err, body) => {
41
+ const elapsed = Date.now() - startTime;
42
+ const sleepFor = Math.max(this.pollingInterval - elapsed, 0);
43
+
44
+ this.logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor);
45
+ if (err) {
46
+ const {status} = err;
47
+ if (status && !isHttpRecoverable(status)) {
48
+ const message = httpErrorMessage(err, 'polling request');
49
+ this.logger?.error(message);
50
+ this.errorHandler?.(new PollingError(message, status));
51
+ // It is not recoverable, return and do not trigger another
52
+ // poll.
53
+ reject?.();
54
+ return;
55
+ }
56
+ this.logger?.warn(httpErrorMessage(err, 'polling request', 'will retry'));
57
+ // Falling through, there was some type of error, we need to trigger
58
+ // a new poll.
59
+ this.timeoutHandle = setTimeout(() => {
60
+ this.poll(resolve, reject);
61
+ }, sleepFor);
62
+ } else {
63
+ let featureFlags = [];
64
+ let userKeyId = this.user?.keyId!;
65
+ let processStreamResponse: ProcessStreamResponse | undefined = this.listeners.get('patch');
66
+
67
+ if (body) {
68
+ const message = JSON.parse(body);
69
+ if (message.messageType === 'data-sync') {
70
+ switch (message.data.eventType) {
71
+ case StreamResponseEventType.patch:
72
+ processStreamResponse = this.listeners.get('patch');
73
+ break;
74
+ case StreamResponseEventType.full:
75
+ processStreamResponse = this.listeners.get('put');
76
+ break;
77
+ }
78
+
79
+ ({featureFlags, userKeyId} = message.data);
80
+ }
81
+ }
82
+
83
+ const data = processStreamResponse?.deserializeData?.(featureFlags);
84
+ await processStreamResponse?.processJson?.(userKeyId, data);
85
+ resolve?.();
86
+ // Falling through, there was some type of error, we need to trigger
87
+ // a new poll.
88
+ this.timeoutHandle = setTimeout(() => {
89
+ this.poll();
90
+ }, sleepFor);
91
+ }
92
+ });
93
+ }
94
+
95
+ async identify(user: IUser): Promise<void> {
96
+ this.user = {...user};
97
+ if (this.timeoutHandle) {
98
+ clearTimeout(this.timeoutHandle);
99
+ this.timeoutHandle = undefined;
100
+ }
101
+
102
+ return new Promise((resolve, reject) => {
103
+ this.poll(resolve, reject);
104
+ });
105
+ }
106
+
107
+ close(): void {
108
+ this.stop();
109
+ }
110
+
111
+ start(): void {
112
+ this.poll();
113
+ }
114
+
115
+ stop(): void {
116
+ if (this.timeoutHandle) {
117
+ clearTimeout(this.timeoutHandle);
118
+ this.timeoutHandle = undefined;
119
+ }
120
+ this.stopped = true;
121
+ }
122
+ }
123
+
124
+
125
+
@@ -1,61 +1,61 @@
1
- import { IRequestor } from "./IRequestor";
2
- import Configuration from "../Configuration";
3
- import { IInfo } from "../platform/IInfo";
4
- import { IRequestOptions, IRequests, IResponse } from "../platform/requests";
5
- import { StreamingError } from "../errors";
6
- import { defaultHeaders } from "../utils/http";
7
-
8
- /**
9
- * @internal
10
- */
11
- export default class Requestor implements IRequestor {
12
- private readonly headers: Record<string, string>;
13
-
14
- private readonly uri: string;
15
-
16
- constructor(
17
- sdkKey: string,
18
- config: Configuration,
19
- info: IInfo,
20
- private readonly requests: IRequests,
21
- ) {
22
- this.headers = defaultHeaders(sdkKey, info);
23
- this.uri = config.pollingUri;
24
- }
25
-
26
- /**
27
- * Perform a request and utilize the ETag cache. The ETags are cached in the
28
- * requestor instance.
29
- */
30
- private async request(
31
- requestUrl: string,
32
- options: IRequestOptions,
33
- ): Promise<{
34
- res: IResponse;
35
- body: string;
36
- }> {
37
- const res = await this.requests.fetch(requestUrl, options);
38
-
39
- const body = await res.text();
40
-
41
- return {res, body};
42
- }
43
-
44
- async requestData(timestamp: number, payload: any, cb: (err: any, body: any) => void) {
45
- const options: IRequestOptions = {
46
- method: 'POST',
47
- headers: this.headers,
48
- body: JSON.stringify(payload)
49
- };
50
- try {
51
- const {res, body} = await this.request(`${ this.uri }?timestamp=${ timestamp ?? 0 }`, options);
52
- if (res.status !== 200 && res.status !== 304) {
53
- const err = new StreamingError(`Unexpected status code: ${ res.status }`, res.status);
54
- return cb(err, undefined);
55
- }
56
- return cb(undefined, res.status === 304 ? null : body);
57
- } catch (err) {
58
- return cb(err, undefined);
59
- }
60
- }
61
- }
1
+ import { IRequestor } from "./IRequestor";
2
+ import Configuration from "../Configuration";
3
+ import { IInfo } from "../platform/IInfo";
4
+ import { IRequestOptions, IRequests, IResponse } from "../platform/requests";
5
+ import { StreamingError } from "../errors";
6
+ import { defaultHeaders } from "../utils/http";
7
+
8
+ /**
9
+ * @internal
10
+ */
11
+ export default class Requestor implements IRequestor {
12
+ private readonly headers: Record<string, string>;
13
+
14
+ private readonly uri: string;
15
+
16
+ constructor(
17
+ sdkKey: string,
18
+ config: Configuration,
19
+ info: IInfo,
20
+ private readonly requests: IRequests,
21
+ ) {
22
+ this.headers = defaultHeaders(sdkKey, info);
23
+ this.uri = config.pollingUri;
24
+ }
25
+
26
+ /**
27
+ * Perform a request and utilize the ETag cache. The ETags are cached in the
28
+ * requestor instance.
29
+ */
30
+ private async request(
31
+ requestUrl: string,
32
+ options: IRequestOptions,
33
+ ): Promise<{
34
+ res: IResponse;
35
+ body: string;
36
+ }> {
37
+ const res = await this.requests.fetch(requestUrl, options);
38
+
39
+ const body = await res.text();
40
+
41
+ return {res, body};
42
+ }
43
+
44
+ async requestData(timestamp: number, payload: any, cb: (err: any, body: any) => void) {
45
+ const options: IRequestOptions = {
46
+ method: 'POST',
47
+ headers: this.headers,
48
+ body: JSON.stringify(payload)
49
+ };
50
+ try {
51
+ const {res, body} = await this.request(`${ this.uri }?timestamp=${ timestamp ?? 0 }`, options);
52
+ if (res.status !== 200 && res.status !== 304) {
53
+ const err = new StreamingError(`Unexpected status code: ${ res.status }`, res.status);
54
+ return cb(err, undefined);
55
+ }
56
+ return cb(undefined, res.status === 304 ? null : body);
57
+ } catch (err) {
58
+ return cb(err, undefined);
59
+ }
60
+ }
61
+ }
@@ -1,74 +1,78 @@
1
- import { IDataSynchronizer } from "./IDataSynchronizer";
2
- import ClientContext from "../options/ClientContext";
3
- import { EventName, ProcessStreamResponse } from "./types";
4
- import { ILogger } from "../logging/ILogger";
5
- import { IWebSocketWithEvents } from "../platform/IWebSocket";
6
- import { IUser } from "../options/IUser";
7
-
8
- class WebSocketDataSynchronizer implements IDataSynchronizer {
9
- private socket?: IWebSocketWithEvents;
10
- private readonly logger?: ILogger;
11
-
12
- private connectionAttemptStartTime?: number;
13
-
14
- constructor(
15
- sdkKey: string,
16
- user: IUser,
17
- clientContext: ClientContext,
18
- socket: IWebSocketWithEvents,
19
- private readonly getStoreTimestamp: () => number,
20
- private readonly listeners: Map<EventName, ProcessStreamResponse>,
21
- webSocketPingInterval: number
22
- ) {
23
- const {logger, streamingUri} = clientContext;
24
-
25
- this.logger = logger;
26
- this.socket = socket;
27
- this.socket.config({
28
- sdkKey,
29
- streamingUri,
30
- pingInterval: webSocketPingInterval,
31
- user,
32
- logger,
33
- getStoreTimestamp
34
- });
35
-
36
- this.listeners.forEach(({deserializeData, processJson}, eventName) => {
37
- this.socket?.addListener(eventName, (event) => {
38
- this.logger?.debug(`Received ${ eventName } event`);
39
-
40
- if (event?.data) {
41
- const {featureFlags, userKeyId} = event.data;
42
- const data = deserializeData(featureFlags);
43
- processJson(userKeyId, data);
44
- }
45
- });
46
- })
47
- }
48
-
49
- identify(user: IUser): void {
50
- this.socket?.identify(user);
51
- }
52
-
53
- start(): void {
54
- this.logConnectionStarted();
55
-
56
- this.socket?.connect();
57
- }
58
-
59
- private logConnectionStarted() {
60
- this.connectionAttemptStartTime = Date.now();
61
- this.logger?.info(`Stream connection attempt StartTime ${ this.connectionAttemptStartTime }`);
62
- }
63
-
64
- close(): void {
65
- this.stop();
66
- }
67
-
68
- stop(): void {
69
- this.socket?.close();
70
- this.socket = undefined;
71
- }
72
- }
73
-
1
+ import { IDataSynchronizer } from "./IDataSynchronizer";
2
+ import ClientContext from "../options/ClientContext";
3
+ import { EventName, ProcessStreamResponse } from "./types";
4
+ import { ILogger } from "../logging/ILogger";
5
+ import { IWebSocketWithEvents } from "../platform/IWebSocket";
6
+ import { IUser } from "../options/IUser";
7
+
8
+ class WebSocketDataSynchronizer implements IDataSynchronizer {
9
+ private socket?: IWebSocketWithEvents;
10
+ private readonly logger?: ILogger;
11
+ private identifyResolve?: () => void;
12
+
13
+ private connectionAttemptStartTime?: number;
14
+
15
+ constructor(
16
+ sdkKey: string,
17
+ user: IUser,
18
+ clientContext: ClientContext,
19
+ socket: IWebSocketWithEvents,
20
+ private readonly getStoreTimestamp: () => number,
21
+ private readonly listeners: Map<EventName, ProcessStreamResponse>,
22
+ webSocketPingInterval: number
23
+ ) {
24
+ const {logger, streamingUri} = clientContext;
25
+
26
+ this.logger = logger;
27
+ this.socket = socket;
28
+ this.socket.config({
29
+ sdkKey,
30
+ streamingUri,
31
+ pingInterval: webSocketPingInterval,
32
+ user,
33
+ logger,
34
+ getStoreTimestamp
35
+ });
36
+
37
+ this.listeners.forEach(({deserializeData, processJson}, eventName) => {
38
+ this.socket?.addListener(eventName, async (event) => {
39
+ this.logger?.debug(`Received ${ eventName } event`);
40
+
41
+ if (event?.data) {
42
+ const {featureFlags, userKeyId} = event.data;
43
+ const data = deserializeData(featureFlags);
44
+ await processJson(userKeyId, data);
45
+ this.identifyResolve?.();
46
+ this.identifyResolve = undefined;
47
+ }
48
+ });
49
+ })
50
+ }
51
+
52
+ async identify(user: IUser): Promise<void> {
53
+ this.socket?.identify(user);
54
+ return new Promise(resolve => this.identifyResolve = resolve);
55
+ }
56
+
57
+ start(): void {
58
+ this.logConnectionStarted();
59
+
60
+ this.socket?.connect();
61
+ }
62
+
63
+ private logConnectionStarted() {
64
+ this.connectionAttemptStartTime = Date.now();
65
+ this.logger?.info(`Stream connection attempt StartTime ${ this.connectionAttemptStartTime }`);
66
+ }
67
+
68
+ close(): void {
69
+ this.stop();
70
+ }
71
+
72
+ stop(): void {
73
+ this.socket?.close();
74
+ this.socket = undefined;
75
+ }
76
+ }
77
+
74
78
  export default WebSocketDataSynchronizer;
@@ -1,9 +1,9 @@
1
- export * from './DataSyncMode';
2
- export * from './IDataSynchronizer';
3
- export * from './IRequestor';
4
- export * from './NullDataSynchronizer';
5
- export * from './PollingDataSynchronizer';
6
- export * from './Requestor';
7
- export * from './types';
8
- export * from './utils';
1
+ export * from './DataSyncMode';
2
+ export * from './IDataSynchronizer';
3
+ export * from './IRequestor';
4
+ export * from './NullDataSynchronizer';
5
+ export * from './PollingDataSynchronizer';
6
+ export * from './Requestor';
7
+ export * from './types';
8
+ export * from './utils';
9
9
  export * from './WebSocketDataSynchronizer';
@@ -1,20 +1,20 @@
1
- import { PollingError } from "../errors";
2
- import { IFlag } from "../evaluation/data/IFlag";
3
-
4
- export type PollingErrorHandler = (err: PollingError) => void;
5
-
6
- export enum StreamResponseEventType {
7
- full = 'full',
8
- patch = 'patch'
9
- }
10
-
11
- export interface IStreamResponse {
12
- eventType: StreamResponseEventType,
13
- featureFlags: IFlag[]
14
- }
15
-
16
- export type EventName = 'delete' | 'patch' | 'ping' | 'put';
17
- export type ProcessStreamResponse = {
18
- deserializeData: (flags: IFlag[]) => any;
19
- processJson: (userKeyId: string, json: any) => void;
1
+ import { PollingError } from "../errors";
2
+ import { IFlag } from "../evaluation/data/IFlag";
3
+
4
+ export type PollingErrorHandler = (err: PollingError) => void;
5
+
6
+ export enum StreamResponseEventType {
7
+ full = 'full',
8
+ patch = 'patch'
9
+ }
10
+
11
+ export interface IStreamResponse {
12
+ eventType: StreamResponseEventType,
13
+ featureFlags: IFlag[]
14
+ }
15
+
16
+ export type EventName = 'delete' | 'patch' | 'ping' | 'put';
17
+ export type ProcessStreamResponse = {
18
+ deserializeData: (flags: IFlag[]) => any;
19
+ processJson: (userKeyId: string, json: any) => Promise<void>;
20
20
  };
@@ -1,32 +1,32 @@
1
- /********************** encode text begin *****************************/
2
- const alphabet: Record<string, string> = {
3
- "0": "Q",
4
- "1": "B",
5
- "2": "W",
6
- "3": "S",
7
- "4": "P",
8
- "5": "H",
9
- "6": "D",
10
- "7": "X",
11
- "8": "Z",
12
- "9": "U",
13
- }
14
-
15
- function encodeNumber(param: number, length: number): string {
16
- var s = "000000000000" + param;
17
- const numberWithLeadingZeros = s.slice(s.length - length);
18
- return numberWithLeadingZeros.split('').map(n => alphabet[n]).join('');
19
- }
20
-
21
- // generate connection token
22
- export function generateConnectionToken(text: string): string {
23
- text = text.replace(/=*$/, '');
24
- const timestamp = Date.now();
25
- const timestampCode = encodeNumber(timestamp, timestamp.toString().length);
26
- // get random number less than the length of the text as the start point, and it must be greater or equal to 2
27
- const start = Math.max(Math.floor(Math.random() * text.length), 2);
28
-
29
- return `${ encodeNumber(start, 3) }${ encodeNumber(timestampCode.length, 2) }${ text.slice(0, start) }${ timestampCode }${ text.slice(start) }`;
30
- }
31
-
1
+ /********************** encode text begin *****************************/
2
+ const alphabet: Record<string, string> = {
3
+ "0": "Q",
4
+ "1": "B",
5
+ "2": "W",
6
+ "3": "S",
7
+ "4": "P",
8
+ "5": "H",
9
+ "6": "D",
10
+ "7": "X",
11
+ "8": "Z",
12
+ "9": "U",
13
+ }
14
+
15
+ function encodeNumber(param: number, length: number): string {
16
+ var s = "000000000000" + param;
17
+ const numberWithLeadingZeros = s.slice(s.length - length);
18
+ return numberWithLeadingZeros.split('').map(n => alphabet[n]).join('');
19
+ }
20
+
21
+ // generate connection token
22
+ export function generateConnectionToken(text: string): string {
23
+ text = text.replace(/=*$/, '');
24
+ const timestamp = Date.now();
25
+ const timestampCode = encodeNumber(timestamp, timestamp.toString().length);
26
+ // get random number less than the length of the text as the start point, and it must be greater or equal to 2
27
+ const start = Math.max(Math.floor(Math.random() * text.length), 2);
28
+
29
+ return `${ encodeNumber(start, 3) }${ encodeNumber(timestampCode.length, 2) }${ text.slice(0, start) }${ timestampCode }${ text.slice(start) }`;
30
+ }
31
+
32
32
  /********************** encode text end *****************************/