@streamlayer/sdk-web-api 0.0.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/README.md +6 -0
- package/lib/grpc/queries/event.d.ts +5 -0
- package/lib/grpc/queries/event.js +38 -0
- package/lib/grpc/queries/index.d.ts +3 -0
- package/lib/grpc/queries/index.js +3 -0
- package/lib/grpc/queries/organization.d.ts +21 -0
- package/lib/grpc/queries/organization.js +28 -0
- package/lib/grpc/queries/user.d.ts +13 -0
- package/lib/grpc/queries/user.js +41 -0
- package/lib/grpc/subscription.d.ts +43 -0
- package/lib/grpc/subscription.js +114 -0
- package/lib/grpc/transport.d.ts +75 -0
- package/lib/grpc/transport.js +160 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +9 -0
- package/lib/utils/devtools.d.ts +1 -0
- package/lib/utils/devtools.js +65 -0
- package/lib/utils/grpc-stub.d.ts +7 -0
- package/lib/utils/grpc-stub.js +23 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ReadableAtom } from 'nanostores';
|
|
2
|
+
import type { StreamSettings } from '@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb';
|
|
3
|
+
import { Transport } from '../transport';
|
|
4
|
+
export declare const $retrieveEventId: ($providerStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<string | undefined, any>;
|
|
5
|
+
export declare const $streamSettings: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<StreamSettings | undefined, any>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Events } from '@streamlayer/sl-eslib/sports/events/events_connect';
|
|
2
|
+
import { Client } from '@streamlayer/sl-eslib/sdkSettings/client/client_connect';
|
|
3
|
+
export const $retrieveEventId = ($providerStreamId, transport) => {
|
|
4
|
+
const { client, queryKey } = transport.createPromiseClient(Events, {
|
|
5
|
+
method: 'retrieveEventId',
|
|
6
|
+
params: [$providerStreamId],
|
|
7
|
+
});
|
|
8
|
+
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
9
|
+
fetcher: async (_, __, id) => {
|
|
10
|
+
if (!id || typeof id !== 'string') {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const res = await client.retrieveEventId({
|
|
15
|
+
id,
|
|
16
|
+
});
|
|
17
|
+
return res.data?.id || '';
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
export const $streamSettings = (slStreamId, transport) => {
|
|
26
|
+
const { client, queryKey } = transport.createPromiseClient(Client, { method: 'getStream', params: [slStreamId] });
|
|
27
|
+
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
28
|
+
fetcher: async (_, __, id) => {
|
|
29
|
+
if (!id) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const res = await client.getStream({
|
|
33
|
+
id: id, // we are sure that id is a string
|
|
34
|
+
});
|
|
35
|
+
return res.data?.attributes;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReadableAtom } from 'nanostores';
|
|
2
|
+
import { Transport } from '../transport';
|
|
3
|
+
export { $user } from './user';
|
|
4
|
+
export declare const $organizationSettings: ($enabled: ReadableAtom<'on' | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<{
|
|
5
|
+
id: string;
|
|
6
|
+
overlays?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").SdkOverlay[] | undefined;
|
|
7
|
+
buttonIcon?: string | undefined;
|
|
8
|
+
tinodeHost?: string | undefined;
|
|
9
|
+
audience?: string | undefined;
|
|
10
|
+
name?: string | undefined;
|
|
11
|
+
provider?: string | undefined;
|
|
12
|
+
primaryColor?: string | undefined;
|
|
13
|
+
secondaryColor?: string | undefined;
|
|
14
|
+
moderationPrimaryColor?: string | undefined;
|
|
15
|
+
linkShareIcon?: string | undefined;
|
|
16
|
+
linkShareText?: string | undefined;
|
|
17
|
+
brandDefaults?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").BrandDefaults | undefined;
|
|
18
|
+
pub?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").JWK | undefined;
|
|
19
|
+
getstream?: import("@streamlayer/sl-eslib/sdkSettings/sdkSettings.common_pb").GetStreamSettingsClient | undefined;
|
|
20
|
+
} | undefined, any>;
|
|
21
|
+
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>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Client } from '@streamlayer/sl-eslib/sdkSettings/client/client_connect';
|
|
2
|
+
export { $user } from './user';
|
|
3
|
+
export const $organizationSettings = ($enabled, transport) => {
|
|
4
|
+
const { client, queryKey } = transport.createPromiseClient(Client, { method: 'getOrganization', params: [$enabled] });
|
|
5
|
+
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
6
|
+
fetcher: async () => {
|
|
7
|
+
const res = await client.getOrganization({});
|
|
8
|
+
return res.data
|
|
9
|
+
? {
|
|
10
|
+
...res.data.attributes,
|
|
11
|
+
id: res.data.id,
|
|
12
|
+
}
|
|
13
|
+
: undefined;
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
export const $organizationAdvertising = ($enabled, transport) => {
|
|
18
|
+
const { client, queryKey } = transport.createPromiseClient(Client, {
|
|
19
|
+
method: 'getOrganizationAdvertising',
|
|
20
|
+
params: [$enabled],
|
|
21
|
+
});
|
|
22
|
+
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
23
|
+
fetcher: async () => {
|
|
24
|
+
const res = await client.getOrganizationAdvertising({});
|
|
25
|
+
return res.data?.attributes;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReadableAtom } from 'nanostores';
|
|
2
|
+
import type { BypassAuthRequest, BypassAuthResponse } from '@streamlayer/sl-eslib/users/users_pb';
|
|
3
|
+
import { PlainMessage } from '@bufbuild/protobuf';
|
|
4
|
+
import { Transport } from '../transport';
|
|
5
|
+
export declare const $user: ($userToken: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/users/users_pb").MeResponse, any>;
|
|
6
|
+
export declare const $bypassLogin: (transport: Transport) => import("@nanostores/query").MutatorStore<PlainMessage<BypassAuthRequest>, PlainMessage<BypassAuthResponse>, any>;
|
|
7
|
+
export declare const bypassAuth: (transport: Transport, params: {
|
|
8
|
+
userKey?: string;
|
|
9
|
+
schema?: string;
|
|
10
|
+
init?: boolean;
|
|
11
|
+
}) => Promise<BypassAuthResponse>;
|
|
12
|
+
export declare const $userSettings: ($userToken: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/sdkSettings/client/client_pb").ClientSettings | undefined, any>;
|
|
13
|
+
export declare const register: (transport: Transport, phone: string) => Promise<import("@streamlayer/sl-eslib/users/users_pb").RegisterResponse>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Users } from '@streamlayer/sl-eslib/users/users_connect';
|
|
2
|
+
import { Client } from '@streamlayer/sl-eslib/sdkSettings/client/client_connect';
|
|
3
|
+
// user query
|
|
4
|
+
// cache user
|
|
5
|
+
// invalidate token
|
|
6
|
+
// login
|
|
7
|
+
// save token
|
|
8
|
+
// called on update user key
|
|
9
|
+
export const $user = ($userToken, transport) => {
|
|
10
|
+
const { queryKey, client } = transport.createPromiseClient(Users, { method: 'me', params: [$userToken] });
|
|
11
|
+
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
12
|
+
fetcher: () => client.me({}),
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
export const $bypassLogin = (transport) => {
|
|
16
|
+
const { client, queryKeyStr } = transport.createPromiseClient(Users, { method: 'bypassAuth' });
|
|
17
|
+
return transport.nanoquery.createMutatorStore(async ({ data: { userKey, schema, init }, getCacheUpdater }) => {
|
|
18
|
+
const [updateCache] = getCacheUpdater(queryKeyStr);
|
|
19
|
+
const user = await client.bypassAuth({ userKey, schema, init });
|
|
20
|
+
updateCache(user);
|
|
21
|
+
return user;
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
export const bypassAuth = (transport, params) => {
|
|
25
|
+
const { client } = transport.createPromiseClient(Users, { method: 'bypassAuth' });
|
|
26
|
+
return client.bypassAuth(params);
|
|
27
|
+
};
|
|
28
|
+
export const $userSettings = ($userToken, // sl user token
|
|
29
|
+
transport) => {
|
|
30
|
+
const { client, queryKey } = transport.createPromiseClient(Client, { method: 'get', params: [$userToken] });
|
|
31
|
+
return transport.nanoquery.createFetcherStore(queryKey, {
|
|
32
|
+
fetcher: async () => {
|
|
33
|
+
const data = await client.get({});
|
|
34
|
+
return data.data?.attributes;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
export const register = (transport, phone) => {
|
|
39
|
+
const { client } = transport.createPromiseClient(Users, { method: 'register' });
|
|
40
|
+
return client.register({ id: phone });
|
|
41
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { CallbackClient } from '@connectrpc/connect';
|
|
2
|
+
import { Atom } from 'nanostores';
|
|
3
|
+
import type { ServiceType, Message, PlainMessage, MethodInfoServerStreaming } from '@bufbuild/protobuf';
|
|
4
|
+
import { Transport } from './transport';
|
|
5
|
+
type StreamMethods<T extends ServiceType> = {
|
|
6
|
+
[P in keyof CallbackClient<T> as T['methods'][P] extends MethodInfoServerStreaming<any, any> ? P : never]: P;
|
|
7
|
+
};
|
|
8
|
+
export type StreamMethod<T extends ServiceType> = StreamMethods<T>[keyof StreamMethods<T>] extends keyof CallbackClient<T> ? StreamMethods<T>[keyof StreamMethods<T>] : never;
|
|
9
|
+
declare enum ServerStreamSubscriptionStatus {
|
|
10
|
+
Init = "init",
|
|
11
|
+
Ready = "ready",
|
|
12
|
+
Connecting = "connecting",
|
|
13
|
+
Connected = "connected",
|
|
14
|
+
Disconnected = "disconnected",
|
|
15
|
+
Failed = "failed",
|
|
16
|
+
Reconnecting = "reconnecting"
|
|
17
|
+
}
|
|
18
|
+
export type ServerStreamSubscriptionOptions = {
|
|
19
|
+
name: string;
|
|
20
|
+
withStore?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare class ServerStreamSubscription<T extends ServiceType, Request extends Message<Request>, Response extends Message<Response>, M extends StreamMethod<T> = StreamMethod<T>, Method extends CallbackClient<T>[M] = CallbackClient<T>[M]> {
|
|
23
|
+
params: Atom<PlainMessage<Request>> | PlainMessage<Request>;
|
|
24
|
+
private stream?;
|
|
25
|
+
private method;
|
|
26
|
+
private name;
|
|
27
|
+
private headers;
|
|
28
|
+
private listeners;
|
|
29
|
+
private state;
|
|
30
|
+
private store?;
|
|
31
|
+
constructor(headers: Transport['$headers'], method: Method, params: Atom<PlainMessage<Request>> | PlainMessage<Request>, options: ServerStreamSubscriptionOptions);
|
|
32
|
+
updateState: (status: ServerStreamSubscriptionStatus) => void;
|
|
33
|
+
addStateLog: (msg: string) => void;
|
|
34
|
+
addListener: (name: string, listener: (response: Response) => void) => boolean;
|
|
35
|
+
removeListener: (name: string) => void;
|
|
36
|
+
connect: () => void;
|
|
37
|
+
disconnect: () => void;
|
|
38
|
+
reconnect: () => void;
|
|
39
|
+
getStore: () => import("nanostores").WritableAtom<Response | null | undefined> | undefined;
|
|
40
|
+
private onData;
|
|
41
|
+
private onStreamError;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { MapStore, SingleStore, createMapStore, createSingleStore } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { Code } from '@connectrpc/connect';
|
|
3
|
+
var ServerStreamSubscriptionStatus;
|
|
4
|
+
(function (ServerStreamSubscriptionStatus) {
|
|
5
|
+
ServerStreamSubscriptionStatus["Init"] = "init";
|
|
6
|
+
ServerStreamSubscriptionStatus["Ready"] = "ready";
|
|
7
|
+
ServerStreamSubscriptionStatus["Connecting"] = "connecting";
|
|
8
|
+
ServerStreamSubscriptionStatus["Connected"] = "connected";
|
|
9
|
+
ServerStreamSubscriptionStatus["Disconnected"] = "disconnected";
|
|
10
|
+
ServerStreamSubscriptionStatus["Failed"] = "failed";
|
|
11
|
+
ServerStreamSubscriptionStatus["Reconnecting"] = "reconnecting";
|
|
12
|
+
})(ServerStreamSubscriptionStatus || (ServerStreamSubscriptionStatus = {}));
|
|
13
|
+
export class ServerStreamSubscription {
|
|
14
|
+
params;
|
|
15
|
+
stream;
|
|
16
|
+
method;
|
|
17
|
+
name;
|
|
18
|
+
headers;
|
|
19
|
+
listeners;
|
|
20
|
+
state;
|
|
21
|
+
store;
|
|
22
|
+
constructor(headers, method, params, options) {
|
|
23
|
+
const initState = {
|
|
24
|
+
status: ServerStreamSubscriptionStatus.Init,
|
|
25
|
+
ts: new Date(),
|
|
26
|
+
log: [],
|
|
27
|
+
};
|
|
28
|
+
this.state = new MapStore(createMapStore(initState), `subscription:${options.name}:state`);
|
|
29
|
+
this.name = options.name;
|
|
30
|
+
this.headers = headers;
|
|
31
|
+
this.listeners = new Map();
|
|
32
|
+
this.params = params;
|
|
33
|
+
this.method = method;
|
|
34
|
+
if (options.withStore) {
|
|
35
|
+
this.store = new SingleStore(createSingleStore(null), `subscription:${options.name}:store`);
|
|
36
|
+
}
|
|
37
|
+
if ('subscribe' in params && typeof params.subscribe === 'function') {
|
|
38
|
+
params.subscribe(() => {
|
|
39
|
+
this.reconnect();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
this.updateState(ServerStreamSubscriptionStatus.Ready);
|
|
43
|
+
}
|
|
44
|
+
updateState = (status) => {
|
|
45
|
+
this.state.setValue('status', status);
|
|
46
|
+
this.state.setValue('ts', new Date());
|
|
47
|
+
this.addStateLog(`status => ${status}`);
|
|
48
|
+
};
|
|
49
|
+
addStateLog = (msg) => {
|
|
50
|
+
const log = this.state.getStore().get()['log'];
|
|
51
|
+
this.state.setValue('log', [...log, `${new Date().toLocaleString()}: ${msg}`]);
|
|
52
|
+
};
|
|
53
|
+
addListener = (name, listener) => {
|
|
54
|
+
if (this.listeners.has(name)) {
|
|
55
|
+
this.addStateLog(`listener '${name}' not added`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
this.listeners.set(name, listener);
|
|
59
|
+
this.addStateLog(`listener '${name}' added`);
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
removeListener = (name) => {
|
|
63
|
+
this.listeners.delete(name);
|
|
64
|
+
this.addStateLog(`listener '${name}' removed`);
|
|
65
|
+
};
|
|
66
|
+
connect = () => {
|
|
67
|
+
this.updateState(ServerStreamSubscriptionStatus.Connecting);
|
|
68
|
+
if (this.stream) {
|
|
69
|
+
this.addStateLog(`disconnect prev connection`);
|
|
70
|
+
this.stream();
|
|
71
|
+
}
|
|
72
|
+
const params = 'get' in this.params && typeof this.params.get === 'function' ? this.params.get() : this.params;
|
|
73
|
+
this.stream = this.method(params,
|
|
74
|
+
// ToDo: fix types
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
76
|
+
// @ts-ignore
|
|
77
|
+
this.onData, this.onStreamError, { headers: this.headers.getValues() });
|
|
78
|
+
this.updateState(ServerStreamSubscriptionStatus.Connected);
|
|
79
|
+
};
|
|
80
|
+
disconnect = () => {
|
|
81
|
+
if (this.stream) {
|
|
82
|
+
this.stream();
|
|
83
|
+
}
|
|
84
|
+
this.listeners.clear();
|
|
85
|
+
this.updateState(ServerStreamSubscriptionStatus.Disconnected);
|
|
86
|
+
};
|
|
87
|
+
reconnect = () => {
|
|
88
|
+
this.updateState(ServerStreamSubscriptionStatus.Reconnecting);
|
|
89
|
+
this.connect();
|
|
90
|
+
};
|
|
91
|
+
getStore = () => this.store?.getStore();
|
|
92
|
+
onData = (response) => {
|
|
93
|
+
this.addStateLog(`received data => ${JSON.stringify(response)}`);
|
|
94
|
+
if (this.store) {
|
|
95
|
+
this.store.setValue(response);
|
|
96
|
+
}
|
|
97
|
+
for (const [, listener] of this.listeners) {
|
|
98
|
+
listener(response);
|
|
99
|
+
}
|
|
100
|
+
this.addStateLog(`data routed to ${this.listeners.size} listeners`);
|
|
101
|
+
};
|
|
102
|
+
onStreamError = (error) => {
|
|
103
|
+
if (error === undefined) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (error.code !== Code.Canceled && error.rawMessage !== '[canceled] BodyStreamBuffer was aborted') {
|
|
107
|
+
this.updateState(ServerStreamSubscriptionStatus.Failed);
|
|
108
|
+
this.state.setValue('error', error);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.disconnect();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createRouterTransport, ConnectRouter, Interceptor, PromiseClient, CallbackClient, UnaryRequest, StreamRequest } from '@connectrpc/connect';
|
|
2
|
+
import type { ServiceType, Message, PlainMessage } from '@bufbuild/protobuf';
|
|
3
|
+
import { createGrpcWebTransport } from '@connectrpc/connect-web';
|
|
4
|
+
import type { KeyInput } from '@nanostores/query';
|
|
5
|
+
import { nanoquery } from '@nanostores/query';
|
|
6
|
+
import { Atom } from 'nanostores';
|
|
7
|
+
import { ServerStreamSubscription, type ServerStreamSubscriptionOptions } from './subscription';
|
|
8
|
+
type KnownHeaders = {
|
|
9
|
+
authorization?: string;
|
|
10
|
+
sdk?: string;
|
|
11
|
+
'sl-device-id': string;
|
|
12
|
+
} & Record<string, string>;
|
|
13
|
+
export type GrpcTransport = Transport['transport'];
|
|
14
|
+
type ReservedHeaders = 'sdk' | 'authorization';
|
|
15
|
+
type ExcludeReservedHeaders<T> = ReservedHeaders extends T ? never : T extends ReservedHeaders ? never : T;
|
|
16
|
+
declare global {
|
|
17
|
+
interface Window {
|
|
18
|
+
__GRPC_DEVTOOLS_EXTENSION__?: () => import('@connectrpc/connect').Interceptor;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
type NanoqueryReturnType = ReturnType<typeof nanoquery>;
|
|
22
|
+
type NanoqueryObjectType = {
|
|
23
|
+
createFetcherStore: NanoqueryReturnType[0];
|
|
24
|
+
createMutatorStore: NanoqueryReturnType[1];
|
|
25
|
+
utils: NanoqueryReturnType[2];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* transport wrapper, initialize grpc transport, store headers and connect interceptors
|
|
29
|
+
*/
|
|
30
|
+
export declare class Transport {
|
|
31
|
+
toJsonOptions: {
|
|
32
|
+
emitDefaultValues: boolean;
|
|
33
|
+
enumAsInteger: boolean;
|
|
34
|
+
useProtoFieldName: boolean;
|
|
35
|
+
};
|
|
36
|
+
readonly transport: ReturnType<typeof createGrpcWebTransport>;
|
|
37
|
+
readonly nanoquery: NanoqueryObjectType;
|
|
38
|
+
readonly host: string;
|
|
39
|
+
protected interceptors: Interceptor[];
|
|
40
|
+
private readonly $headers;
|
|
41
|
+
private clients;
|
|
42
|
+
private callbackClients;
|
|
43
|
+
private subscriptions;
|
|
44
|
+
constructor(host: string);
|
|
45
|
+
addSubscription: <T extends ServiceType, Req extends Message<Req>, Res extends Message<Res>>(method: CallbackClient<T>[import("./subscription").StreamMethod<T>], params: PlainMessage<Req> | Atom<PlainMessage<Req>>, options: ServerStreamSubscriptionOptions) => ServerStreamSubscription<ServiceType, Message<import("@bufbuild/protobuf").AnyMessage>, Message<import("@bufbuild/protobuf").AnyMessage>, never, never> | ServerStreamSubscription<T, Req, Res, import("./subscription").StreamMethod<T>, CallbackClient<T>[import("./subscription").StreamMethod<T>]>;
|
|
46
|
+
removeSubscription: (subscription: ServerStreamSubscription<ServiceType, Message, Message>) => void;
|
|
47
|
+
disconnect: () => void;
|
|
48
|
+
registerInterceptor: (interceptor: Interceptor) => void;
|
|
49
|
+
removeInterceptor: (interceptor: Interceptor) => void;
|
|
50
|
+
getClient: <T extends ServiceType>(service: T) => PromiseClient<T>;
|
|
51
|
+
getCallbackClient: <T extends ServiceType>(service: T) => CallbackClient<T>;
|
|
52
|
+
createPromiseClient: <T extends ServiceType>(service: T, { params, method }: {
|
|
53
|
+
params?: KeyInput | undefined;
|
|
54
|
+
method: keyof T["methods"];
|
|
55
|
+
}) => {
|
|
56
|
+
client: PromiseClient<T>;
|
|
57
|
+
queryKey: ((string | number | true) | import("nanostores").ReadableAtom<(string | number | true) | (false | void | null | undefined)> | import("@nanostores/query").FetcherStore<any, any>)[];
|
|
58
|
+
queryKeyStr: string;
|
|
59
|
+
};
|
|
60
|
+
createCallbackClient: <T extends ServiceType>(service: T) => {
|
|
61
|
+
client: CallbackClient<T>;
|
|
62
|
+
};
|
|
63
|
+
setSdkKey: (sdkKey: string) => void;
|
|
64
|
+
setAuth: (token?: string) => void;
|
|
65
|
+
setHeader: <T extends string = string>(name: ExcludeReservedHeaders<T>, value: string) => void;
|
|
66
|
+
getHeader: (name: keyof KnownHeaders) => string;
|
|
67
|
+
getHeaders: () => KnownHeaders;
|
|
68
|
+
initInterceptors: () => void;
|
|
69
|
+
}
|
|
70
|
+
export declare class MockTransport extends Transport {
|
|
71
|
+
transport: ReturnType<typeof createRouterTransport>;
|
|
72
|
+
calls: Array<UnaryRequest | StreamRequest>;
|
|
73
|
+
constructor(transport: (router: ConnectRouter) => void);
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { MapStore, createMapStore } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { createRouterTransport, createPromiseClient, createCallbackClient, } from '@connectrpc/connect';
|
|
3
|
+
import { createGrpcWebTransport } from '@connectrpc/connect-web';
|
|
4
|
+
import { nanoquery } from '@nanostores/query';
|
|
5
|
+
import { __GRPC_DEVTOOLS_EXTENSION__ } from '../utils/devtools';
|
|
6
|
+
import { ServerStreamSubscription } from './subscription';
|
|
7
|
+
/**
|
|
8
|
+
* transport wrapper, initialize grpc transport, store headers and connect interceptors
|
|
9
|
+
*/
|
|
10
|
+
export class Transport {
|
|
11
|
+
toJsonOptions = {
|
|
12
|
+
emitDefaultValues: false,
|
|
13
|
+
enumAsInteger: true,
|
|
14
|
+
useProtoFieldName: false,
|
|
15
|
+
};
|
|
16
|
+
transport;
|
|
17
|
+
nanoquery;
|
|
18
|
+
host;
|
|
19
|
+
interceptors = [];
|
|
20
|
+
$headers;
|
|
21
|
+
clients;
|
|
22
|
+
callbackClients;
|
|
23
|
+
subscriptions;
|
|
24
|
+
constructor(host) {
|
|
25
|
+
this.host = host;
|
|
26
|
+
this.$headers = new MapStore(createMapStore({
|
|
27
|
+
['sl-device-id']: process?.env?.NX_DEVICE_ID || 'sdk-web-dev',
|
|
28
|
+
}), 'transport:headers');
|
|
29
|
+
this.initInterceptors();
|
|
30
|
+
this.clients = new Map();
|
|
31
|
+
this.callbackClients = new Map();
|
|
32
|
+
this.subscriptions = new Map();
|
|
33
|
+
const [createFetcherStore, createMutatorStore, utils] = nanoquery();
|
|
34
|
+
this.nanoquery = { createFetcherStore, createMutatorStore, utils };
|
|
35
|
+
this.transport = createGrpcWebTransport({
|
|
36
|
+
baseUrl: host,
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
interceptors: this.interceptors,
|
|
40
|
+
useBinaryFormat: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// use shared request params, based on Atom, each new Atom will create new subscription
|
|
44
|
+
// mutate Atom, subscription automatically reconnect with new params
|
|
45
|
+
// if headers changed, reconnect subscription with new headers automatically
|
|
46
|
+
addSubscription = (method, params, options) => {
|
|
47
|
+
const currentSubscription = this.subscriptions.get(params);
|
|
48
|
+
if (currentSubscription) {
|
|
49
|
+
return currentSubscription;
|
|
50
|
+
}
|
|
51
|
+
const subscription = new ServerStreamSubscription(this.$headers, method, params, options);
|
|
52
|
+
// ToDo: fix types
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
54
|
+
// @ts-ignore
|
|
55
|
+
this.subscriptions.set(params, subscription);
|
|
56
|
+
return subscription;
|
|
57
|
+
};
|
|
58
|
+
removeSubscription = (subscription) => {
|
|
59
|
+
subscription.disconnect();
|
|
60
|
+
this.subscriptions.delete(subscription.params);
|
|
61
|
+
};
|
|
62
|
+
// cleanup subscriptions
|
|
63
|
+
disconnect = () => {
|
|
64
|
+
for (const [name, subscription] of this.subscriptions) {
|
|
65
|
+
subscription.disconnect();
|
|
66
|
+
this.subscriptions.delete(name);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
registerInterceptor = (interceptor) => {
|
|
70
|
+
this.interceptors.push(interceptor);
|
|
71
|
+
};
|
|
72
|
+
removeInterceptor = (interceptor) => {
|
|
73
|
+
this.interceptors = this.interceptors.filter((i) => i !== interceptor);
|
|
74
|
+
};
|
|
75
|
+
getClient = (service) => {
|
|
76
|
+
const serviceName = service.typeName;
|
|
77
|
+
if (this.clients.has(serviceName)) {
|
|
78
|
+
return this.clients.get(serviceName);
|
|
79
|
+
}
|
|
80
|
+
const client = createPromiseClient(service, this.transport);
|
|
81
|
+
this.clients.set(serviceName, client);
|
|
82
|
+
return client;
|
|
83
|
+
};
|
|
84
|
+
getCallbackClient = (service) => {
|
|
85
|
+
const serviceName = service.typeName;
|
|
86
|
+
if (this.callbackClients.has(serviceName)) {
|
|
87
|
+
return this.callbackClients.get(serviceName);
|
|
88
|
+
}
|
|
89
|
+
const client = createCallbackClient(service, this.transport);
|
|
90
|
+
this.callbackClients.set(serviceName, client);
|
|
91
|
+
return client;
|
|
92
|
+
};
|
|
93
|
+
// create unary client, used for query request
|
|
94
|
+
createPromiseClient = (service, { params = [], method }) => {
|
|
95
|
+
const client = this.getClient(service);
|
|
96
|
+
const methodName = service.methods[method].name;
|
|
97
|
+
const queryKey = [
|
|
98
|
+
service.typeName,
|
|
99
|
+
methodName.charAt(0).toLowerCase() + methodName.slice(1),
|
|
100
|
+
...(Array.isArray(params) ? params : [params]),
|
|
101
|
+
];
|
|
102
|
+
const queryKeyWithoutParams = [service.typeName, methodName.charAt(0).toLowerCase() + methodName.slice(1)];
|
|
103
|
+
return { client, queryKey, queryKeyStr: queryKeyWithoutParams.join('') };
|
|
104
|
+
};
|
|
105
|
+
// create callback client, used for server stream subscriptions
|
|
106
|
+
createCallbackClient = (service) => {
|
|
107
|
+
const client = this.getCallbackClient(service);
|
|
108
|
+
return { client };
|
|
109
|
+
};
|
|
110
|
+
setSdkKey = (sdkKey) => {
|
|
111
|
+
this.$headers.setValue('sdk', sdkKey);
|
|
112
|
+
};
|
|
113
|
+
setAuth = (token) => {
|
|
114
|
+
this.$headers.setValue('authorization', token);
|
|
115
|
+
};
|
|
116
|
+
setHeader = (name, value) => this.$headers.setValue(name, value);
|
|
117
|
+
getHeader = (name) => this.$headers.getValue(name);
|
|
118
|
+
getHeaders = () => this.$headers.getValues();
|
|
119
|
+
initInterceptors = () => {
|
|
120
|
+
if (this.interceptors.length !== 0) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const auth = (next) => (req) => {
|
|
124
|
+
const headers = this.$headers.getValues();
|
|
125
|
+
for (const header in headers) {
|
|
126
|
+
req.header.set(header, headers[header]);
|
|
127
|
+
}
|
|
128
|
+
return next(req);
|
|
129
|
+
};
|
|
130
|
+
this.interceptors.push(auth);
|
|
131
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
132
|
+
this.interceptors.push(__GRPC_DEVTOOLS_EXTENSION__());
|
|
133
|
+
}
|
|
134
|
+
// if (window.__GRPC_DEVTOOLS_EXTENSION__) {
|
|
135
|
+
// this.interceptors.push(window.__GRPC_DEVTOOLS_EXTENSION__())
|
|
136
|
+
// } else {
|
|
137
|
+
// window.addEventListener('grpc_devtools_loaded', () => {
|
|
138
|
+
// if (window.__GRPC_DEVTOOLS_EXTENSION__) {
|
|
139
|
+
// this.interceptors.push(window.__GRPC_DEVTOOLS_EXTENSION__())
|
|
140
|
+
// }
|
|
141
|
+
// })
|
|
142
|
+
// }
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export class MockTransport extends Transport {
|
|
146
|
+
calls;
|
|
147
|
+
constructor(transport) {
|
|
148
|
+
super(process.env.NX_GRPC_HOST || 'https://grpc-web.next.streamlayer.io:443');
|
|
149
|
+
this.calls = [];
|
|
150
|
+
this.interceptors.push((next) => (req) => {
|
|
151
|
+
this.calls.push(req);
|
|
152
|
+
return next(req);
|
|
153
|
+
});
|
|
154
|
+
this.transport = createRouterTransport(transport, {
|
|
155
|
+
transport: {
|
|
156
|
+
interceptors: this.interceptors,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { FetcherStore } from '@nanostores/query';
|
|
3
|
+
export type { ServerStreamSubscriptionOptions } from './grpc/subscription';
|
|
4
|
+
export { Transport } from './grpc/transport';
|
|
5
|
+
import { Transport } from './grpc/transport';
|
|
6
|
+
export type { GrpcTransport } from './grpc/transport';
|
|
7
|
+
export * as queries from './grpc/queries';
|
|
8
|
+
export type GetApiResponseType<T extends (...args: any[]) => FetcherStore> = ReturnType<ReturnType<T>['get']>['data'];
|
|
9
|
+
declare module '@streamlayer/sdk-web-interfaces' {
|
|
10
|
+
interface StreamLayerSDK {
|
|
11
|
+
host: string;
|
|
12
|
+
}
|
|
13
|
+
interface StreamLayerContext {
|
|
14
|
+
transport: Transport;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export declare const transport: (instance: StreamLayerContext, opts: {
|
|
18
|
+
sdkKey: string;
|
|
19
|
+
host: string;
|
|
20
|
+
}, done: () => void) => void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Transport } from './grpc/transport';
|
|
2
|
+
import { Transport } from './grpc/transport';
|
|
3
|
+
export * as queries from './grpc/queries';
|
|
4
|
+
export const transport = (instance, opts, done) => {
|
|
5
|
+
instance.transport = new Transport(opts.host);
|
|
6
|
+
instance.sdk.host = instance.transport.host;
|
|
7
|
+
instance.transport.setSdkKey(opts.sdkKey);
|
|
8
|
+
done();
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const __GRPC_DEVTOOLS_EXTENSION__: any;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
async function* logEach(store, stream) {
|
|
4
|
+
for await (const m of stream) {
|
|
5
|
+
store.response.message = m;
|
|
6
|
+
store.received_at = Date.now();
|
|
7
|
+
const msg = {
|
|
8
|
+
type: '__GRPC_DEVTOOLS_EXTENSION__',
|
|
9
|
+
data: store,
|
|
10
|
+
};
|
|
11
|
+
window.postMessage(msg);
|
|
12
|
+
yield m;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
export const __GRPC_DEVTOOLS_EXTENSION__ = () => (next) => async (request) => {
|
|
17
|
+
const store = {
|
|
18
|
+
name: request.url,
|
|
19
|
+
request: {},
|
|
20
|
+
response: {},
|
|
21
|
+
};
|
|
22
|
+
store.request.header = Object.fromEntries(request.header.entries());
|
|
23
|
+
store.sent_at = Date.now();
|
|
24
|
+
try {
|
|
25
|
+
const response = await next(request);
|
|
26
|
+
store.received_at = Date.now();
|
|
27
|
+
store.stream = response.stream;
|
|
28
|
+
store.response.header = Object.fromEntries(response.header.entries());
|
|
29
|
+
store.response.trailer = Object.fromEntries(response.trailer.entries());
|
|
30
|
+
if (!response.stream) {
|
|
31
|
+
store.request.message = request.message;
|
|
32
|
+
store.response.message = response.message;
|
|
33
|
+
store.latency = store.received_at - store.sent_at;
|
|
34
|
+
const msg = {
|
|
35
|
+
type: '__GRPC_DEVTOOLS_EXTENSION__',
|
|
36
|
+
data: store,
|
|
37
|
+
};
|
|
38
|
+
window.postMessage(msg);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return {
|
|
42
|
+
...response,
|
|
43
|
+
message: logEach(store, response.message),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
store.received_at = Date.now();
|
|
50
|
+
store.request.message = request.message;
|
|
51
|
+
store.response.trailer = {
|
|
52
|
+
['grpc-status']: e.code,
|
|
53
|
+
['grpc-message']: e.rawMessage,
|
|
54
|
+
};
|
|
55
|
+
store.response.message = e.rawMessage;
|
|
56
|
+
store.latency = store.received_at - store.sent_at;
|
|
57
|
+
const msg = {
|
|
58
|
+
type: '__GRPC_DEVTOOLS_EXTENSION__',
|
|
59
|
+
data: store,
|
|
60
|
+
};
|
|
61
|
+
window.postMessage(msg);
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
window.dispatchEvent(new CustomEvent('grpc_devtools_loaded'));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Transport } from '..';
|
|
2
|
+
export class GrpcStub {
|
|
3
|
+
transport;
|
|
4
|
+
stubs;
|
|
5
|
+
constructor() {
|
|
6
|
+
this.transport = new Transport(process.env.NX_GRPC_HOST || 'https://grpc-web.next.streamlayer.io:443');
|
|
7
|
+
this.stubs = new Map();
|
|
8
|
+
const stubbedUnary = (service, method, signal, timeoutMs, header, input) => {
|
|
9
|
+
const key = `${service.typeName}${method.name}`.toLowerCase();
|
|
10
|
+
console.log('grpc unary', { key, input });
|
|
11
|
+
for (const [stubKey, stubValue] of this.stubs) {
|
|
12
|
+
if (stubKey === key) {
|
|
13
|
+
return { message: stubValue };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
throw new Error(JSON.stringify({ message: 'not stubbed correctly', service, method, signal, timeoutMs, header, input }));
|
|
17
|
+
};
|
|
18
|
+
this.transport.transport.unary = stubbedUnary;
|
|
19
|
+
}
|
|
20
|
+
stub = (method, response) => {
|
|
21
|
+
this.stubs.set(method.toLowerCase(), response);
|
|
22
|
+
};
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@streamlayer/sdk-web-api",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"typings": "./lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib/",
|
|
9
|
+
"package.json"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
14
|
+
"import": "./lib/index.js",
|
|
15
|
+
"default": "./lib/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./queries/*": {
|
|
18
|
+
"types": "./lib/grpc/queries/*.d.ts",
|
|
19
|
+
"import": "./lib/grpc/queries/*.js",
|
|
20
|
+
"default": "./lib/grpc/queries/*.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@streamlayer/sdk-web-interfaces": "^0.17.3"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@swc/helpers": "~0.5.3",
|
|
28
|
+
"@bufbuild/protobuf": "^1.4.2",
|
|
29
|
+
"@connectrpc/connect": "^1.1.3",
|
|
30
|
+
"@connectrpc/connect-web": "^1.1.3",
|
|
31
|
+
"@nanostores/query": "^0.2.8",
|
|
32
|
+
"@streamlayer/sl-eslib": "^5.45.1",
|
|
33
|
+
"nanostores": "^0.9.4",
|
|
34
|
+
"tslib": "^2.6.2"
|
|
35
|
+
}
|
|
36
|
+
}
|