@streamlayer/sdk-web-api 0.22.0 → 0.23.0
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.
|
@@ -17,5 +17,7 @@ export declare const $organizationSettings: ($enabled: ReadableAtom<'on' | undef
|
|
|
17
17
|
brandDefaults?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").BrandDefaults | undefined;
|
|
18
18
|
pub?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").JWK | undefined;
|
|
19
19
|
getstream?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").GetStreamSettingsClient | undefined;
|
|
20
|
+
publicName?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").PublicName | undefined;
|
|
21
|
+
analyticsVersion?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").AnalyticsVersion | undefined;
|
|
20
22
|
} | undefined, any>;
|
|
21
23
|
export declare const $organizationAdvertising: ($enabled: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").Advertising | undefined, any>;
|
package/lib/grpc/queries/user.js
CHANGED
|
@@ -13,10 +13,11 @@ export const $user = ($userToken, transport) => {
|
|
|
13
13
|
});
|
|
14
14
|
};
|
|
15
15
|
export const bypassLogin = (transport) => {
|
|
16
|
-
const { client } = transport.createPromiseClient(Users, { method: 'bypassAuth' });
|
|
16
|
+
const { client, createRequestOptions } = transport.createPromiseClient(Users, { method: 'bypassAuth' });
|
|
17
|
+
const contextValues = createRequestOptions({ retryAttempts: 0 });
|
|
17
18
|
// inviterKey to do add to payload, ONLY after implement it on backend!
|
|
18
19
|
return async ({ userKey, schema, init, inviterKey }) => {
|
|
19
|
-
const res = await client.bypassAuth({ userKey, schema, init });
|
|
20
|
+
const res = await client.bypassAuth({ userKey, schema, init }, { contextValues });
|
|
20
21
|
if (res.meta) {
|
|
21
22
|
res.meta.inviterKey = inviterKey;
|
|
22
23
|
}
|
|
@@ -24,8 +25,9 @@ export const bypassLogin = (transport) => {
|
|
|
24
25
|
};
|
|
25
26
|
};
|
|
26
27
|
export const bypassAuth = (transport, params) => {
|
|
27
|
-
const { client } = transport.createPromiseClient(Users, { method: 'bypassAuth' });
|
|
28
|
-
|
|
28
|
+
const { client, createRequestOptions } = transport.createPromiseClient(Users, { method: 'bypassAuth' });
|
|
29
|
+
const contextValues = createRequestOptions({ retryAttempts: 0 });
|
|
30
|
+
return client.bypassAuth(params, { contextValues });
|
|
29
31
|
};
|
|
30
32
|
export const $userSettings = ($userToken, // sl user token
|
|
31
33
|
transport) => {
|
|
@@ -38,6 +40,7 @@ transport) => {
|
|
|
38
40
|
});
|
|
39
41
|
};
|
|
40
42
|
export const register = (transport, phone) => {
|
|
41
|
-
const { client } = transport.createPromiseClient(Users, { method: 'register' });
|
|
42
|
-
|
|
43
|
+
const { client, createRequestOptions } = transport.createPromiseClient(Users, { method: 'register' });
|
|
44
|
+
const contextValues = createRequestOptions({ retryAttempts: 0 });
|
|
45
|
+
return client.register({ id: phone }, { contextValues });
|
|
43
46
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Interceptor } from '@connectrpc/connect';
|
|
2
|
+
/**
|
|
3
|
+
* Retry interceptor
|
|
4
|
+
*
|
|
5
|
+
* This interceptor is used to retry requests in case of network errors.
|
|
6
|
+
* Retries are performed according to the exponential backoff algorithm.
|
|
7
|
+
* Allowing retries for the following error codes:
|
|
8
|
+
* [
|
|
9
|
+
* Code.Unknown,
|
|
10
|
+
* Code.Internal,
|
|
11
|
+
* Code.DeadlineExceeded,
|
|
12
|
+
* Code.ResourceExhausted,
|
|
13
|
+
* Code.FailedPrecondition,
|
|
14
|
+
* Code.Unavailable,
|
|
15
|
+
* Code.DataLoss,
|
|
16
|
+
* ]
|
|
17
|
+
*
|
|
18
|
+
* Retry params:
|
|
19
|
+
* - retryAttempts: number of attempts to retry the request, 0 means no retries
|
|
20
|
+
* - retryDelay: max delay between retries in milliseconds
|
|
21
|
+
*
|
|
22
|
+
* Example:
|
|
23
|
+
* ```ts
|
|
24
|
+
const { client, createRequestOptions, queryKey } = transport.createPromiseClient(Leaderboard, {
|
|
25
|
+
method: 'summary',
|
|
26
|
+
params: [$eventId, $userId],
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
30
|
+
fetcher: async (_, __, eventId, userId) => {
|
|
31
|
+
const contextValues = createRequestOptions({
|
|
32
|
+
retryAttempts: 5,
|
|
33
|
+
retryDelay: 10000,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const res = await client.summary(
|
|
37
|
+
{
|
|
38
|
+
eventId: eventId as unknown as bigint,
|
|
39
|
+
userId: userId as string,
|
|
40
|
+
},
|
|
41
|
+
{ contextValues }
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return res.data?.attributes
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare const retry: Interceptor;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createLogger } from '@streamlayer/sdk-web-logger';
|
|
2
|
+
import { Code, ConnectError } from '@connectrpc/connect';
|
|
3
|
+
import { RequestOptionsKeys } from './transport';
|
|
4
|
+
const allowedRetryFor = new Set([
|
|
5
|
+
Code.Unknown,
|
|
6
|
+
Code.Internal,
|
|
7
|
+
Code.DeadlineExceeded,
|
|
8
|
+
Code.ResourceExhausted,
|
|
9
|
+
Code.FailedPrecondition,
|
|
10
|
+
Code.Unavailable,
|
|
11
|
+
Code.DataLoss,
|
|
12
|
+
]);
|
|
13
|
+
const log = createLogger('grpc:retry');
|
|
14
|
+
/**
|
|
15
|
+
* Retry interceptor
|
|
16
|
+
*
|
|
17
|
+
* This interceptor is used to retry requests in case of network errors.
|
|
18
|
+
* Retries are performed according to the exponential backoff algorithm.
|
|
19
|
+
* Allowing retries for the following error codes:
|
|
20
|
+
* [
|
|
21
|
+
* Code.Unknown,
|
|
22
|
+
* Code.Internal,
|
|
23
|
+
* Code.DeadlineExceeded,
|
|
24
|
+
* Code.ResourceExhausted,
|
|
25
|
+
* Code.FailedPrecondition,
|
|
26
|
+
* Code.Unavailable,
|
|
27
|
+
* Code.DataLoss,
|
|
28
|
+
* ]
|
|
29
|
+
*
|
|
30
|
+
* Retry params:
|
|
31
|
+
* - retryAttempts: number of attempts to retry the request, 0 means no retries
|
|
32
|
+
* - retryDelay: max delay between retries in milliseconds
|
|
33
|
+
*
|
|
34
|
+
* Example:
|
|
35
|
+
* ```ts
|
|
36
|
+
const { client, createRequestOptions, queryKey } = transport.createPromiseClient(Leaderboard, {
|
|
37
|
+
method: 'summary',
|
|
38
|
+
params: [$eventId, $userId],
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
42
|
+
fetcher: async (_, __, eventId, userId) => {
|
|
43
|
+
const contextValues = createRequestOptions({
|
|
44
|
+
retryAttempts: 5,
|
|
45
|
+
retryDelay: 10000,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const res = await client.summary(
|
|
49
|
+
{
|
|
50
|
+
eventId: eventId as unknown as bigint,
|
|
51
|
+
userId: userId as string,
|
|
52
|
+
},
|
|
53
|
+
{ contextValues }
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return res.data?.attributes
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export const retry = (next) => {
|
|
62
|
+
return async (req) => {
|
|
63
|
+
const attempts = req.contextValues.get(RequestOptionsKeys.retryAttempts);
|
|
64
|
+
const minDelay = 300;
|
|
65
|
+
const maxDelay = req.contextValues.get(RequestOptionsKeys.retryDelay);
|
|
66
|
+
if (req.stream || attempts === 0) {
|
|
67
|
+
return next(req);
|
|
68
|
+
}
|
|
69
|
+
log.trace({ url: req.url, attempts, maxDelay }, 'retry options');
|
|
70
|
+
for (let attempt = 0;; attempt++) {
|
|
71
|
+
try {
|
|
72
|
+
return await next(req);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
log.trace({ attempt, error }, 'retry attempt');
|
|
76
|
+
const cErr = ConnectError.from(error);
|
|
77
|
+
if (attempt >= attempts || !(cErr instanceof ConnectError) || !allowedRetryFor.has(cErr.code)) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
// https://aws.amazon.com/ru/blogs/architecture/exponential-backoff-and-jitter/
|
|
81
|
+
const backoff = Math.min(maxDelay, Math.pow(2, attempt) * minDelay);
|
|
82
|
+
const delayMs = Math.round((backoff * (1 + Math.random())) / 2);
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
};
|
package/lib/grpc/transport.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ type NanoqueryObjectType = {
|
|
|
25
25
|
createMutatorStore: NanoqueryReturnType[1];
|
|
26
26
|
utils: NanoqueryReturnType[2];
|
|
27
27
|
};
|
|
28
|
+
export declare const RequestOptionsKeys: {
|
|
29
|
+
retryAttempts: import("@connectrpc/connect").ContextKey<number>;
|
|
30
|
+
retryDelay: import("@connectrpc/connect").ContextKey<number>;
|
|
31
|
+
};
|
|
32
|
+
type RequestOptionsKey = keyof typeof RequestOptionsKeys;
|
|
33
|
+
type RequestOptions = Partial<Record<RequestOptionsKey, (typeof RequestOptionsKeys)[RequestOptionsKey]['defaultValue']>>;
|
|
28
34
|
/**
|
|
29
35
|
* transport wrapper, initialize grpc transport, store headers and connect interceptors
|
|
30
36
|
*/
|
|
@@ -56,6 +62,7 @@ export declare class Transport {
|
|
|
56
62
|
method: keyof T["methods"];
|
|
57
63
|
}) => {
|
|
58
64
|
client: PromiseClient<T>;
|
|
65
|
+
createRequestOptions: (options: RequestOptions) => import("@connectrpc/connect").ContextValues;
|
|
59
66
|
queryKey: ((string | number | true) | import("nanostores").ReadableAtom<(string | number | true) | (false | void | null | undefined)> | import("@nanostores/query").FetcherStore<any, any>)[];
|
|
60
67
|
queryKeyStr: string;
|
|
61
68
|
};
|
package/lib/grpc/transport.js
CHANGED
|
@@ -2,9 +2,15 @@ import { MapStore, createMapStore } from '@streamlayer/sdk-web-interfaces';
|
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
3
|
import { createRouterTransport, createPromiseClient, } from '@connectrpc/connect';
|
|
4
4
|
import { createGrpcWebTransport } from '@connectrpc/connect-web';
|
|
5
|
+
import { createContextValues, createContextKey } from '@connectrpc/connect';
|
|
5
6
|
import { nanoquery } from '@nanostores/query';
|
|
6
7
|
import { __GRPC_DEVTOOLS_EXTENSION__ } from '../utils/devtools';
|
|
7
8
|
import { ServerStreamSubscription } from './subscription';
|
|
9
|
+
import { retry } from './retry';
|
|
10
|
+
export const RequestOptionsKeys = {
|
|
11
|
+
retryAttempts: createContextKey(5, { description: 'Number of attempts to retry' }),
|
|
12
|
+
retryDelay: createContextKey(30000, { description: 'Max delay between retries in milliseconds' }),
|
|
13
|
+
};
|
|
8
14
|
// generate random device id and store it in local storage
|
|
9
15
|
const getDeviceId = () => {
|
|
10
16
|
const deviceId = localStorage.getItem('sl-device-id');
|
|
@@ -48,7 +54,7 @@ export class Transport {
|
|
|
48
54
|
this.nanoquery = { createFetcherStore, createMutatorStore, utils };
|
|
49
55
|
this.transport = createGrpcWebTransport({
|
|
50
56
|
baseUrl: host,
|
|
51
|
-
defaultTimeoutMs:
|
|
57
|
+
defaultTimeoutMs: 10000,
|
|
52
58
|
interceptors: this.interceptors,
|
|
53
59
|
useBinaryFormat: true,
|
|
54
60
|
});
|
|
@@ -127,7 +133,15 @@ export class Transport {
|
|
|
127
133
|
...(Array.isArray(params) ? params : [params]),
|
|
128
134
|
];
|
|
129
135
|
const queryKeyWithoutParams = [service.typeName, methodName.charAt(0).toLowerCase() + methodName.slice(1)];
|
|
130
|
-
|
|
136
|
+
const createRequestOptions = (options) => {
|
|
137
|
+
const contextValues = createContextValues();
|
|
138
|
+
for (const option in options) {
|
|
139
|
+
const contextKey = RequestOptionsKeys[option];
|
|
140
|
+
contextValues.set(contextKey, options[option]);
|
|
141
|
+
}
|
|
142
|
+
return contextValues;
|
|
143
|
+
};
|
|
144
|
+
return { client, createRequestOptions, queryKey, queryKeyStr: queryKeyWithoutParams.join('') };
|
|
131
145
|
};
|
|
132
146
|
// create promise client, used for server stream subscriptions
|
|
133
147
|
createStreamClient = (service) => {
|
|
@@ -159,6 +173,7 @@ export class Transport {
|
|
|
159
173
|
if (process.env.NODE_ENV !== 'test') {
|
|
160
174
|
this.interceptors.push(__GRPC_DEVTOOLS_EXTENSION__());
|
|
161
175
|
}
|
|
176
|
+
this.interceptors.push(retry);
|
|
162
177
|
// if (window.__GRPC_DEVTOOLS_EXTENSION__) {
|
|
163
178
|
// this.interceptors.push(window.__GRPC_DEVTOOLS_EXTENSION__())
|
|
164
179
|
// } else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamlayer/sdk-web-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"typings": "./lib/index.d.ts",
|
|
@@ -21,14 +21,15 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@streamlayer/sdk-web-interfaces": "^0.20.
|
|
24
|
+
"@streamlayer/sdk-web-interfaces": "^0.20.6",
|
|
25
|
+
"@streamlayer/sdk-web-logger": "^0.5.17"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@bufbuild/protobuf": "^1.
|
|
28
|
+
"@bufbuild/protobuf": "^1.7.2",
|
|
28
29
|
"@connectrpc/connect": "^1.3.0",
|
|
29
30
|
"@connectrpc/connect-web": "^1.3.0",
|
|
30
31
|
"@nanostores/query": "^0.2.9",
|
|
31
|
-
"@streamlayer/sl-eslib": "^5.
|
|
32
|
+
"@streamlayer/sl-eslib": "^5.79.3",
|
|
32
33
|
"@swc/helpers": "^0.5.3",
|
|
33
34
|
"nanoid": "3.3.7",
|
|
34
35
|
"nanostores": "^0.9.5",
|