@croct/sdk 0.11.0-alpha → 0.11.0-alpha.2

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 (296) hide show
  1. package/activeRecord.js +1 -0
  2. package/activeRecord.js.map +1 -0
  3. package/base64Url.js +1 -0
  4. package/base64Url.js.map +1 -0
  5. package/cache/cache.js +1 -0
  6. package/cache/cache.js.map +1 -0
  7. package/cache/fallbackCache.js +1 -0
  8. package/cache/fallbackCache.js.map +1 -0
  9. package/cache/inMemoryCache.js +1 -0
  10. package/cache/inMemoryCache.js.map +1 -0
  11. package/cache/index.js +1 -0
  12. package/cache/index.js.map +1 -0
  13. package/cache/localStorageCache.js +1 -0
  14. package/cache/localStorageCache.js.map +1 -0
  15. package/channel/beaconSocketChannel.js +1 -0
  16. package/channel/beaconSocketChannel.js.map +1 -0
  17. package/channel/channel.js +1 -0
  18. package/channel/channel.js.map +1 -0
  19. package/channel/encodedChannel.js +1 -0
  20. package/channel/encodedChannel.js.map +1 -0
  21. package/channel/guaranteedChannel.js +1 -0
  22. package/channel/guaranteedChannel.js.map +1 -0
  23. package/channel/index.js +1 -0
  24. package/channel/index.js.map +1 -0
  25. package/channel/queuedChannel.js +1 -0
  26. package/channel/queuedChannel.js.map +1 -0
  27. package/channel/retryChannel.js +1 -0
  28. package/channel/retryChannel.js.map +1 -0
  29. package/channel/sandboxChannel.js +1 -0
  30. package/channel/sandboxChannel.js.map +1 -0
  31. package/channel/socketChannel.js +1 -0
  32. package/channel/socketChannel.js.map +1 -0
  33. package/cid/assigner.js +1 -0
  34. package/cid/assigner.js.map +1 -0
  35. package/cid/cachedAssigner.js +1 -0
  36. package/cid/cachedAssigner.js.map +1 -0
  37. package/cid/fixedAssigner.js +1 -0
  38. package/cid/fixedAssigner.js.map +1 -0
  39. package/cid/index.js +1 -0
  40. package/cid/index.js.map +1 -0
  41. package/cid/remoteAssigner.js +1 -0
  42. package/cid/remoteAssigner.js.map +1 -0
  43. package/constants.d.ts +5 -5
  44. package/constants.js +2 -1
  45. package/constants.js.map +1 -0
  46. package/container.js +1 -0
  47. package/container.js.map +1 -0
  48. package/contentFetcher.js +1 -0
  49. package/contentFetcher.js.map +1 -0
  50. package/context.js +1 -0
  51. package/context.js.map +1 -0
  52. package/error.js +1 -0
  53. package/error.js.map +1 -0
  54. package/evaluator.js +1 -0
  55. package/evaluator.js.map +1 -0
  56. package/eventManager.js +1 -0
  57. package/eventManager.js.map +1 -0
  58. package/facade/contentFetcherFacade.js +1 -0
  59. package/facade/contentFetcherFacade.js.map +1 -0
  60. package/facade/evaluatorFacade.js +1 -0
  61. package/facade/evaluatorFacade.js.map +1 -0
  62. package/facade/index.js +1 -0
  63. package/facade/index.js.map +1 -0
  64. package/facade/sdkFacade.js +1 -0
  65. package/facade/sdkFacade.js.map +1 -0
  66. package/facade/sessionFacade.js +1 -0
  67. package/facade/sessionFacade.js.map +1 -0
  68. package/facade/sessionPatch.js +1 -0
  69. package/facade/sessionPatch.js.map +1 -0
  70. package/facade/trackerFacade.js +1 -0
  71. package/facade/trackerFacade.js.map +1 -0
  72. package/facade/userFacade.js +1 -0
  73. package/facade/userFacade.js.map +1 -0
  74. package/facade/userPatch.js +1 -0
  75. package/facade/userPatch.js.map +1 -0
  76. package/index.js +1 -0
  77. package/index.js.map +1 -0
  78. package/logging/consoleLogger.js +1 -0
  79. package/logging/consoleLogger.js.map +1 -0
  80. package/logging/index.js +1 -0
  81. package/logging/index.js.map +1 -0
  82. package/logging/logger.js +1 -0
  83. package/logging/logger.js.map +1 -0
  84. package/logging/namespacedLogger.js +1 -0
  85. package/logging/namespacedLogger.js.map +1 -0
  86. package/logging/nullLogger.js +1 -0
  87. package/logging/nullLogger.js.map +1 -0
  88. package/namespacedStorage.js +1 -0
  89. package/namespacedStorage.js.map +1 -0
  90. package/package.json +3 -2
  91. package/patch.js +1 -0
  92. package/patch.js.map +1 -0
  93. package/queue/capacityRestrictedQueue.js +1 -0
  94. package/queue/capacityRestrictedQueue.js.map +1 -0
  95. package/queue/inMemoryQueue.js +1 -0
  96. package/queue/inMemoryQueue.js.map +1 -0
  97. package/queue/index.js +1 -0
  98. package/queue/index.js.map +1 -0
  99. package/queue/monitoredQueue.js +1 -0
  100. package/queue/monitoredQueue.js.map +1 -0
  101. package/queue/persistentQueue.js +1 -0
  102. package/queue/persistentQueue.js.map +1 -0
  103. package/queue/queue.js +1 -0
  104. package/queue/queue.js.map +1 -0
  105. package/retry/arbitraryPolicy.js +1 -0
  106. package/retry/arbitraryPolicy.js.map +1 -0
  107. package/retry/backoffPolicy.js +1 -0
  108. package/retry/backoffPolicy.js.map +1 -0
  109. package/retry/index.js +1 -0
  110. package/retry/index.js.map +1 -0
  111. package/retry/maxAttemptsPolicy.js +1 -0
  112. package/retry/maxAttemptsPolicy.js.map +1 -0
  113. package/retry/neverPolicy.js +1 -0
  114. package/retry/neverPolicy.js.map +1 -0
  115. package/retry/policy.js +1 -0
  116. package/retry/policy.js.map +1 -0
  117. package/schema/attributeSchema.js +1 -0
  118. package/schema/attributeSchema.js.map +1 -0
  119. package/schema/contentFetcherSchemas.js +1 -0
  120. package/schema/contentFetcherSchemas.js.map +1 -0
  121. package/schema/contentSchemas.js +1 -0
  122. package/schema/contentSchemas.js.map +1 -0
  123. package/schema/contextSchemas.js +1 -0
  124. package/schema/contextSchemas.js.map +1 -0
  125. package/schema/ecommerceSchemas.js +1 -0
  126. package/schema/ecommerceSchemas.js.map +1 -0
  127. package/schema/evaluatorSchemas.js +1 -0
  128. package/schema/evaluatorSchemas.js.map +1 -0
  129. package/schema/eventSchemas.js +1 -0
  130. package/schema/eventSchemas.js.map +1 -0
  131. package/schema/index.js +1 -0
  132. package/schema/index.js.map +1 -0
  133. package/schema/loggerSchema.js +1 -0
  134. package/schema/loggerSchema.js.map +1 -0
  135. package/schema/operationSchemas.js +1 -0
  136. package/schema/operationSchemas.js.map +1 -0
  137. package/schema/sdkFacadeSchemas.js +1 -0
  138. package/schema/sdkFacadeSchemas.js.map +1 -0
  139. package/schema/sdkSchemas.js +1 -0
  140. package/schema/sdkSchemas.js.map +1 -0
  141. package/schema/tokenSchema.js +1 -0
  142. package/schema/tokenSchema.js.map +1 -0
  143. package/schema/userSchema.js +1 -0
  144. package/schema/userSchema.js.map +1 -0
  145. package/sdk.js +1 -0
  146. package/sdk.js.map +1 -0
  147. package/sdkEvents.js +1 -0
  148. package/sdkEvents.js.map +1 -0
  149. package/sourceLocation.js +1 -0
  150. package/sourceLocation.js.map +1 -0
  151. package/src/activeRecord.ts +150 -0
  152. package/src/base64Url.ts +18 -0
  153. package/src/cache/cache.ts +15 -0
  154. package/src/cache/fallbackCache.ts +29 -0
  155. package/src/cache/inMemoryCache.ts +21 -0
  156. package/src/cache/index.ts +4 -0
  157. package/src/cache/localStorageCache.ts +85 -0
  158. package/src/channel/beaconSocketChannel.ts +153 -0
  159. package/src/channel/channel.ts +20 -0
  160. package/src/channel/encodedChannel.ts +21 -0
  161. package/src/channel/guaranteedChannel.ts +131 -0
  162. package/src/channel/index.ts +8 -0
  163. package/src/channel/queuedChannel.ts +112 -0
  164. package/src/channel/retryChannel.ts +90 -0
  165. package/src/channel/sandboxChannel.ts +43 -0
  166. package/src/channel/socketChannel.ts +217 -0
  167. package/src/cid/assigner.ts +3 -0
  168. package/src/cid/cachedAssigner.ts +35 -0
  169. package/src/cid/fixedAssigner.ts +13 -0
  170. package/src/cid/index.ts +4 -0
  171. package/src/cid/remoteAssigner.ts +47 -0
  172. package/src/constants.ts +6 -0
  173. package/src/container.ts +388 -0
  174. package/src/contentFetcher.ts +226 -0
  175. package/src/context.ts +137 -0
  176. package/src/error.ts +31 -0
  177. package/src/evaluator.ts +251 -0
  178. package/src/eventManager.ts +53 -0
  179. package/src/facade/contentFetcherFacade.ts +69 -0
  180. package/src/facade/evaluatorFacade.ts +152 -0
  181. package/src/facade/index.ts +7 -0
  182. package/src/facade/sdkFacade.ts +291 -0
  183. package/src/facade/sessionFacade.ts +14 -0
  184. package/src/facade/sessionPatch.ts +32 -0
  185. package/src/facade/trackerFacade.ts +98 -0
  186. package/src/facade/userFacade.ts +26 -0
  187. package/src/facade/userPatch.ts +32 -0
  188. package/src/index.ts +4 -0
  189. package/src/logging/consoleLogger.ts +37 -0
  190. package/src/logging/index.ts +4 -0
  191. package/src/logging/logger.ts +13 -0
  192. package/src/logging/namespacedLogger.ts +32 -0
  193. package/src/logging/nullLogger.ts +19 -0
  194. package/src/namespacedStorage.ts +69 -0
  195. package/src/patch.ts +64 -0
  196. package/src/queue/capacityRestrictedQueue.ts +44 -0
  197. package/src/queue/inMemoryQueue.ts +43 -0
  198. package/src/queue/index.ts +5 -0
  199. package/src/queue/monitoredQueue.ts +168 -0
  200. package/src/queue/persistentQueue.ts +84 -0
  201. package/src/queue/queue.ts +15 -0
  202. package/src/retry/arbitraryPolicy.ts +21 -0
  203. package/src/retry/backoffPolicy.ts +84 -0
  204. package/src/retry/index.ts +5 -0
  205. package/src/retry/maxAttemptsPolicy.ts +28 -0
  206. package/src/retry/neverPolicy.ts +11 -0
  207. package/src/retry/policy.ts +5 -0
  208. package/src/schema/attributeSchema.ts +6 -0
  209. package/src/schema/contentFetcherSchemas.ts +23 -0
  210. package/src/schema/contentSchemas.ts +44 -0
  211. package/src/schema/contextSchemas.ts +5 -0
  212. package/src/schema/ecommerceSchemas.ts +179 -0
  213. package/src/schema/evaluatorSchemas.ts +11 -0
  214. package/src/schema/eventSchemas.ts +150 -0
  215. package/src/schema/index.ts +11 -0
  216. package/src/schema/loggerSchema.ts +12 -0
  217. package/src/schema/operationSchemas.ts +102 -0
  218. package/src/schema/sdkFacadeSchemas.ts +44 -0
  219. package/src/schema/sdkSchemas.ts +49 -0
  220. package/src/schema/tokenSchema.ts +42 -0
  221. package/src/schema/userSchema.ts +184 -0
  222. package/src/sdk.ts +174 -0
  223. package/src/sdkEvents.ts +15 -0
  224. package/src/sourceLocation.ts +85 -0
  225. package/src/tab.ts +148 -0
  226. package/src/token/cachedTokenStore.ts +34 -0
  227. package/src/token/inMemoryTokenStore.ts +13 -0
  228. package/src/token/index.ts +4 -0
  229. package/src/token/replicatedTokenStore.ts +21 -0
  230. package/src/token/token.ts +164 -0
  231. package/src/tracker.ts +460 -0
  232. package/src/trackingEvents.ts +456 -0
  233. package/src/transformer.ts +7 -0
  234. package/src/utilityTypes.ts +3 -0
  235. package/src/uuid.ts +43 -0
  236. package/src/validation/arrayType.ts +71 -0
  237. package/src/validation/booleanType.ts +22 -0
  238. package/src/validation/functionType.ts +22 -0
  239. package/src/validation/index.ts +12 -0
  240. package/src/validation/jsonType.ts +157 -0
  241. package/src/validation/mixedSchema.ts +7 -0
  242. package/src/validation/nullType.ts +22 -0
  243. package/src/validation/numberType.ts +59 -0
  244. package/src/validation/objectType.ts +138 -0
  245. package/src/validation/schema.ts +21 -0
  246. package/src/validation/stringType.ts +118 -0
  247. package/src/validation/unionType.ts +53 -0
  248. package/src/validation/violation.ts +23 -0
  249. package/tab.js +1 -0
  250. package/tab.js.map +1 -0
  251. package/token/cachedTokenStore.js +1 -0
  252. package/token/cachedTokenStore.js.map +1 -0
  253. package/token/inMemoryTokenStore.js +1 -0
  254. package/token/inMemoryTokenStore.js.map +1 -0
  255. package/token/index.js +1 -0
  256. package/token/index.js.map +1 -0
  257. package/token/replicatedTokenStore.js +1 -0
  258. package/token/replicatedTokenStore.js.map +1 -0
  259. package/token/token.js +1 -0
  260. package/token/token.js.map +1 -0
  261. package/tracker.js +1 -0
  262. package/tracker.js.map +1 -0
  263. package/trackingEvents.js +1 -0
  264. package/trackingEvents.js.map +1 -0
  265. package/transformer.js +1 -0
  266. package/transformer.js.map +1 -0
  267. package/utilityTypes.js +1 -0
  268. package/utilityTypes.js.map +1 -0
  269. package/uuid.js +1 -0
  270. package/uuid.js.map +1 -0
  271. package/validation/arrayType.js +1 -0
  272. package/validation/arrayType.js.map +1 -0
  273. package/validation/booleanType.js +1 -0
  274. package/validation/booleanType.js.map +1 -0
  275. package/validation/functionType.js +1 -0
  276. package/validation/functionType.js.map +1 -0
  277. package/validation/index.js +1 -0
  278. package/validation/index.js.map +1 -0
  279. package/validation/jsonType.js +1 -0
  280. package/validation/jsonType.js.map +1 -0
  281. package/validation/mixedSchema.js +1 -0
  282. package/validation/mixedSchema.js.map +1 -0
  283. package/validation/nullType.js +1 -0
  284. package/validation/nullType.js.map +1 -0
  285. package/validation/numberType.js +1 -0
  286. package/validation/numberType.js.map +1 -0
  287. package/validation/objectType.js +1 -0
  288. package/validation/objectType.js.map +1 -0
  289. package/validation/schema.js +1 -0
  290. package/validation/schema.js.map +1 -0
  291. package/validation/stringType.js +1 -0
  292. package/validation/stringType.js.map +1 -0
  293. package/validation/unionType.js +1 -0
  294. package/validation/unionType.js.map +1 -0
  295. package/validation/violation.js +1 -0
  296. package/validation/violation.js.map +1 -0
