@100mslive/hms-whiteboard 0.0.6-alpha.0 → 0.0.6-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.
- package/dist/ErrorFallback.js +1 -1
- package/dist/ErrorFallback.js.map +1 -1
- package/dist/Whiteboard.js +1 -1
- package/dist/Whiteboard.js.map +1 -1
- package/dist/hooks/StoreClient.d.ts +2 -2
- package/dist/hooks/StoreClient.js +1 -1
- package/dist/hooks/StoreClient.js.map +1 -1
- package/dist/hooks/useCollaboration.js +1 -1
- package/dist/hooks/useCollaboration.js.map +1 -1
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +3 -2
- package/src/ErrorFallback.tsx +169 -0
- package/src/Whiteboard.tsx +60 -0
- package/src/grpc/sessionstore.client.ts +174 -0
- package/src/grpc/sessionstore.ts +1128 -0
- package/src/hooks/StoreClient.ts +136 -0
- package/src/hooks/default_store.ts +141 -0
- package/src/hooks/useCollaboration.ts +257 -0
- package/src/hooks/useSessionStore.ts +32 -0
- package/src/hooks/useSetEditorPermissions.tsx +38 -0
- package/src/index.css +26 -0
- package/src/index.ts +2 -0
- package/src/utils.ts +22 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React, { ComponentType, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useValue } from '@tldraw/state';
|
|
3
|
+
import { Editor, hardResetEditor } from '@tldraw/tldraw';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
|
|
6
|
+
const DISCORD_URL = 'https://discord.gg/pTge2BwDBq';
|
|
7
|
+
|
|
8
|
+
export type TLErrorFallbackComponent = ComponentType<{
|
|
9
|
+
error: unknown;
|
|
10
|
+
refresh: () => void;
|
|
11
|
+
editor?: Editor;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
export const ErrorFallback: TLErrorFallbackComponent = ({ error, editor, refresh }) => {
|
|
15
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
16
|
+
const [shouldShowError, setShouldShowError] = useState(process.env.NODE_ENV !== 'production');
|
|
17
|
+
const [didCopy, setDidCopy] = useState(false);
|
|
18
|
+
const [shouldShowResetConfirmation, setShouldShowResetConfirmation] = useState(false);
|
|
19
|
+
|
|
20
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
21
|
+
const errorStack = error instanceof Error ? error.stack : null;
|
|
22
|
+
|
|
23
|
+
const isDarkModeFromApp = useValue(
|
|
24
|
+
'isDarkMode',
|
|
25
|
+
() => {
|
|
26
|
+
try {
|
|
27
|
+
if (editor) {
|
|
28
|
+
return editor.user.getIsDarkMode();
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// we're in a funky error state so this might not work for spooky
|
|
32
|
+
// reasons. if not, we'll have another attempt later:
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
},
|
|
36
|
+
[editor],
|
|
37
|
+
);
|
|
38
|
+
const [
|
|
39
|
+
,
|
|
40
|
+
// isDarkMode
|
|
41
|
+
setIsDarkMode,
|
|
42
|
+
] = useState<null | boolean>(null);
|
|
43
|
+
useLayoutEffect(() => {
|
|
44
|
+
// if we found a theme class from the app, we can just use that
|
|
45
|
+
if (isDarkModeFromApp !== null) {
|
|
46
|
+
setIsDarkMode(isDarkModeFromApp);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// do any of our parents have a theme class? if yes then we can just
|
|
50
|
+
// rely on that and don't need to set our own class
|
|
51
|
+
let parent = containerRef.current?.parentElement;
|
|
52
|
+
let foundParentThemeClass = false;
|
|
53
|
+
while (parent) {
|
|
54
|
+
if (parent.classList.contains('tl-theme__dark') || parent.classList.contains('tl-theme__light')) {
|
|
55
|
+
foundParentThemeClass = true;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
parent = parent.parentElement;
|
|
59
|
+
}
|
|
60
|
+
if (foundParentThemeClass) {
|
|
61
|
+
setIsDarkMode(null);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// if we can't find a theme class from the app or from a parent, we have
|
|
66
|
+
// to fall back on using a media query:
|
|
67
|
+
setIsDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
68
|
+
}, [isDarkModeFromApp]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (didCopy) {
|
|
72
|
+
const timeout = setTimeout(() => {
|
|
73
|
+
setDidCopy(false);
|
|
74
|
+
}, 2000);
|
|
75
|
+
return () => clearTimeout(timeout);
|
|
76
|
+
}
|
|
77
|
+
}, [didCopy]);
|
|
78
|
+
|
|
79
|
+
const copyError = () => {
|
|
80
|
+
const textarea = document.createElement('textarea');
|
|
81
|
+
textarea.value = errorStack ?? errorMessage;
|
|
82
|
+
document.body.appendChild(textarea);
|
|
83
|
+
textarea.select();
|
|
84
|
+
document.execCommand('copy');
|
|
85
|
+
textarea.remove();
|
|
86
|
+
setDidCopy(true);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const resetLocalState = async () => {
|
|
90
|
+
hardResetEditor();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
ref={containerRef}
|
|
96
|
+
className={classNames(
|
|
97
|
+
'tl-container tl-error-boundary',
|
|
98
|
+
// error-boundary is sometimes used outside of the theme
|
|
99
|
+
// container, so we need to provide it with a theme for our
|
|
100
|
+
// styles to work correctly
|
|
101
|
+
// 100ms: default light theme
|
|
102
|
+
// isDarkMode === null ? '' : isDarkMode ? 'tl-theme__dark' : 'tl-theme__light',
|
|
103
|
+
'tl-theme__light',
|
|
104
|
+
)}
|
|
105
|
+
style={{ position: 'static' }}
|
|
106
|
+
>
|
|
107
|
+
<div className="tl-error-boundary__overlay" />
|
|
108
|
+
{/* {editor && (
|
|
109
|
+
// opportunistically attempt to render the canvas to reassure
|
|
110
|
+
// the user that their document is still there. there's a good
|
|
111
|
+
// chance this won't work (ie the error that we're currently
|
|
112
|
+
// notifying the user about originates in the canvas) so it's
|
|
113
|
+
// not a big deal if it doesn't work - in that case we just have
|
|
114
|
+
// a plain grey background.
|
|
115
|
+
<ErrorBoundary onError={noop} fallback={() => null}>
|
|
116
|
+
<EditorContext.Provider value={editor}>
|
|
117
|
+
<div className="tl-overlay tl-error-boundary__canvas">
|
|
118
|
+
<Canvas />
|
|
119
|
+
</div>
|
|
120
|
+
</EditorContext.Provider>
|
|
121
|
+
</ErrorBoundary>
|
|
122
|
+
)} */}
|
|
123
|
+
<div
|
|
124
|
+
className={classNames('tl-modal', 'tl-error-boundary__content', {
|
|
125
|
+
'tl-error-boundary__content__expanded': shouldShowError && !shouldShowResetConfirmation,
|
|
126
|
+
})}
|
|
127
|
+
>
|
|
128
|
+
{shouldShowResetConfirmation ? (
|
|
129
|
+
<>
|
|
130
|
+
<h2>Are you sure?</h2>
|
|
131
|
+
<p>Resetting your data will delete your drawing and cannot be undone.</p>
|
|
132
|
+
<div className="tl-error-boundary__content__actions">
|
|
133
|
+
<button onClick={() => setShouldShowResetConfirmation(false)}>Cancel</button>
|
|
134
|
+
<button className="tl-error-boundary__reset" onClick={resetLocalState}>
|
|
135
|
+
Reset data
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
</>
|
|
139
|
+
) : (
|
|
140
|
+
<>
|
|
141
|
+
<h2>Something's gone wrong.</h2>
|
|
142
|
+
<p>
|
|
143
|
+
Sorry, we encountered an error. Please refresh the page to continue. If you keep seeing this error, you
|
|
144
|
+
can <a href={DISCORD_URL}>ask for help on Discord</a>.
|
|
145
|
+
</p>
|
|
146
|
+
{shouldShowError && (
|
|
147
|
+
<div className="tl-error-boundary__content__error">
|
|
148
|
+
<pre>
|
|
149
|
+
<code>{errorStack ?? errorMessage}</code>
|
|
150
|
+
</pre>
|
|
151
|
+
<button onClick={copyError}>{didCopy ? 'Copied!' : 'Copy'}</button>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
<div className="tl-error-boundary__content__actions">
|
|
155
|
+
<button onClick={() => setShouldShowError(!shouldShowError)}>
|
|
156
|
+
{shouldShowError ? 'Hide details' : 'Show details'}
|
|
157
|
+
</button>
|
|
158
|
+
<div className="tl-error-boundary__content__actions__group">
|
|
159
|
+
<button className="tl-error-boundary__refresh" onClick={refresh}>
|
|
160
|
+
Refresh
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Editor, Tldraw } from '@tldraw/tldraw';
|
|
3
|
+
import { ErrorFallback } from './ErrorFallback';
|
|
4
|
+
import { useCollaboration } from './hooks/useCollaboration';
|
|
5
|
+
import './index.css';
|
|
6
|
+
|
|
7
|
+
export interface WhiteboardProps {
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
token: string;
|
|
10
|
+
zoomToContent?: boolean;
|
|
11
|
+
transparentCanvas?: boolean;
|
|
12
|
+
onMount?: (args: { store?: unknown; editor?: unknown }) => void;
|
|
13
|
+
}
|
|
14
|
+
export function Whiteboard(props: WhiteboardProps) {
|
|
15
|
+
const [key, setKey] = useState(Date.now() + props.token);
|
|
16
|
+
|
|
17
|
+
return <CollaborativeEditor key={key} refresh={() => setKey(Date.now() + props.token)} {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function CollaborativeEditor({
|
|
21
|
+
endpoint,
|
|
22
|
+
token,
|
|
23
|
+
zoomToContent,
|
|
24
|
+
transparentCanvas,
|
|
25
|
+
onMount,
|
|
26
|
+
refresh,
|
|
27
|
+
}: WhiteboardProps & { refresh: () => void }) {
|
|
28
|
+
const [editor, setEditor] = useState<Editor>();
|
|
29
|
+
const store = useCollaboration({
|
|
30
|
+
endpoint,
|
|
31
|
+
token,
|
|
32
|
+
editor,
|
|
33
|
+
zoomToContent,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const handleMount = (editor: Editor) => {
|
|
37
|
+
setEditor(editor);
|
|
38
|
+
// @ts-expect-error - for debugging
|
|
39
|
+
window.editor = editor;
|
|
40
|
+
onMount?.({ store: store.store, editor });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (store.status === 'synced-remote' && store.connectionStatus === 'offline') {
|
|
44
|
+
return <ErrorFallback error={Error('Network connection lost')} editor={editor} refresh={refresh} />;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Tldraw
|
|
49
|
+
className={transparentCanvas ? 'transparent-canvas' : ''}
|
|
50
|
+
autoFocus
|
|
51
|
+
store={store}
|
|
52
|
+
onMount={handleMount}
|
|
53
|
+
components={{
|
|
54
|
+
ErrorFallback: ({ error, editor }) => <ErrorFallback editor={editor} error={error} refresh={refresh} />,
|
|
55
|
+
}}
|
|
56
|
+
hideUi={editor?.getInstanceState()?.isReadonly}
|
|
57
|
+
initialState={editor?.getInstanceState()?.isReadonly ? 'hand' : 'select'}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// @generated by protobuf-ts 2.9.4 with parameter long_type_string
|
|
2
|
+
// @generated from protobuf file "sessionstore.proto" (package "sessionstorepb", syntax proto3)
|
|
3
|
+
// tslint:disable
|
|
4
|
+
import type { RpcOptions, RpcTransport, ServerStreamingCall, ServiceInfo, UnaryCall } from '@protobuf-ts/runtime-rpc';
|
|
5
|
+
import { stackIntercept } from '@protobuf-ts/runtime-rpc';
|
|
6
|
+
import type {
|
|
7
|
+
ChangeStream,
|
|
8
|
+
CountRequest,
|
|
9
|
+
CountResponse,
|
|
10
|
+
DeleteRequest,
|
|
11
|
+
DeleteResponse,
|
|
12
|
+
Event,
|
|
13
|
+
GetRequest,
|
|
14
|
+
GetResponse,
|
|
15
|
+
HelloRequest,
|
|
16
|
+
HelloResponse,
|
|
17
|
+
OpenRequest,
|
|
18
|
+
SetRequest,
|
|
19
|
+
SetResponse,
|
|
20
|
+
SubscribeRequest,
|
|
21
|
+
} from './sessionstore';
|
|
22
|
+
import { Api, Store } from './sessionstore';
|
|
23
|
+
/**
|
|
24
|
+
* @generated from protobuf service sessionstorepb.Api
|
|
25
|
+
*/
|
|
26
|
+
export interface IApiClient {
|
|
27
|
+
/**
|
|
28
|
+
* @generated from protobuf rpc: Hello(sessionstorepb.HelloRequest) returns (sessionstorepb.HelloResponse);
|
|
29
|
+
*/
|
|
30
|
+
hello(input: HelloRequest, options?: RpcOptions): UnaryCall<HelloRequest, HelloResponse>;
|
|
31
|
+
/**
|
|
32
|
+
* @generated from protobuf rpc: Subscribe(sessionstorepb.SubscribeRequest) returns (stream sessionstorepb.Event);
|
|
33
|
+
*/
|
|
34
|
+
subscribe(input: SubscribeRequest, options?: RpcOptions): ServerStreamingCall<SubscribeRequest, Event>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* @generated from protobuf service sessionstorepb.Api
|
|
38
|
+
*/
|
|
39
|
+
export class ApiClient implements IApiClient, ServiceInfo {
|
|
40
|
+
typeName = Api.typeName;
|
|
41
|
+
methods = Api.methods;
|
|
42
|
+
options = Api.options;
|
|
43
|
+
constructor(private readonly _transport: RpcTransport) {}
|
|
44
|
+
/**
|
|
45
|
+
* @generated from protobuf rpc: Hello(sessionstorepb.HelloRequest) returns (sessionstorepb.HelloResponse);
|
|
46
|
+
*/
|
|
47
|
+
hello(input: HelloRequest, options?: RpcOptions): UnaryCall<HelloRequest, HelloResponse> {
|
|
48
|
+
const method = this.methods[0],
|
|
49
|
+
opt = this._transport.mergeOptions(options);
|
|
50
|
+
return stackIntercept<HelloRequest, HelloResponse>('unary', this._transport, method, opt, input);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @generated from protobuf rpc: Subscribe(sessionstorepb.SubscribeRequest) returns (stream sessionstorepb.Event);
|
|
54
|
+
*/
|
|
55
|
+
subscribe(input: SubscribeRequest, options?: RpcOptions): ServerStreamingCall<SubscribeRequest, Event> {
|
|
56
|
+
const method = this.methods[1],
|
|
57
|
+
opt = this._transport.mergeOptions(options);
|
|
58
|
+
return stackIntercept<SubscribeRequest, Event>('serverStreaming', this._transport, method, opt, input);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// metadata token -> session id, room id, user id, username
|
|
62
|
+
|
|
63
|
+
// open is used for presence
|
|
64
|
+
|
|
65
|
+
// change stream will return all keys in order of oldest to newsest.
|
|
66
|
+
|
|
67
|
+
// max number of keys -> 5000
|
|
68
|
+
// max size per key -> 10240 Bytes
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @generated from protobuf service sessionstorepb.Store
|
|
72
|
+
*/
|
|
73
|
+
export interface IStoreClient {
|
|
74
|
+
/**
|
|
75
|
+
* open - start listening to updates in keys with provided match patterns
|
|
76
|
+
* provide change_id as last received ID to resume updates
|
|
77
|
+
*
|
|
78
|
+
* @generated from protobuf rpc: open(sessionstorepb.OpenRequest) returns (stream sessionstorepb.ChangeStream);
|
|
79
|
+
*/
|
|
80
|
+
open(input: OpenRequest, options?: RpcOptions): ServerStreamingCall<OpenRequest, ChangeStream>;
|
|
81
|
+
/**
|
|
82
|
+
* get last stored value in given key
|
|
83
|
+
*
|
|
84
|
+
* @generated from protobuf rpc: get(sessionstorepb.GetRequest) returns (sessionstorepb.GetResponse);
|
|
85
|
+
*/
|
|
86
|
+
get(input: GetRequest, options?: RpcOptions): UnaryCall<GetRequest, GetResponse>;
|
|
87
|
+
/**
|
|
88
|
+
* set key value
|
|
89
|
+
*
|
|
90
|
+
* @generated from protobuf rpc: set(sessionstorepb.SetRequest) returns (sessionstorepb.SetResponse);
|
|
91
|
+
*/
|
|
92
|
+
set(input: SetRequest, options?: RpcOptions): UnaryCall<SetRequest, SetResponse>;
|
|
93
|
+
/**
|
|
94
|
+
* delete key from store
|
|
95
|
+
*
|
|
96
|
+
* @generated from protobuf rpc: delete(sessionstorepb.DeleteRequest) returns (sessionstorepb.DeleteResponse);
|
|
97
|
+
*/
|
|
98
|
+
delete(input: DeleteRequest, options?: RpcOptions): UnaryCall<DeleteRequest, DeleteResponse>;
|
|
99
|
+
/**
|
|
100
|
+
* count get count of keys
|
|
101
|
+
*
|
|
102
|
+
* @generated from protobuf rpc: count(sessionstorepb.CountRequest) returns (sessionstorepb.CountResponse);
|
|
103
|
+
*/
|
|
104
|
+
count(input: CountRequest, options?: RpcOptions): UnaryCall<CountRequest, CountResponse>;
|
|
105
|
+
}
|
|
106
|
+
// metadata token -> session id, room id, user id, username
|
|
107
|
+
|
|
108
|
+
// open is used for presence
|
|
109
|
+
|
|
110
|
+
// change stream will return all keys in order of oldest to newsest.
|
|
111
|
+
|
|
112
|
+
// max number of keys -> 5000
|
|
113
|
+
// max size per key -> 10240 Bytes
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @generated from protobuf service sessionstorepb.Store
|
|
117
|
+
*/
|
|
118
|
+
export class StoreClient implements IStoreClient, ServiceInfo {
|
|
119
|
+
typeName = Store.typeName;
|
|
120
|
+
methods = Store.methods;
|
|
121
|
+
options = Store.options;
|
|
122
|
+
constructor(private readonly _transport: RpcTransport) {}
|
|
123
|
+
/**
|
|
124
|
+
* open - start listening to updates in keys with provided match patterns
|
|
125
|
+
* provide change_id as last received ID to resume updates
|
|
126
|
+
*
|
|
127
|
+
* @generated from protobuf rpc: open(sessionstorepb.OpenRequest) returns (stream sessionstorepb.ChangeStream);
|
|
128
|
+
*/
|
|
129
|
+
open(input: OpenRequest, options?: RpcOptions): ServerStreamingCall<OpenRequest, ChangeStream> {
|
|
130
|
+
const method = this.methods[0],
|
|
131
|
+
opt = this._transport.mergeOptions(options);
|
|
132
|
+
return stackIntercept<OpenRequest, ChangeStream>('serverStreaming', this._transport, method, opt, input);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* get last stored value in given key
|
|
136
|
+
*
|
|
137
|
+
* @generated from protobuf rpc: get(sessionstorepb.GetRequest) returns (sessionstorepb.GetResponse);
|
|
138
|
+
*/
|
|
139
|
+
get(input: GetRequest, options?: RpcOptions): UnaryCall<GetRequest, GetResponse> {
|
|
140
|
+
const method = this.methods[1],
|
|
141
|
+
opt = this._transport.mergeOptions(options);
|
|
142
|
+
return stackIntercept<GetRequest, GetResponse>('unary', this._transport, method, opt, input);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* set key value
|
|
146
|
+
*
|
|
147
|
+
* @generated from protobuf rpc: set(sessionstorepb.SetRequest) returns (sessionstorepb.SetResponse);
|
|
148
|
+
*/
|
|
149
|
+
set(input: SetRequest, options?: RpcOptions): UnaryCall<SetRequest, SetResponse> {
|
|
150
|
+
const method = this.methods[2],
|
|
151
|
+
opt = this._transport.mergeOptions(options);
|
|
152
|
+
return stackIntercept<SetRequest, SetResponse>('unary', this._transport, method, opt, input);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* delete key from store
|
|
156
|
+
*
|
|
157
|
+
* @generated from protobuf rpc: delete(sessionstorepb.DeleteRequest) returns (sessionstorepb.DeleteResponse);
|
|
158
|
+
*/
|
|
159
|
+
delete(input: DeleteRequest, options?: RpcOptions): UnaryCall<DeleteRequest, DeleteResponse> {
|
|
160
|
+
const method = this.methods[3],
|
|
161
|
+
opt = this._transport.mergeOptions(options);
|
|
162
|
+
return stackIntercept<DeleteRequest, DeleteResponse>('unary', this._transport, method, opt, input);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* count get count of keys
|
|
166
|
+
*
|
|
167
|
+
* @generated from protobuf rpc: count(sessionstorepb.CountRequest) returns (sessionstorepb.CountResponse);
|
|
168
|
+
*/
|
|
169
|
+
count(input: CountRequest, options?: RpcOptions): UnaryCall<CountRequest, CountResponse> {
|
|
170
|
+
const method = this.methods[4],
|
|
171
|
+
opt = this._transport.mergeOptions(options);
|
|
172
|
+
return stackIntercept<CountRequest, CountResponse>('unary', this._transport, method, opt, input);
|
|
173
|
+
}
|
|
174
|
+
}
|