@faybl/react-utils 1.1.1 → 1.2.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.
- package/README.md +42 -9
- package/package.json +8 -2
- package/src/index.js +2 -0
- package/src/useConnection.js +19 -0
- package/src/useSession.js +31 -0
package/README.md
CHANGED
|
@@ -6,23 +6,36 @@ React hooks for Faybl web apps.
|
|
|
6
6
|
npm install @faybl/react-utils
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Every hook takes the faybl
|
|
10
|
-
one.
|
|
11
|
-
|
|
9
|
+
Every hook takes the `@faybl/client` client as `client` and does nothing until
|
|
10
|
+
it has one. `useConnection` and `useSession` need client 4.1 or later. A list
|
|
11
|
+
of rids or ids is compared by its content, so a new array with the same ids
|
|
12
|
+
does not resubscribe.
|
|
12
13
|
|
|
13
14
|
```js
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useChannelsLatest,
|
|
17
|
+
useChannelsSeries,
|
|
18
|
+
useConnection,
|
|
19
|
+
usePresence,
|
|
20
|
+
useSession,
|
|
21
|
+
} from '@faybl/react-utils';
|
|
15
22
|
|
|
16
23
|
const presenceById = usePresence({ client, identityIds });
|
|
17
24
|
const latest = useChannelsLatest({ client, rids, commitMs: 1000, onError });
|
|
18
25
|
const series = useChannelsSeries({ client, rids, windowSeconds: 3600 });
|
|
26
|
+
const connected = useConnection({ client });
|
|
27
|
+
const { expired, login } = useSession({ client });
|
|
19
28
|
```
|
|
20
29
|
|
|
21
|
-
| Hook | Returns
|
|
22
|
-
| ------------------- |
|
|
23
|
-
| `usePresence` | `{ [identityId]: { ts, connected } }`, live
|
|
24
|
-
| `useChannelsLatest` | `{ [rid]: { ts, pl } }`, the last stored point, then live
|
|
25
|
-
| `useChannelsSeries` | `{ [rid]: points }`, the last `windowSeconds`, oldest first, then live
|
|
30
|
+
| Hook | Returns |
|
|
31
|
+
| ------------------- | ------------------------------------------------------------------------- |
|
|
32
|
+
| `usePresence` | `{ [identityId]: { ts, connected } }`, live |
|
|
33
|
+
| `useChannelsLatest` | `{ [rid]: { ts, pl } }`, the last stored point, then live |
|
|
34
|
+
| `useChannelsSeries` | `{ [rid]: points }`, the last `windowSeconds`, oldest first, then live |
|
|
35
|
+
| `useConnection` | whether the MQTT connection is up |
|
|
36
|
+
| `useSession` | `{ expired, login }`: whether the login session is gone, and the way back |
|
|
37
|
+
|
|
38
|
+
`connected` and `expired` are `undefined` until the client has reported them.
|
|
26
39
|
|
|
27
40
|
The two channel hooks take two options:
|
|
28
41
|
|
|
@@ -30,3 +43,23 @@ The two channel hooks take two options:
|
|
|
30
43
|
Without it, every point renders as it arrives.
|
|
31
44
|
- `onError(error, rid)` is called when a history read fails. The hooks report
|
|
32
45
|
nothing on their own.
|
|
46
|
+
|
|
47
|
+
## An expired session
|
|
48
|
+
|
|
49
|
+
`useSession` shows nothing and never navigates on its own; what happens when
|
|
50
|
+
the session expires is the app's choice. `login` navigates the whole page to
|
|
51
|
+
`/oauth2/start` and back to where the user was, which happens at once while the
|
|
52
|
+
identity provider still has a session.
|
|
53
|
+
|
|
54
|
+
A banner with a button that calls `login` keeps whatever the user was typing. A
|
|
55
|
+
page with nothing to lose can call `login` from an effect once `expired` is
|
|
56
|
+
true instead.
|
|
57
|
+
|
|
58
|
+
```jsx
|
|
59
|
+
const SessionBanner = ({ client }) => {
|
|
60
|
+
const { expired, login } = useSession({ client });
|
|
61
|
+
return expired ? (
|
|
62
|
+
<Alert action={<Button onClick={login}>Log in</Button>}>Your session has expired.</Alert>
|
|
63
|
+
) : undefined;
|
|
64
|
+
};
|
|
65
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faybl/react-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "React hooks for Faybl web apps",
|
|
5
5
|
"license": "TBD",
|
|
6
6
|
"author": "Arcturitech",
|
|
@@ -21,10 +21,16 @@
|
|
|
21
21
|
"directory": "packages/react-utils"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
+
"@faybl/client": "^4.1.0",
|
|
24
25
|
"react": "^19.2.0"
|
|
25
26
|
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"@faybl/client": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
26
32
|
"dependencies": {
|
|
27
|
-
"@faybl/utils": "^1.
|
|
33
|
+
"@faybl/utils": "^1.2.0"
|
|
28
34
|
},
|
|
29
35
|
"engines": {
|
|
30
36
|
"node": ">=22"
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { useChannelsLatest } from './useChannelsLatest.js';
|
|
2
2
|
export { useChannelsSeries } from './useChannelsSeries.js';
|
|
3
|
+
export { useConnection } from './useConnection.js';
|
|
3
4
|
export { usePresence } from './usePresence.js';
|
|
5
|
+
export { useSession } from './useSession.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether the client's MQTT connection is up: true or false once the client
|
|
5
|
+
* has reported it, undefined before that and without a client.
|
|
6
|
+
* @param {object} params
|
|
7
|
+
* @param {object} params.client faybl-client with MQTT
|
|
8
|
+
* @returns {boolean | undefined}
|
|
9
|
+
*/
|
|
10
|
+
export const useConnection = ({ client }) => {
|
|
11
|
+
const [state, setState] = useState({ client: undefined, connected: undefined });
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!client) return undefined;
|
|
15
|
+
return client.connection.subscribe(({ connected }) => setState({ client, connected }));
|
|
16
|
+
}, [client]);
|
|
17
|
+
|
|
18
|
+
return state.client === client ? state.connected : undefined;
|
|
19
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const LOGIN_PATH = '/oauth2/start';
|
|
4
|
+
|
|
5
|
+
const navigateTo = (url) => window.location.assign(url);
|
|
6
|
+
|
|
7
|
+
// The login proxy sends the user back to `rd`; while the identity provider
|
|
8
|
+
// still has a session that happens at once.
|
|
9
|
+
export const loginUrl = (href) => `${LOGIN_PATH}?rd=${encodeURIComponent(href)}`;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Whether the login session behind the client has expired, and `login`, which
|
|
13
|
+
* sends the user to log in and back to the current page. `expired` is
|
|
14
|
+
* undefined until the client has reported it. Nothing navigates on its own.
|
|
15
|
+
* @param {object} params
|
|
16
|
+
* @param {object} params.client faybl-client
|
|
17
|
+
* @param {(url: string) => void} [params.navigate] Top-level navigation
|
|
18
|
+
* @returns {{ expired: boolean | undefined, login: () => void }}
|
|
19
|
+
*/
|
|
20
|
+
export const useSession = ({ client, navigate = navigateTo }) => {
|
|
21
|
+
const [state, setState] = useState({ client: undefined, expired: undefined });
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!client) return undefined;
|
|
25
|
+
return client.session.subscribe(({ expired }) => setState({ client, expired }));
|
|
26
|
+
}, [client]);
|
|
27
|
+
|
|
28
|
+
const login = useCallback(() => navigate(loginUrl(window.location.href)), [navigate]);
|
|
29
|
+
|
|
30
|
+
return { expired: state.client === client ? state.expired : undefined, login };
|
|
31
|
+
};
|