@@ -0,0 +1,85 @@
1
+ import {CacheListener, ObservableCache} from './cache';
2
+
3
+ export class LocalStorageCache implements ObservableCache {
4
+ private readonly storage: Storage;
5
+
6
+ private readonly key: string;
7
+
8
+ private value: string|null;
9
+
10
+ private readonly listeners: CacheListener[] = [];
11
+
12
+ public constructor(storage: Storage, key: string) {
13
+ this.storage = storage;
14
+ this.key = key;
15
+ this.value = storage.getItem(key);
16
+ }
17
+
18
+ public static autoSync(cache: LocalStorageCache): (() => void) {
19
+ const listener = cache.sync.bind(cache);
20
+
21
+ window.addEventListener('storage', listener);
22
+
23
+ return (): void => window.removeEventListener('storage', listener);
24
+ }
25
+
26
+ public get(): string|null {
27
+ return this.value;
28
+ }
29
+
30
+ public put(value: string): void {
31
+ this.storage.setItem(this.key, value);
32
+
33
+ if (this.value !== value) {
34
+ this.value = value;
35
+ this.notifyChange(value);
36
+ }
37
+ }
38
+
39
+ public clear(): void {
40
+ this.storage.removeItem(this.key);
41
+
42
+ if (this.value !== null) {
43
+ this.value = null;
44
+ this.notifyChange(null);
45
+ }
46
+ }
47
+
48
+ public addListener(listener: CacheListener): void {
49
+ if (!this.listeners.includes(listener)) {
50
+ this.listeners.push(listener);
51
+ }
52
+ }
53
+
54
+ public removeListener(listener: CacheListener): void {
55
+ const index = this.listeners.indexOf(listener);
56
+
57
+ if (index > -1) {
58
+ this.listeners.splice(index, 1);
59
+ }
60
+ }
61
+
62
+ private notifyChange(value: string|null): void {
63
+ this.listeners.forEach(listener => listener(value));
64
+ }
65
+
66
+ private sync(event: StorageEvent): void {
67
+ if (event.storageArea !== this.storage || (event.key !== null && event.key !== this.key)) {
68
+ // Ignore unrelated changes
69
+ return;
70
+ }
71
+
72
+ /*
73
+ * Retrieving the value from the store rather than the event ensures
74
+ * the cache will be in sync with the latest value set.
75
+ * In case of cascading changes, it prevents notifying listeners
76
+ * about intermediate states already outdated at this point.
77
+ */
78
+ const value = this.storage.getItem(this.key);
79
+
80
+ if (this.value !== value) {
81
+ this.value = value;
82
+ this.notifyChange(value);
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,153 @@
1
+ import {ChannelListener, DuplexChannel} from './channel';
2
+ import {Envelope} from './guaranteedChannel';
3
+ import {Logger, LoggerFactory, NullLogger} from '../logging';
4
+ import {CidAssigner} from '../cid';
5
+
6
+ export interface DuplexChannelFactory {
7
+ (url: string, logger: Logger): DuplexChannel<string, string>;
8
+ }
9
+
10
+ type Configuration = {
11
+ logger?: Logger,
12
+ loggerFactory?: LoggerFactory,
13
+ tokenParameter: string,
14
+ trackerEndpointUrl: string,
15
+ channelFactory: DuplexChannelFactory,
16
+ cidAssigner: CidAssigner,
17
+ cidParameter: string,
18
+ };
19
+
20
+ type Violation = {
21
+ message: string,
22
+ path: string,
23
+ };
24
+
25
+ type Confirmation = {
26
+ receiptId: string | null,
27
+ violations?: Violation[],
28
+ };
29
+
30
+ export class BeaconSocketChannel implements DuplexChannel<string, Envelope<string, string>> {
31
+ private readonly socketFactory: DuplexChannelFactory;
32
+
33
+ private readonly logger: Logger;
34
+
35
+ private readonly loggerFactory: LoggerFactory;
36
+
37
+ private readonly cidAssigner: CidAssigner;
38
+
39
+ private readonly cidParameter: string;
40
+
41
+ private readonly tokenParameter: string;
42
+
43
+ private readonly trackerEndpointUrl: string;
44
+
45
+ private readonly listeners: Array<ChannelListener<string>> = [];
46
+
47
+ private socketChannel?: DuplexChannel<string, string>;
48
+
49
+ private token?: string;
50
+
51
+ private connectionIndex = 0;
52
+
53
+ public constructor(configuration: Configuration) {
54
+ this.socketFactory = configuration.channelFactory;
55
+ this.logger = configuration.logger ?? new NullLogger();
56
+ this.loggerFactory = configuration.loggerFactory ?? ((): Logger => new NullLogger());
57
+ this.cidAssigner = configuration.cidAssigner;
58
+ this.cidParameter = configuration.cidParameter;
59
+ this.trackerEndpointUrl = configuration.trackerEndpointUrl;
60
+ this.tokenParameter = configuration.tokenParameter;
61
+ this.notify = this.notify.bind(this);
62
+ }
63
+
64
+ public async publish({id: receiptId, message}: Envelope<string, string>): Promise<void> {
65
+ const {token, timestamp, context, payload} = JSON.parse(message);
66
+
67
+ if (this.token !== token || this.socketChannel === undefined) {
68
+ if (this.socketChannel !== undefined) {
69
+ this.logger.info('Connection no longer valid for current message.');
70
+
71
+ this.socketChannel.unsubscribe(this.notify);
72
+
73
+ await this.socketChannel.close();
74
+ }
75
+
76
+ this.token = token;
77
+ this.socketChannel = await this.createSocketChannel(token);
78
+ }
79
+
80
+ return this.socketChannel.publish(
81
+ JSON.stringify({
82
+ receiptId: receiptId,
83
+ originalTime: timestamp,
84
+ departureTime: Date.now(),
85
+ context: context,
86
+ payload: payload,
87
+ }),
88
+ );
89
+ }
90
+
91
+ private async createSocketChannel(token?: string): Promise<DuplexChannel<string, string>> {
92
+ const endpoint = new URL(this.trackerEndpointUrl);
93
+
94
+ endpoint.searchParams.append(this.cidParameter, await this.cidAssigner.assignCid());
95
+
96
+ if (token !== undefined) {
97
+ endpoint.searchParams.append(this.tokenParameter, token);
98
+ }
99
+
100
+ const channel: DuplexChannel<string, string> = this.socketFactory(
101
+ endpoint.toString(),
102
+ this.loggerFactory(`WebSocket#${this.connectionIndex}`),
103
+ );
104
+
105
+ this.connectionIndex += 1;
106
+
107
+ channel.subscribe(this.notify);
108
+
109
+ return channel;
110
+ }
111
+
112
+ public subscribe(listener: ChannelListener<string>): void {
113
+ if (!this.listeners.includes(listener)) {
114
+ this.listeners.push(listener);
115
+ }
116
+ }
117
+
118
+ public unsubscribe(listener: ChannelListener<string>): void {
119
+ const index = this.listeners.indexOf(listener);
120
+
121
+ if (index >= 0) {
122
+ this.listeners.splice(index, 1);
123
+ }
124
+ }
125
+
126
+ private notify(message: string): void {
127
+ let confirmation: Confirmation;
128
+
129
+ try {
130
+ confirmation = JSON.parse(message);
131
+ } catch {
132
+ this.logger.error('Invalid JSON message received.');
133
+
134
+ return;
135
+ }
136
+
137
+ const {violations = [], receiptId} = confirmation;
138
+
139
+ violations.forEach(violation => this.logger.error(violation.message));
140
+
141
+ if (receiptId !== null) {
142
+ this.listeners.forEach(dispatch => dispatch(receiptId));
143
+ }
144
+ }
145
+
146
+ public close(): Promise<void> {
147
+ if (this.socketChannel === undefined) {
148
+ return Promise.resolve();
149
+ }
150
+
151
+ return this.socketChannel.close();
152
+ }
153
+ }
@@ -0,0 +1,20 @@
1
+ export interface Closeable {
2
+ close(): Promise<void>;
3
+ }
4
+
5
+ export interface OutputChannel<O> extends Closeable {
6
+ publish(message: O): Promise<void>;
7
+ }
8
+
9
+ export type ChannelListener<T> = {
10
+ (message: T): void,
11
+ };
12
+
13
+ export interface InputChannel<I> extends Closeable {
14
+ subscribe(listener: ChannelListener<I>): void;
15
+
16
+ unsubscribe(listener: ChannelListener<I>): void;
17
+ }
18
+
19
+ export interface DuplexChannel<I, O> extends InputChannel<I>, OutputChannel<O> {
20
+ }
@@ -0,0 +1,21 @@
1
+ import {OutputChannel} from './channel';
2
+ import {Transformer} from '../transformer';
3
+
4
+ export class EncodedChannel<D, E> implements OutputChannel<D> {
5
+ private readonly encode: Transformer<D, E>;
6
+
7
+ private readonly channel: OutputChannel<E>;
8
+
9
+ public constructor(channel: OutputChannel<E>, encoder: Transformer<D, E>) {
10
+ this.channel = channel;
11
+ this.encode = encoder;
12
+ }
13
+
14
+ public publish(message: D): Promise<void> {
15
+ return this.encode(message).then((result => this.channel.publish(result)));
16
+ }
17
+
18
+ public close(): Promise<void> {
19
+ return this.channel.close();
20
+ }
21
+ }
@@ -0,0 +1,131 @@
1
+ import {Logger, NullLogger} from '../logging';
2
+ import {DuplexChannel, OutputChannel} from './channel';
3
+
4
+ export type MessageStamper<M, S> = {
5
+ generate(message: M): S,
6
+ };
7
+
8
+ export class TimeStamper implements MessageStamper<any, string> {
9
+ public generate(): string {
10
+ return String(Date.now());
11
+ }
12
+ }
13
+
14
+ export type Envelope<M, S> = {
15
+ id: S,
16
+ message: M,
17
+ };
18
+
19
+ type Options = {
20
+ ackTimeout: number,
21
+ };
22
+
23
+ type Configuration<M, S> = Partial<Options> & {
24
+ channel: DuplexChannel<S, Envelope<M, S>>,
25
+ stamper: MessageStamper<M, S>,
26
+ logger?: Logger,
27
+ };
28
+
29
+ export class GuaranteedChannel<M, S> implements OutputChannel<M> {
30
+ private readonly channel: DuplexChannel<S, Envelope<M, S>>;
31
+
32
+ private readonly stamper: MessageStamper<M, S>;
33
+
34
+ private readonly logger: Logger;
35
+
36
+ private readonly options: Options;
37
+
38
+ private closed = false;
39
+
40
+ public constructor({channel, logger, stamper, ...options}: Configuration<M, S>) {
41
+ this.channel = channel;
42
+ this.logger = logger ?? new NullLogger();
43
+ this.stamper = stamper;
44
+ this.options = {
45
+ ...options,
46
+ ackTimeout: options.ackTimeout ?? 5000,
47
+ };
48
+ }
49
+
50
+ public publish(message: M): Promise<void> {
51
+ if (this.closed) {
52
+ return Promise.reject(new Error('Channel is closed.'));
53
+ }
54
+
55
+ return new Promise((resolve, reject): void => {
56
+ const id = this.stamper.generate(message);
57
+
58
+ let timeoutTimer: number;
59
+ let closeWatcher: number;
60
+ let confirmed = false;
61
+ const start = Date.now();
62
+
63
+ const acknowledge = (response: any): void => {
64
+ if (response === id) {
65
+ confirmed = true;
66
+
67
+ const elapsed = Date.now() - start;
68
+
69
+ window.clearTimeout(timeoutTimer);
70
+ window.clearInterval(closeWatcher);
71
+
72
+ this.logger.debug(`Delivery confirmed #${id}, elapsed ${elapsed}ms.`);
73
+
74
+ this.channel.unsubscribe(acknowledge);
75
+
76
+ resolve();
77
+ }
78
+ };
79
+
80
+ this.channel.subscribe(acknowledge);
81
+
82
+ const abort = (error: any): void => {
83
+ window.clearTimeout(timeoutTimer);
84
+ window.clearInterval(closeWatcher);
85
+
86
+ this.logger.error(`Failed to send message #${id}`);
87
+
88
+ this.channel.unsubscribe(acknowledge);
89
+
90
+ reject(error);
91
+ };
92
+
93
+ const wait = (): void => {
94
+ if (confirmed) {
95
+ return;
96
+ }
97
+
98
+ closeWatcher = window.setInterval(
99
+ () => {
100
+ if (this.closed) {
101
+ // Cancel delay immediately when the channel is closed
102
+ abort(new Error('Connection deliberately closed.'));
103
+ }
104
+ },
105
+ 0,
106
+ );
107
+
108
+ this.logger.debug(`Waiting confirmation #${id}...`);
109
+
110
+ timeoutTimer = window.setTimeout(
111
+ () => {
112
+ abort(new Error('Maximum confirmation time reached.'));
113
+ },
114
+ this.options.ackTimeout,
115
+ );
116
+ };
117
+
118
+ this.logger.debug(`Sending message #${id}...`);
119
+
120
+ this.channel
121
+ .publish({id: id, message: message})
122
+ .then(wait, abort);
123
+ });
124
+ }
125
+
126
+ public close(): Promise<void> {
127
+ this.closed = true;
128
+
129
+ return this.channel.close();
130
+ }
131
+ }
@@ -0,0 +1,8 @@
1
+ export * from './channel';
2
+ export {BeaconSocketChannel, DuplexChannelFactory} from './beaconSocketChannel';
3
+ export {EncodedChannel} from './encodedChannel';
4
+ export {GuaranteedChannel} from './guaranteedChannel';
5
+ export {QueuedChannel} from './queuedChannel';
6
+ export {RetryChannel} from './retryChannel';
7
+ export {SandboxChannel} from './sandboxChannel';
8
+ export {SocketChannel} from './socketChannel';
@@ -0,0 +1,112 @@
1
+ import {OutputChannel} from './channel';
2
+ import {Queue} from '../queue';
3
+ import {Logger, NullLogger} from '../logging';
4
+
5
+ export class QueuedChannel<T> implements OutputChannel<T> {
6
+ private readonly channel: OutputChannel<T>;
7
+
8
+ private readonly queue: Queue<T>;
9
+
10
+ private readonly logger: Logger;
11
+
12
+ private pending?: Promise<void>;
13
+
14
+ private closed = false;
15
+
16
+ public constructor(channel: OutputChannel<T>, queue: Queue<T>, logger?: Logger) {
17
+ this.channel = channel;
18
+ this.queue = queue;
19
+ this.logger = logger ?? new NullLogger();
20
+ }
21
+
22
+ public flush(): Promise<void> {
23
+ if (this.pending === undefined) {
24
+ return this.requeue();
25
+ }
26
+
27
+ return this.pending.catch(this.requeue.bind(this));
28
+ }
29
+
30
+ public publish(message: T): Promise<void> {
31
+ if (this.closed) {
32
+ return Promise.reject(new Error('Channel is closed.'));
33
+ }
34
+
35
+ if (this.queue.length() >= this.queue.getCapacity()) {
36
+ this.logger.warn('The queue is full, message rejected.');
37
+
38
+ return Promise.reject(new Error('The queue is full.'));
39
+ }
40
+
41
+ if (this.pending === undefined) {
42
+ this.pending = this.queue.isEmpty()
43
+ ? Promise.resolve()
44
+ : Promise.reject(new Error('The queue must be flushed.'));
45
+ }
46
+
47
+ this.enqueue(message);
48
+
49
+ this.pending = this.pending.then(
50
+ () => this.channel
51
+ .publish(message)
52
+ .then(this.dequeue.bind(this)),
53
+ );
54
+
55
+ return this.pending;
56
+ }
57
+
58
+ private enqueue(message: T): void {
59
+ this.logger.debug('Enqueueing message...');
60
+ this.logger.debug(`Queue length: ${this.queue.length() + 1}`);
61
+
62
+ this.queue.push(message);
63
+ }
64
+
65
+ private dequeue(): void {
66
+ this.logger.debug('Dequeuing message...');
67
+ this.logger.debug(`Queue length: ${Math.max(0, this.queue.length() - 1)}`);
68
+
69
+ this.queue.shift();
70
+ }
71
+
72
+ private requeue(): Promise<void> {
73
+ if (this.closed) {
74
+ return Promise.reject(new Error('Channel is closed.'));
75
+ }
76
+
77
+ this.pending = Promise.resolve();
78
+
79
+ if (this.queue.isEmpty()) {
80
+ return this.pending;
81
+ }
82
+
83
+ const length = this.queue.length();
84
+
85
+ this.logger.debug('Requeuing messages...');
86
+ this.logger.debug(`Queue length: ${length}`);
87
+
88
+ for (const message of this.queue.all()) {
89
+ this.pending = this.pending.then(
90
+ () => this.channel
91
+ .publish(message)
92
+ .then(this.dequeue.bind(this)),
93
+ );
94
+ }
95
+
96
+ return this.pending;
97
+ }
98
+
99
+ public async close(): Promise<void> {
100
+ this.closed = true;
101
+
102
+ await this.channel.close();
103
+
104
+ if (this.pending !== undefined) {
105
+ try {
106
+ await this.pending;
107
+ } catch {
108
+ // suppress errors
109
+ }
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,90 @@
1
+ import {OutputChannel} from './channel';
2
+ import {Logger, NullLogger} from '../logging';
3
+ import {RetryPolicy} from '../retry';
4
+
5
+ type Configuration<T> = {
6
+ channel: OutputChannel<T>,
7
+ retryPolicy: RetryPolicy<T>,
8
+ logger?: Logger,
9
+ };
10
+
11
+ export class RetryChannel<T> implements OutputChannel<T> {
12
+ private readonly channel: OutputChannel<T>;
13
+
14
+ private readonly retryPolicy: RetryPolicy<T>;
15
+
16
+ private readonly logger: Logger;
17
+
18
+ private closed = false;
19
+
20
+ public constructor({channel, retryPolicy, logger}: Configuration<T>) {
21
+ this.channel = channel;
22
+ this.retryPolicy = retryPolicy;
23
+ this.logger = logger ?? new NullLogger();
24
+ }
25
+
26
+ public publish(message: T): Promise<void> {
27
+ if (this.closed) {
28
+ return Promise.reject(new Error('The channel is closed.'));
29
+ }
30
+
31
+ return this.channel
32
+ .publish(message)
33
+ .catch(error => this.retry(message, error));
34
+ }
35
+
36
+ public async retry(message: T, error: unknown): Promise<void> {
37
+ let attempt = 0;
38
+
39
+ while (this.retryPolicy.shouldRetry(attempt, message, error)) {
40
+ if (this.closed) {
41
+ throw new Error('Connection deliberately closed.');
42
+ }
43
+
44
+ const delay = this.retryPolicy.getDelay(attempt);
45
+
46
+ this.logger.debug(`Retry attempt ${attempt + 1}`);
47
+
48
+ if (delay > 0) {
49
+ this.logger.debug(`Retry attempt delayed in ${delay}ms`);
50
+
51
+ await new Promise<void>((resolve, reject): void => {
52
+ const closeWatcher = window.setInterval(
53
+ () => {
54
+ if (this.closed) {
55
+ // Cancel delay immediately when the channel is closed
56
+ window.clearInterval(closeWatcher);
57
+
58
+ reject(new Error('Connection deliberately closed.'));
59
+ }
60
+ },
61
+ 0,
62
+ );
63
+
64
+ window.setTimeout(
65
+ (): void => {
66
+ window.clearInterval(closeWatcher);
67
+
68
+ resolve();
69
+ },
70
+ delay,
71
+ );
72
+ });
73
+ }
74
+
75
+ try {
76
+ return await this.channel.publish(message);
77
+ } catch {
78
+ attempt += 1;
79
+ }
80
+ }
81
+
82
+ throw new Error('Maximum retry attempts reached.');
83
+ }
84
+
85
+ public close(): Promise<void> {
86
+ this.closed = true;
87
+
88
+ return this.channel.close();
89
+ }
90
+ }
@@ -0,0 +1,43 @@
1
+ import {ChannelListener, DuplexChannel} from './channel';
2
+
3
+ export class SandboxChannel<I, O> implements DuplexChannel<I, O> {
4
+ private readonly listeners: Array<ChannelListener<I>> = [];
5
+
6
+ public readonly messages: O[] = [];
7
+
8
+ private closed = false;
9
+
10
+ public publish(message: O): Promise<void> {
11
+ this.messages.push(message);
12
+
13
+ return Promise.resolve();
14
+ }
15
+
16
+ public notify(message: I): void {
17
+ this.listeners.forEach(dispatch => dispatch(message));
18
+ }
19
+
20
+ public subscribe(listener: ChannelListener<I>): void {
21
+ if (!this.listeners.includes(listener)) {
22
+ this.listeners.push(listener);
23
+ }
24
+ }
25
+
26
+ public unsubscribe(listener: ChannelListener<I>): void {
27
+ const index = this.listeners.indexOf(listener);
28
+
29
+ if (index >= 0) {
30
+ this.listeners.splice(index, 1);
31
+ }
32
+ }
33
+
34
+ public close(): Promise<void> {
35
+ this.closed = true;
36
+
37
+ return Promise.resolve();
38
+ }
39
+
40
+ public isClosed(): boolean {
41
+ return this.closed;
42
+ }
43
+ }