@croct/sdk 0.10.0 → 0.11.0-alpha.1
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.
- package/.src/activeRecord.ts +150 -0
- package/.src/base64Url.ts +18 -0
- package/.src/cache/cache.ts +15 -0
- package/.src/cache/fallbackCache.ts +29 -0
- package/.src/cache/inMemoryCache.ts +21 -0
- package/.src/cache/index.ts +4 -0
- package/.src/cache/localStorageCache.ts +85 -0
- package/.src/channel/beaconSocketChannel.ts +153 -0
- package/.src/channel/channel.ts +20 -0
- package/.src/channel/encodedChannel.ts +21 -0
- package/.src/channel/guaranteedChannel.ts +131 -0
- package/.src/channel/index.ts +8 -0
- package/.src/channel/queuedChannel.ts +112 -0
- package/.src/channel/retryChannel.ts +90 -0
- package/.src/channel/sandboxChannel.ts +43 -0
- package/.src/channel/socketChannel.ts +217 -0
- package/.src/cid/assigner.ts +3 -0
- package/.src/cid/cachedAssigner.ts +35 -0
- package/.src/cid/fixedAssigner.ts +13 -0
- package/.src/cid/index.ts +4 -0
- package/.src/cid/remoteAssigner.ts +47 -0
- package/.src/constants.ts +6 -0
- package/.src/container.ts +388 -0
- package/.src/contentFetcher.ts +226 -0
- package/.src/context.ts +137 -0
- package/.src/error.ts +31 -0
- package/.src/evaluator.ts +251 -0
- package/.src/eventManager.ts +53 -0
- package/.src/facade/contentFetcherFacade.ts +69 -0
- package/.src/facade/evaluatorFacade.ts +152 -0
- package/.src/facade/index.ts +7 -0
- package/.src/facade/sdkFacade.ts +291 -0
- package/.src/facade/sessionFacade.ts +14 -0
- package/.src/facade/sessionPatch.ts +32 -0
- package/.src/facade/trackerFacade.ts +98 -0
- package/.src/facade/userFacade.ts +26 -0
- package/.src/facade/userPatch.ts +32 -0
- package/.src/index.ts +4 -0
- package/.src/logging/consoleLogger.ts +37 -0
- package/.src/logging/index.ts +4 -0
- package/.src/logging/logger.ts +13 -0
- package/.src/logging/namespacedLogger.ts +32 -0
- package/.src/logging/nullLogger.ts +19 -0
- package/.src/namespacedStorage.ts +69 -0
- package/.src/patch.ts +64 -0
- package/.src/queue/capacityRestrictedQueue.ts +44 -0
- package/.src/queue/inMemoryQueue.ts +43 -0
- package/.src/queue/index.ts +5 -0
- package/.src/queue/monitoredQueue.ts +168 -0
- package/.src/queue/persistentQueue.ts +84 -0
- package/.src/queue/queue.ts +15 -0
- package/.src/retry/arbitraryPolicy.ts +21 -0
- package/.src/retry/backoffPolicy.ts +84 -0
- package/.src/retry/index.ts +5 -0
- package/.src/retry/maxAttemptsPolicy.ts +28 -0
- package/.src/retry/neverPolicy.ts +11 -0
- package/.src/retry/policy.ts +5 -0
- package/.src/schema/attributeSchema.ts +6 -0
- package/.src/schema/contentFetcherSchemas.ts +23 -0
- package/.src/schema/contentSchemas.ts +44 -0
- package/.src/schema/contextSchemas.ts +5 -0
- package/.src/schema/ecommerceSchemas.ts +179 -0
- package/.src/schema/evaluatorSchemas.ts +11 -0
- package/.src/schema/eventSchemas.ts +150 -0
- package/.src/schema/index.ts +11 -0
- package/.src/schema/loggerSchema.ts +12 -0
- package/.src/schema/operationSchemas.ts +102 -0
- package/.src/schema/sdkFacadeSchemas.ts +44 -0
- package/.src/schema/sdkSchemas.ts +49 -0
- package/.src/schema/tokenSchema.ts +42 -0
- package/.src/schema/userSchema.ts +184 -0
- package/.src/sdk.ts +174 -0
- package/.src/sdkEvents.ts +15 -0
- package/.src/sourceLocation.ts +85 -0
- package/.src/tab.ts +148 -0
- package/.src/token/cachedTokenStore.ts +34 -0
- package/.src/token/inMemoryTokenStore.ts +13 -0
- package/.src/token/index.ts +4 -0
- package/.src/token/replicatedTokenStore.ts +21 -0
- package/.src/token/token.ts +164 -0
- package/.src/tracker.ts +460 -0
- package/.src/trackingEvents.ts +456 -0
- package/.src/transformer.ts +7 -0
- package/.src/utilityTypes.ts +3 -0
- package/.src/uuid.ts +43 -0
- package/.src/validation/arrayType.ts +71 -0
- package/.src/validation/booleanType.ts +22 -0
- package/.src/validation/functionType.ts +22 -0
- package/.src/validation/index.ts +12 -0
- package/.src/validation/jsonType.ts +157 -0
- package/.src/validation/mixedSchema.ts +7 -0
- package/.src/validation/nullType.ts +22 -0
- package/.src/validation/numberType.ts +59 -0
- package/.src/validation/objectType.ts +138 -0
- package/.src/validation/schema.ts +21 -0
- package/.src/validation/stringType.ts +118 -0
- package/.src/validation/unionType.ts +53 -0
- package/.src/validation/violation.ts +23 -0
- package/activeRecord.js +33 -36
- package/base64Url.js +1 -0
- package/cache/cache.js +1 -0
- package/cache/fallbackCache.js +16 -32
- package/cache/inMemoryCache.js +10 -10
- package/cache/index.js +2 -1
- package/cache/localStorageCache.js +25 -25
- package/channel/beaconSocketChannel.d.ts +1 -1
- package/channel/beaconSocketChannel.js +50 -79
- package/channel/channel.d.ts +1 -1
- package/channel/channel.js +1 -0
- package/channel/encodedChannel.js +9 -10
- package/channel/guaranteedChannel.d.ts +4 -4
- package/channel/guaranteedChannel.js +42 -43
- package/channel/index.js +2 -1
- package/channel/queuedChannel.js +36 -64
- package/channel/retryChannel.d.ts +1 -1
- package/channel/retryChannel.js +45 -77
- package/channel/sandboxChannel.js +18 -18
- package/channel/socketChannel.d.ts +4 -4
- package/channel/socketChannel.js +78 -79
- package/cid/assigner.js +1 -0
- package/cid/cachedAssigner.js +16 -27
- package/cid/fixedAssigner.js +6 -6
- package/cid/index.js +2 -1
- package/cid/remoteAssigner.js +24 -36
- package/constants.d.ts +6 -5
- package/constants.js +7 -5
- package/container.d.ts +13 -6
- package/container.js +153 -168
- package/contentFetcher.d.ts +59 -0
- package/contentFetcher.js +130 -0
- package/context.d.ts +3 -3
- package/context.js +37 -38
- package/error.js +3 -2
- package/evaluator.d.ts +33 -24
- package/evaluator.js +127 -117
- package/eventManager.d.ts +1 -1
- package/eventManager.js +15 -15
- package/facade/contentFetcherFacade.d.ts +27 -0
- package/facade/contentFetcherFacade.js +41 -0
- package/facade/evaluatorFacade.d.ts +13 -3
- package/facade/evaluatorFacade.js +58 -72
- package/facade/index.js +1 -0
- package/facade/sdkFacade.d.ts +10 -3
- package/facade/sdkFacade.js +130 -141
- package/facade/sessionFacade.js +7 -7
- package/facade/sessionPatch.js +10 -13
- package/facade/trackerFacade.js +33 -38
- package/facade/userFacade.js +11 -11
- package/facade/userPatch.js +10 -13
- package/index.js +3 -2
- package/logging/consoleLogger.js +19 -35
- package/logging/index.js +2 -1
- package/logging/logger.js +1 -0
- package/logging/namespacedLogger.js +15 -15
- package/logging/nullLogger.js +11 -13
- package/namespacedStorage.js +31 -47
- package/package.json +13 -16
- package/patch.d.ts +1 -1
- package/patch.js +1 -0
- package/queue/capacityRestrictedQueue.js +18 -18
- package/queue/inMemoryQueue.js +23 -28
- package/queue/index.js +2 -1
- package/queue/monitoredQueue.d.ts +2 -2
- package/queue/monitoredQueue.js +40 -40
- package/queue/persistentQueue.js +34 -38
- package/queue/queue.js +1 -0
- package/retry/arbitraryPolicy.js +9 -10
- package/retry/backoffPolicy.d.ts +1 -1
- package/retry/backoffPolicy.js +12 -13
- package/retry/index.js +2 -1
- package/retry/maxAttemptsPolicy.js +8 -8
- package/retry/neverPolicy.js +7 -9
- package/retry/policy.js +1 -0
- package/schema/attributeSchema.js +2 -1
- package/schema/contentFetcherSchemas.d.ts +2 -0
- package/schema/contentFetcherSchemas.js +23 -0
- package/schema/contentSchemas.js +2 -1
- package/schema/contextSchemas.js +2 -1
- package/schema/ecommerceSchemas.js +2 -1
- package/schema/evaluatorSchemas.d.ts +2 -0
- package/schema/{evaluationSchemas.js → evaluatorSchemas.js} +4 -3
- package/schema/eventSchemas.js +6 -7
- package/schema/index.d.ts +2 -1
- package/schema/index.js +4 -2
- package/schema/loggerSchema.js +2 -1
- package/schema/operationSchemas.js +9 -8
- package/schema/sdkFacadeSchemas.js +10 -6
- package/schema/sdkSchemas.js +9 -5
- package/schema/tokenSchema.js +6 -4
- package/schema/userSchema.js +3 -2
- package/sdk.d.ts +9 -3
- package/sdk.js +82 -127
- package/sdkEvents.d.ts +3 -3
- package/sdkEvents.js +1 -0
- package/sourceLocation.d.ts +3 -3
- package/sourceLocation.js +14 -14
- package/tab.d.ts +5 -5
- package/tab.js +51 -80
- package/token/cachedTokenStore.js +10 -10
- package/token/inMemoryTokenStore.js +8 -8
- package/token/index.js +2 -1
- package/token/replicatedTokenStore.js +8 -8
- package/token/token.d.ts +9 -5
- package/token/token.js +64 -57
- package/tracker.d.ts +4 -4
- package/tracker.js +146 -122
- package/trackingEvents.d.ts +36 -36
- package/trackingEvents.js +13 -6
- package/transformer.js +2 -1
- package/utilityTypes.d.ts +2 -2
- package/utilityTypes.js +1 -0
- package/uuid.js +10 -7
- package/validation/arrayType.d.ts +2 -2
- package/validation/arrayType.js +30 -27
- package/validation/booleanType.js +12 -15
- package/validation/functionType.js +12 -15
- package/validation/index.js +2 -1
- package/validation/jsonType.d.ts +2 -2
- package/validation/jsonType.js +62 -80
- package/validation/mixedSchema.js +5 -7
- package/validation/nullType.js +12 -15
- package/validation/numberType.d.ts +1 -1
- package/validation/numberType.js +24 -22
- package/validation/objectType.d.ts +1 -1
- package/validation/objectType.js +62 -72
- package/validation/schema.js +7 -10
- package/validation/stringType.d.ts +1 -1
- package/validation/stringType.js +37 -47
- package/validation/unionType.js +28 -77
- package/validation/violation.js +2 -2
- package/schema/evaluationSchemas.d.ts +0 -2
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {JsonStructure, JsonValue} from '@croct/json';
|
|
2
|
+
import {Operation, Patch} from './patch';
|
|
3
|
+
import {TrackingEvent} from './trackingEvents';
|
|
4
|
+
import {
|
|
5
|
+
addOperation,
|
|
6
|
+
clearOperation,
|
|
7
|
+
combineOperation,
|
|
8
|
+
decrementOperation,
|
|
9
|
+
incrementOperation,
|
|
10
|
+
mergeOperation,
|
|
11
|
+
setOperation,
|
|
12
|
+
unsetOperation,
|
|
13
|
+
removeOperation,
|
|
14
|
+
} from './schema';
|
|
15
|
+
|
|
16
|
+
const operationSchema = {
|
|
17
|
+
add: addOperation,
|
|
18
|
+
set: setOperation,
|
|
19
|
+
merge: mergeOperation,
|
|
20
|
+
combine: combineOperation,
|
|
21
|
+
increment: incrementOperation,
|
|
22
|
+
decrement: decrementOperation,
|
|
23
|
+
clear: clearOperation,
|
|
24
|
+
unset: unsetOperation,
|
|
25
|
+
remove: removeOperation,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export abstract class ActiveRecord<T extends TrackingEvent> {
|
|
29
|
+
private readonly operations: Operation[] = [];
|
|
30
|
+
|
|
31
|
+
public set(value: JsonValue): this;
|
|
32
|
+
|
|
33
|
+
public set(property: string, value: JsonValue): this;
|
|
34
|
+
|
|
35
|
+
public set(propertyOrValue: string | JsonValue, value?: JsonValue): this {
|
|
36
|
+
if (typeof propertyOrValue === 'string') {
|
|
37
|
+
return this.pushOperation({
|
|
38
|
+
type: 'set',
|
|
39
|
+
path: propertyOrValue,
|
|
40
|
+
value: value as JsonValue,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return this.pushOperation({
|
|
45
|
+
type: 'set',
|
|
46
|
+
path: '.',
|
|
47
|
+
value: propertyOrValue,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public add(property: string, value: JsonValue): this {
|
|
52
|
+
return this.pushOperation({
|
|
53
|
+
type: 'add',
|
|
54
|
+
path: property,
|
|
55
|
+
value: value,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public combine(property: string, value: JsonValue): this {
|
|
60
|
+
return this.pushOperation({
|
|
61
|
+
type: 'combine',
|
|
62
|
+
path: property,
|
|
63
|
+
value: value,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public merge(value: JsonStructure): this;
|
|
68
|
+
|
|
69
|
+
public merge(property: string, value: JsonStructure): this;
|
|
70
|
+
|
|
71
|
+
public merge(propertyOrValue: string | JsonStructure, value?: JsonStructure): this {
|
|
72
|
+
if (typeof propertyOrValue === 'string') {
|
|
73
|
+
return this.pushOperation({
|
|
74
|
+
type: 'merge',
|
|
75
|
+
path: propertyOrValue,
|
|
76
|
+
value: value as JsonStructure,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return this.pushOperation({
|
|
81
|
+
type: 'merge',
|
|
82
|
+
path: '.',
|
|
83
|
+
value: propertyOrValue,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public increment(property: string, amount = 1): this {
|
|
88
|
+
return this.pushOperation({
|
|
89
|
+
type: 'increment',
|
|
90
|
+
path: property,
|
|
91
|
+
value: amount,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public decrement(property: string, amount = 1): this {
|
|
96
|
+
return this.pushOperation({
|
|
97
|
+
type: 'decrement',
|
|
98
|
+
path: property,
|
|
99
|
+
value: amount,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public clear(property: string): this {
|
|
104
|
+
return this.pushOperation({
|
|
105
|
+
type: 'clear',
|
|
106
|
+
path: property,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public unset(property: string): this {
|
|
111
|
+
return this.pushOperation({
|
|
112
|
+
type: 'unset',
|
|
113
|
+
path: property,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public remove(property: string, value: JsonValue): this {
|
|
118
|
+
return this.pushOperation({
|
|
119
|
+
type: 'remove',
|
|
120
|
+
path: property,
|
|
121
|
+
value: value,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private pushOperation(operation: Operation): this {
|
|
126
|
+
const {type, ...data} = operation;
|
|
127
|
+
|
|
128
|
+
operationSchema[type].validate(data);
|
|
129
|
+
|
|
130
|
+
this.operations.push(operation);
|
|
131
|
+
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected reset(): this {
|
|
136
|
+
this.operations.splice(0);
|
|
137
|
+
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public abstract save(): Promise<T>;
|
|
142
|
+
|
|
143
|
+
protected isDirty(): boolean {
|
|
144
|
+
return this.operations.length > 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected buildPatch(): Patch {
|
|
148
|
+
return {operations: this.operations.slice()};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function base64Unescape(value: string): string {
|
|
2
|
+
return (value + '==='.slice((value.length + 3) % 4)).replace(/-/g, '+').replace(/_/g, '/');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function base64Escape(value: string): string {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/\+/g, '-')
|
|
8
|
+
.replace(/\//g, '_')
|
|
9
|
+
.replace(/=/g, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function base64UrlEncode(value: string): string {
|
|
13
|
+
return base64Escape(window.btoa(value));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function base64UrlDecode(value: string): string {
|
|
17
|
+
return window.atob(base64Unescape(value));
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface Cache {
|
|
2
|
+
get(): string|null;
|
|
3
|
+
put(value: string): void;
|
|
4
|
+
clear(): void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CacheListener {
|
|
8
|
+
(value: string|null): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ObservableCache extends Cache {
|
|
12
|
+
addListener(listener: CacheListener): void;
|
|
13
|
+
|
|
14
|
+
removeListener(listener: CacheListener): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {Cache} from './cache';
|
|
2
|
+
|
|
3
|
+
export class FallbackCache implements Cache {
|
|
4
|
+
private readonly caches: Cache[];
|
|
5
|
+
|
|
6
|
+
public constructor(...caches: Cache[]) {
|
|
7
|
+
this.caches = caches;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public get(): string|null {
|
|
11
|
+
for (const cache of this.caches) {
|
|
12
|
+
const value = cache.get();
|
|
13
|
+
|
|
14
|
+
if (value !== null) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public put(value: string): void {
|
|
23
|
+
this.caches.forEach(cache => cache.put(value));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public clear(): void {
|
|
27
|
+
this.caches.forEach(cache => cache.clear());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {Cache} from './cache';
|
|
2
|
+
|
|
3
|
+
export class InMemoryCache implements Cache {
|
|
4
|
+
private cache?: string;
|
|
5
|
+
|
|
6
|
+
public constructor(cache?: string) {
|
|
7
|
+
this.cache = cache;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public get(): string | null {
|
|
11
|
+
return this.cache ?? null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public put(value: string): void {
|
|
15
|
+
this.cache = value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public clear(): void {
|
|
19
|
+
delete this.cache;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -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';
|