@safercity/sdk-react 0.0.1 → 0.1.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 +138 -35
- package/dist/index.cjs +209 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -40
- package/dist/index.d.ts +91 -40
- package/dist/index.js +208 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -6
package/README.md
CHANGED
|
@@ -10,9 +10,31 @@ npm install @safercity/sdk-react @tanstack/react-query
|
|
|
10
10
|
bun add @safercity/sdk-react @tanstack/react-query
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Authentication Modes
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
The provider supports three authentication modes. Choose the one that fits your architecture:
|
|
16
|
+
|
|
17
|
+
### Proxy Mode (Default - Most Secure)
|
|
18
|
+
|
|
19
|
+
Client -> Your Backend -> SaferCity API. Your backend handles credentials.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { SaferCityProvider } from '@safercity/sdk-react';
|
|
23
|
+
|
|
24
|
+
function App() {
|
|
25
|
+
return (
|
|
26
|
+
<SaferCityProvider mode="proxy" proxyBaseUrl="/api/safercity">
|
|
27
|
+
<YourApp />
|
|
28
|
+
</SaferCityProvider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Set up the proxy on your backend with `createNextHandler` or `createExpressMiddleware` from `@safercity/sdk`.
|
|
34
|
+
|
|
35
|
+
### Direct Mode
|
|
36
|
+
|
|
37
|
+
Client -> SaferCity API with an external auth token. For white-label apps using Clerk, Auth0, better-auth, etc.
|
|
16
38
|
|
|
17
39
|
```tsx
|
|
18
40
|
import { SaferCityProvider } from '@safercity/sdk-react';
|
|
@@ -20,9 +42,10 @@ import { SaferCityProvider } from '@safercity/sdk-react';
|
|
|
20
42
|
function App() {
|
|
21
43
|
return (
|
|
22
44
|
<SaferCityProvider
|
|
45
|
+
mode="direct"
|
|
23
46
|
baseUrl="https://api.safercity.com"
|
|
24
|
-
token={userToken}
|
|
25
47
|
tenantId="tenant-123"
|
|
48
|
+
getAccessToken={() => session?.accessToken}
|
|
26
49
|
>
|
|
27
50
|
<YourApp />
|
|
28
51
|
</SaferCityProvider>
|
|
@@ -30,7 +53,84 @@ function App() {
|
|
|
30
53
|
}
|
|
31
54
|
```
|
|
32
55
|
|
|
33
|
-
|
|
56
|
+
The provider automatically refreshes the token every 30 seconds by calling `getAccessToken`.
|
|
57
|
+
|
|
58
|
+
### Cookie Mode
|
|
59
|
+
|
|
60
|
+
Browser with `credentials: include`. For first-party web apps using session cookies.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { SaferCityProvider } from '@safercity/sdk-react';
|
|
64
|
+
|
|
65
|
+
function App() {
|
|
66
|
+
return (
|
|
67
|
+
<SaferCityProvider mode="cookie" baseUrl="https://api.safercity.com">
|
|
68
|
+
<YourApp />
|
|
69
|
+
</SaferCityProvider>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The provider automatically checks session status on mount via `/v1/auth/session/status`.
|
|
75
|
+
|
|
76
|
+
### Legacy Mode
|
|
77
|
+
|
|
78
|
+
For backward compatibility, you can still pass `baseUrl` and `token` directly without a `mode`:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<SaferCityProvider baseUrl="https://api.safercity.com" token={userToken} tenantId="tenant-123">
|
|
82
|
+
<YourApp />
|
|
83
|
+
</SaferCityProvider>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Session Management (Cookie Mode)
|
|
87
|
+
|
|
88
|
+
When using cookie mode, the provider exposes session management hooks:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { useSession, useSessionManager, useAuthMode } from '@safercity/sdk-react';
|
|
92
|
+
|
|
93
|
+
function AuthStatus() {
|
|
94
|
+
const session = useSession();
|
|
95
|
+
const mode = useAuthMode(); // "proxy" | "direct" | "cookie"
|
|
96
|
+
|
|
97
|
+
if (session.isLoading) return <div>Loading...</div>;
|
|
98
|
+
if (session.error) return <div>Error: {session.error.message}</div>;
|
|
99
|
+
|
|
100
|
+
return <div>Authenticated: {session.isAuthenticated ? 'Yes' : 'No'}</div>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function LoginButton() {
|
|
104
|
+
const { createSession, clearSession, refreshCsrf, isAuthenticated } = useSessionManager();
|
|
105
|
+
|
|
106
|
+
const handleLogin = async (externalToken: string) => {
|
|
107
|
+
await createSession(externalToken, 'tenant-123');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const handleLogout = async () => {
|
|
111
|
+
await clearSession();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return isAuthenticated
|
|
115
|
+
? <button onClick={handleLogout}>Logout</button>
|
|
116
|
+
: <button onClick={() => handleLogin('...')}>Login</button>;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### SessionState
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
interface SessionState {
|
|
124
|
+
isAuthenticated: boolean;
|
|
125
|
+
isLoading: boolean;
|
|
126
|
+
userId?: string;
|
|
127
|
+
tenantId?: string;
|
|
128
|
+
expiresAt?: number;
|
|
129
|
+
error?: Error;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Using Hooks
|
|
34
134
|
|
|
35
135
|
```tsx
|
|
36
136
|
import { useUsers, usePanics, useCreatePanic } from '@safercity/sdk-react';
|
|
@@ -68,32 +168,16 @@ function PanicButton({ userId }: { userId: string }) {
|
|
|
68
168
|
}
|
|
69
169
|
```
|
|
70
170
|
|
|
71
|
-
## Streaming Hook
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
import { usePanicStream } from '@safercity/sdk-react';
|
|
75
|
-
|
|
76
|
-
function PanicTracker({ panicId }: { panicId: string }) {
|
|
77
|
-
const { data, isConnected, error, events } = usePanicStream(panicId, {
|
|
78
|
-
keepHistory: true,
|
|
79
|
-
onEvent: (event) => console.log('Update:', event),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (error) return <div>Error: {error.message}</div>;
|
|
83
|
-
if (!isConnected) return <div>Connecting...</div>;
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<div>
|
|
87
|
-
<p>Latest: {data?.data}</p>
|
|
88
|
-
<p>Total events: {events.length}</p>
|
|
89
|
-
</div>
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
171
|
## Available Hooks
|
|
95
172
|
|
|
96
|
-
###
|
|
173
|
+
### Provider Hooks
|
|
174
|
+
- `useSaferCity()` - Full context (client, mode, session, session management)
|
|
175
|
+
- `useSaferCityClient()` - SaferCity client instance
|
|
176
|
+
- `useSession()` - Session state (cookie mode)
|
|
177
|
+
- `useAuthMode()` - Current auth mode (`"proxy"` | `"direct"` | `"cookie"`)
|
|
178
|
+
- `useSessionManager()` - Session management functions (cookie mode)
|
|
179
|
+
|
|
180
|
+
### Health and Auth
|
|
97
181
|
- `useHealthCheck()` - API health status
|
|
98
182
|
- `useWhoAmI()` - Current auth context
|
|
99
183
|
|
|
@@ -126,12 +210,36 @@ function PanicTracker({ panicId }: { panicId: string }) {
|
|
|
126
210
|
- `useCrimeCategories()` - Get crime categories
|
|
127
211
|
- `useCrimeTypes()` - Get crime types
|
|
128
212
|
|
|
213
|
+
## Streaming Hook
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
import { usePanicStream } from '@safercity/sdk-react';
|
|
217
|
+
|
|
218
|
+
function PanicTracker({ panicId }: { panicId: string }) {
|
|
219
|
+
const { data, isConnected, error, events } = usePanicStream(panicId, {
|
|
220
|
+
keepHistory: true,
|
|
221
|
+
onEvent: (event) => console.log('Update:', event),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
225
|
+
if (!isConnected) return <div>Connecting...</div>;
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div>
|
|
229
|
+
<p>Latest: {data?.data}</p>
|
|
230
|
+
<p>Total events: {events.length}</p>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
129
236
|
## Query Keys
|
|
130
237
|
|
|
131
238
|
Use query keys for manual cache management:
|
|
132
239
|
|
|
133
240
|
```tsx
|
|
134
|
-
import { saferCityKeys
|
|
241
|
+
import { saferCityKeys } from '@safercity/sdk-react';
|
|
242
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
135
243
|
|
|
136
244
|
function RefreshButton() {
|
|
137
245
|
const queryClient = useQueryClient();
|
|
@@ -164,11 +272,7 @@ const queryClient = new QueryClient({
|
|
|
164
272
|
|
|
165
273
|
function App() {
|
|
166
274
|
return (
|
|
167
|
-
<SaferCityProvider
|
|
168
|
-
baseUrl="https://api.safercity.com"
|
|
169
|
-
token={token}
|
|
170
|
-
queryClient={queryClient}
|
|
171
|
-
>
|
|
275
|
+
<SaferCityProvider mode="proxy" proxyBaseUrl="/api/safercity" queryClient={queryClient}>
|
|
172
276
|
<YourApp />
|
|
173
277
|
</SaferCityProvider>
|
|
174
278
|
);
|
|
@@ -184,7 +288,6 @@ function CustomComponent() {
|
|
|
184
288
|
const client = useSaferCityClient();
|
|
185
289
|
|
|
186
290
|
const customRequest = async () => {
|
|
187
|
-
// Use raw client for custom requests
|
|
188
291
|
const response = await client._client.get('/custom-endpoint');
|
|
189
292
|
return response.data;
|
|
190
293
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -6,20 +6,187 @@ var reactQuery = require('@tanstack/react-query');
|
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
|
|
8
8
|
var SaferCityContext = react.createContext(null);
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
function isProxyMode(props) {
|
|
10
|
+
return !("mode" in props) || props.mode === "proxy" || props.mode === void 0;
|
|
11
|
+
}
|
|
12
|
+
function isDirectMode(props) {
|
|
13
|
+
return "mode" in props && props.mode === "direct";
|
|
14
|
+
}
|
|
15
|
+
function isCookieMode(props) {
|
|
16
|
+
return "mode" in props && props.mode === "cookie";
|
|
17
|
+
}
|
|
18
|
+
function isLegacyMode(props) {
|
|
19
|
+
return "baseUrl" in props && "token" in props && !("mode" in props);
|
|
20
|
+
}
|
|
21
|
+
function SaferCityProvider(props) {
|
|
22
|
+
const { children, queryClient: externalQueryClient } = props;
|
|
23
|
+
const mode = react.useMemo(() => {
|
|
24
|
+
if (isDirectMode(props)) return "direct";
|
|
25
|
+
if (isCookieMode(props)) return "cookie";
|
|
26
|
+
return "proxy";
|
|
27
|
+
}, [props]);
|
|
28
|
+
const [session, setSession] = react.useState({
|
|
29
|
+
isAuthenticated: false,
|
|
30
|
+
isLoading: mode === "cookie"
|
|
31
|
+
});
|
|
32
|
+
const client = react.useMemo(() => {
|
|
33
|
+
if (isLegacyMode(props)) {
|
|
34
|
+
return sdk.createSaferCityClient({
|
|
35
|
+
baseUrl: props.baseUrl,
|
|
36
|
+
token: props.token,
|
|
37
|
+
tenantId: props.tenantId,
|
|
38
|
+
fetch: props.fetch,
|
|
39
|
+
timeout: props.timeout,
|
|
40
|
+
headers: props.headers
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (isProxyMode(props)) {
|
|
44
|
+
const proxyBaseUrl = props.proxyBaseUrl ?? "/api/safercity";
|
|
45
|
+
return sdk.createSaferCityClient({
|
|
46
|
+
baseUrl: proxyBaseUrl
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (isDirectMode(props)) {
|
|
50
|
+
return sdk.createSaferCityClient({
|
|
51
|
+
baseUrl: props.baseUrl,
|
|
52
|
+
tenantId: props.tenantId
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (isCookieMode(props)) {
|
|
56
|
+
return sdk.createSaferCityClient({
|
|
57
|
+
baseUrl: props.baseUrl,
|
|
58
|
+
tenantId: props.tenantId,
|
|
59
|
+
headers: {
|
|
60
|
+
// Note: cookies are sent automatically with fetch credentials: 'include'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Invalid SaferCityProvider configuration");
|
|
65
|
+
}, [props]);
|
|
66
|
+
const directModeTokenGetter = isDirectMode(props) ? props.getAccessToken : null;
|
|
67
|
+
react.useEffect(() => {
|
|
68
|
+
if (!directModeTokenGetter) return;
|
|
69
|
+
let isMounted = true;
|
|
70
|
+
const updateToken = async () => {
|
|
71
|
+
const token = await directModeTokenGetter();
|
|
72
|
+
if (isMounted) {
|
|
73
|
+
client.setToken(token);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
updateToken();
|
|
77
|
+
const interval = setInterval(updateToken, 3e4);
|
|
78
|
+
return () => {
|
|
79
|
+
isMounted = false;
|
|
80
|
+
clearInterval(interval);
|
|
81
|
+
};
|
|
82
|
+
}, [client, directModeTokenGetter]);
|
|
83
|
+
react.useEffect(() => {
|
|
84
|
+
if (mode !== "cookie") return;
|
|
85
|
+
if (!isCookieMode(props)) return;
|
|
86
|
+
let isMounted = true;
|
|
87
|
+
const checkSession = async () => {
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch(`${props.baseUrl}/v1/auth/session/status`, {
|
|
90
|
+
credentials: "include"
|
|
91
|
+
});
|
|
92
|
+
if (!isMounted) return;
|
|
93
|
+
if (response.ok) {
|
|
94
|
+
const data = await response.json();
|
|
95
|
+
setSession({
|
|
96
|
+
isAuthenticated: data.authenticated,
|
|
97
|
+
isLoading: false,
|
|
98
|
+
userId: data.userId,
|
|
99
|
+
tenantId: data.tenantId,
|
|
100
|
+
expiresAt: data.expiresAt
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
setSession({
|
|
104
|
+
isAuthenticated: false,
|
|
105
|
+
isLoading: false
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (!isMounted) return;
|
|
110
|
+
setSession({
|
|
111
|
+
isAuthenticated: false,
|
|
112
|
+
isLoading: false,
|
|
113
|
+
error: error instanceof Error ? error : new Error("Session check failed")
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
checkSession();
|
|
118
|
+
return () => {
|
|
119
|
+
isMounted = false;
|
|
120
|
+
};
|
|
121
|
+
}, [mode, props]);
|
|
122
|
+
const createSession = react.useCallback(async (token, tenantId) => {
|
|
123
|
+
if (mode !== "cookie" || !isCookieMode(props)) {
|
|
124
|
+
throw new Error("createSession is only available in cookie mode");
|
|
125
|
+
}
|
|
126
|
+
setSession((prev) => ({ ...prev, isLoading: true, error: void 0 }));
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetch(`${props.baseUrl}/v1/auth/session`, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
credentials: "include",
|
|
131
|
+
headers: {
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
...tenantId && { "X-Tenant-ID": tenantId }
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({ token, tenantId })
|
|
136
|
+
});
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
const error = await response.json();
|
|
139
|
+
throw new Error(error.message || "Failed to create session");
|
|
140
|
+
}
|
|
141
|
+
const data = await response.json();
|
|
142
|
+
setSession({
|
|
143
|
+
isAuthenticated: true,
|
|
144
|
+
isLoading: false,
|
|
145
|
+
expiresAt: data.expiresAt,
|
|
146
|
+
tenantId
|
|
147
|
+
});
|
|
148
|
+
} catch (error) {
|
|
149
|
+
setSession({
|
|
150
|
+
isAuthenticated: false,
|
|
151
|
+
isLoading: false,
|
|
152
|
+
error: error instanceof Error ? error : new Error("Failed to create session")
|
|
153
|
+
});
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}, [mode, props]);
|
|
157
|
+
const clearSession = react.useCallback(async () => {
|
|
158
|
+
if (mode !== "cookie" || !isCookieMode(props)) {
|
|
159
|
+
throw new Error("clearSession is only available in cookie mode");
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
await fetch(`${props.baseUrl}/v1/auth/session/logout`, {
|
|
163
|
+
method: "POST",
|
|
164
|
+
credentials: "include"
|
|
165
|
+
});
|
|
166
|
+
} finally {
|
|
167
|
+
setSession({
|
|
168
|
+
isAuthenticated: false,
|
|
169
|
+
isLoading: false
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}, [mode, props]);
|
|
173
|
+
const refreshCsrf = react.useCallback(async () => {
|
|
174
|
+
if (mode !== "cookie" || !isCookieMode(props)) {
|
|
175
|
+
throw new Error("refreshCsrf is only available in cookie mode");
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const response = await fetch(`${props.baseUrl}/v1/auth/session/refresh`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
credentials: "include"
|
|
181
|
+
});
|
|
182
|
+
if (response.ok) {
|
|
183
|
+
const data = await response.json();
|
|
184
|
+
return data.csrfToken;
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
}
|
|
188
|
+
return void 0;
|
|
189
|
+
}, [mode, props]);
|
|
23
190
|
const queryClient = react.useMemo(
|
|
24
191
|
() => externalQueryClient ?? new reactQuery.QueryClient({
|
|
25
192
|
defaultOptions: {
|
|
@@ -38,7 +205,14 @@ function SaferCityProvider({
|
|
|
38
205
|
}),
|
|
39
206
|
[externalQueryClient]
|
|
40
207
|
);
|
|
41
|
-
const contextValue = react.useMemo(() => ({
|
|
208
|
+
const contextValue = react.useMemo(() => ({
|
|
209
|
+
client,
|
|
210
|
+
mode,
|
|
211
|
+
session,
|
|
212
|
+
createSession,
|
|
213
|
+
clearSession,
|
|
214
|
+
refreshCsrf
|
|
215
|
+
}), [client, mode, session, createSession, clearSession, refreshCsrf]);
|
|
42
216
|
return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxRuntime.jsx(SaferCityContext.Provider, { value: contextValue, children }) });
|
|
43
217
|
}
|
|
44
218
|
function useSaferCity() {
|
|
@@ -53,6 +227,23 @@ function useSaferCity() {
|
|
|
53
227
|
function useSaferCityClient() {
|
|
54
228
|
return useSaferCity().client;
|
|
55
229
|
}
|
|
230
|
+
function useSession() {
|
|
231
|
+
return useSaferCity().session;
|
|
232
|
+
}
|
|
233
|
+
function useAuthMode() {
|
|
234
|
+
return useSaferCity().mode;
|
|
235
|
+
}
|
|
236
|
+
function useSessionManager() {
|
|
237
|
+
const { createSession, clearSession, refreshCsrf, session } = useSaferCity();
|
|
238
|
+
return {
|
|
239
|
+
session,
|
|
240
|
+
createSession,
|
|
241
|
+
clearSession,
|
|
242
|
+
refreshCsrf,
|
|
243
|
+
isAuthenticated: session.isAuthenticated,
|
|
244
|
+
isLoading: session.isLoading
|
|
245
|
+
};
|
|
246
|
+
}
|
|
56
247
|
var saferCityKeys = {
|
|
57
248
|
all: ["safercity"],
|
|
58
249
|
// Health
|
|
@@ -471,6 +662,7 @@ function useStream(createStream, options = {}) {
|
|
|
471
662
|
|
|
472
663
|
exports.SaferCityProvider = SaferCityProvider;
|
|
473
664
|
exports.saferCityKeys = saferCityKeys;
|
|
665
|
+
exports.useAuthMode = useAuthMode;
|
|
474
666
|
exports.useCancelPanic = useCancelPanic;
|
|
475
667
|
exports.useCreatePanic = useCreatePanic;
|
|
476
668
|
exports.useCreateSubscription = useCreateSubscription;
|
|
@@ -486,6 +678,8 @@ exports.usePanicStream = usePanicStream;
|
|
|
486
678
|
exports.usePanics = usePanics;
|
|
487
679
|
exports.useSaferCity = useSaferCity;
|
|
488
680
|
exports.useSaferCityClient = useSaferCityClient;
|
|
681
|
+
exports.useSession = useSession;
|
|
682
|
+
exports.useSessionManager = useSessionManager;
|
|
489
683
|
exports.useStream = useStream;
|
|
490
684
|
exports.useSubscriptionStats = useSubscriptionStats;
|
|
491
685
|
exports.useSubscriptionTypes = useSubscriptionTypes;
|