@seamapi/react 0.1.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/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021-2023 Seam Labs, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # Seam React Components
2
+
3
+ [![npm](https://img.shields.io/npm/v/@seamapi/react.svg)](https://www.npmjs.com/package/@seamapi/react)
4
+ [![GitHub Actions](https://github.com/seamapi/react/actions/workflows/check.yml/badge.svg)](https://github.com/seamapi/react/actions/workflows/check.yml)
5
+
6
+ Seam React component library.
7
+
8
+ ## Description
9
+
10
+ Zero-backend beautiful drop-in pre-built components with everything you need to get started with Seam.
11
+
12
+ ## Installation
13
+
14
+ Add this as a dependency to your project using [npm] with
15
+
16
+ ```
17
+ $ npm install @seamapi/react
18
+ ```
19
+
20
+ [npm]: https://www.npmjs.com/
21
+
22
+ ## Development and Testing
23
+
24
+ ### Quickstart
25
+
26
+ ```
27
+ $ git clone https://github.com/seamapi/react.git
28
+ $ cd react
29
+ $ nvm install
30
+ $ npm install
31
+ $ npm run test:watch
32
+ ```
33
+
34
+ Primary development tasks are defined under `scripts` in `package.json`
35
+ and available via `npm run`.
36
+ View them with
37
+
38
+ ```
39
+ $ npm run
40
+ ```
41
+
42
+ ### Source code
43
+
44
+ The [source code] is hosted on GitHub.
45
+ Clone the project with
46
+
47
+ ```
48
+ $ git clone git@github.com:seamapi/react.git
49
+ ```
50
+
51
+ [source code]: https://github.com/seamapi/react
52
+
53
+ ### Requirements
54
+
55
+ You will need [Node.js] with [npm] and a [Node.js debugging] client.
56
+
57
+ Be sure that all commands run under the correct Node version, e.g.,
58
+ if using [nvm], install the correct version with
59
+
60
+ ```
61
+ $ nvm install
62
+ ```
63
+
64
+ Set the active version for each shell session with
65
+
66
+ ```
67
+ $ nvm use
68
+ ```
69
+
70
+ Install the development dependencies with
71
+
72
+ ```
73
+ $ npm install
74
+ ```
75
+
76
+ [Node.js]: https://nodejs.org/
77
+ [Node.js debugging]: https://nodejs.org/en/docs/guides/debugging-getting-started/
78
+ [npm]: https://www.npmjs.com/
79
+ [nvm]: https://github.com/creationix/nvm
80
+
81
+ ### Publishing
82
+
83
+ #### Automatic
84
+
85
+ New versions are released automatically with [semantic-release].
86
+ as long as commits follow the [Angular Commit Message Conventions].
87
+
88
+ [Angular Commit Message Conventions]: https://semantic-release.gitbook.io/semantic-release/#commit-message-format
89
+ [semantic-release]: https://semantic-release.gitbook.io/
90
+
91
+ #### Manual
92
+
93
+ Publish a new version by triggering a [version workflow_dispatch on GitHub Actions].
94
+ The `version` input will be passed as the first argument to [npm-version].
95
+
96
+ This may be done on the web or using the [GitHub CLI] with
97
+
98
+ ```
99
+ $ gh workflow run version.yml --raw-field version=<version>
100
+ ```
101
+
102
+ [GitHub CLI]: https://cli.github.com/
103
+ [npm-version]: https://docs.npmjs.com/cli/version
104
+ [version workflow_dispatch on GitHub Actions]: https://github.com/seamapi/react/actions?query=workflow%3Aversion
105
+
106
+ ## GitHub Actions
107
+
108
+ _GitHub Actions should already be configured: this section is for reference only._
109
+
110
+ The following repository secrets must be set on [GitHub Actions]:
111
+
112
+ - `NPM_TOKEN`: npm token for installing and publishing packages.
113
+ - `GH_TOKEN`: A personal access token for the bot user with
114
+ `packages:write` and `contents:write` permission.
115
+ - `GIT_USER_NAME`: The GitHub bot user's real name.
116
+ - `GIT_USER_EMAIL`: The GitHub bot user's email.
117
+ - `GPG_PRIVATE_KEY`: The GitHub bot user's [GPG private key].
118
+ - `GPG_PASSPHRASE`: The GitHub bot user's GPG passphrase.
119
+
120
+ [GitHub Actions]: https://github.com/features/actions
121
+ [GPG private key]: https://github.com/marketplace/actions/import-gpg#prerequisites
122
+
123
+ ## Contributing
124
+
125
+ > If using squash merge, edit and ensure the commit message follows the [Angular Commit Message Conventions] specification.
126
+ > Otherwise, each individual commit must follow the [Angular Commit Message Conventions] specification.
127
+
128
+ 1. Create your feature branch (`git checkout -b my-new-feature`).
129
+ 2. Make changes.
130
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
131
+ 4. Push to the branch (`git push origin my-new-feature`).
132
+ 5. Create a new draft pull request.
133
+ 6. Ensure all checks pass.
134
+ 7. Mark your pull request ready for review.
135
+ 8. Wait for the required approval from the code owners.
136
+ 9. Merge when ready.
137
+
138
+ [Angular Commit Message Conventions]: https://semantic-release.gitbook.io/semantic-release/#commit-message-format
139
+
140
+ ## License
141
+
142
+ This npm package is licensed under the [MIT license].
143
+
144
+ [MIT license]: https://github.com/seamapi/react/blob/main/LICENSE.txt
145
+
146
+ ## Warranty
147
+
148
+ This software is provided by the copyright holders and contributors "as is" and
149
+ any express or implied warranties, including, but not limited to, the implied
150
+ warranties of merchantability and fitness for a particular purpose are
151
+ disclaimed. In no event shall the copyright holder or contributors be liable for
152
+ any direct, indirect, incidental, special, exemplary, or consequential damages
153
+ (including, but not limited to, procurement of substitute goods or services;
154
+ loss of use, data, or profits; or business interruption) however caused and on
155
+ any theory of liability, whether in contract, strict liability, or tort
156
+ (including negligence or otherwise) arising in any way out of the use of this
157
+ software, even if advised of the possibility of such damage.
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './lib/index.js';
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './lib/index.js';
2
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { type ReactElement } from 'react';
2
+ import { type UseDevicesParams } from '../../../lib/hooks/use-devices.js';
3
+ export type DeviceManagerProps = UseDevicesParams;
4
+ /**
5
+ * Fetch, list, and manage devices.
6
+ */
7
+ export declare const DeviceManager: (props: DeviceManagerProps) => ReactElement;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import {} from 'react';
3
+ import {} from 'seamapi';
4
+ import { useDevices } from '../../../lib/hooks/use-devices.js';
5
+ /**
6
+ * Fetch, list, and manage devices.
7
+ */
8
+ export const DeviceManager = (props) => {
9
+ const { devices, isLoading, isError, error } = useDevices(props);
10
+ if (isLoading)
11
+ return _jsx("p", { role: 'loading', children: t.loading });
12
+ if (isError)
13
+ return _jsx("p", { children: error?.message });
14
+ return (_jsx("ul", { children: devices?.map((device) => (_jsx(DeviceManagerItem, { ...device }, device.device_id))) }));
15
+ };
16
+ const DeviceManagerItem = (props) => {
17
+ return (_jsxs("li", { children: [_jsxs("p", { children: [t.device_id, ": ", props.device_id] }), _jsxs("p", { children: [t.name, ": ", props.properties.name] })] }));
18
+ };
19
+ const t = {
20
+ loading: 'Loading devices',
21
+ device_id: 'Device ID',
22
+ name: 'Device name',
23
+ };
24
+ //# sourceMappingURL=DeviceManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceManager.js","sourceRoot":"","sources":["../../../src/lib/components/DeviceManager/DeviceManager.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAqB,MAAM,OAAO,CAAA;AACzC,OAAO,EAA4C,MAAM,SAAS,CAAA;AAElE,OAAO,EAAE,UAAU,EAAyB,MAAM,0BAA0B,CAAA;AAI5E;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAyB,EAAgB,EAAE;IACvE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAEhE,IAAI,SAAS;QAAE,OAAO,YAAG,IAAI,EAAC,SAAS,YAAE,CAAC,CAAC,OAAO,GAAK,CAAA;IACvD,IAAI,OAAO;QAAE,OAAO,sBAAI,KAAK,EAAE,OAAO,GAAK,CAAA;IAE3C,OAAO,CACL,uBACG,OAAO,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CACxB,KAAC,iBAAiB,OAA4B,MAAM,IAA5B,MAAM,CAAC,SAAS,CAAgB,CACzD,CAAC,GACC,CACN,CAAA;AACH,CAAC,CAAA;AAED,MAAM,iBAAiB,GAAG,CACxB,KAAqC,EACvB,EAAE;IAChB,OAAO,CACL,yBACE,wBACG,CAAC,CAAC,SAAS,QAAI,KAAK,CAAC,SAAS,IAC7B,EACJ,wBACG,CAAC,CAAC,IAAI,QAAI,KAAK,CAAC,UAAU,CAAC,IAAI,IAC9B,IACD,CACN,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,GAAG;IACR,OAAO,EAAE,iBAAiB;IAC1B,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,aAAa;CACpB,CAAA"}
@@ -0,0 +1 @@
1
+ export * from './DeviceManager/DeviceManager.js';
@@ -0,0 +1,2 @@
1
+ export * from './DeviceManager/DeviceManager.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAA"}
@@ -0,0 +1 @@
1
+ export * from './use-seam.js';
@@ -0,0 +1,2 @@
1
+ export * from './use-seam.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { type UseQueryResult } from '@tanstack/react-query';
2
+ import { type DevicesListRequest, type DevicesListResponse, type SeamError } from 'seamapi';
3
+ export type UseDevicesParams = DevicesListRequest;
4
+ export type UseDevicesData = DevicesListResponse['devices'];
5
+ type Result = UseQueryResult<UseDevicesData, SeamError>;
6
+ type UseDevicesResult = Omit<Result, 'data'> & {
7
+ devices: Result['data'];
8
+ };
9
+ export declare function useDevices(params: DevicesListRequest): UseDevicesResult;
10
+ export {};
@@ -0,0 +1,17 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import {} from 'seamapi';
3
+ import { useSeam } from './use-seam.js';
4
+ export function useDevices(params) {
5
+ const { client } = useSeam();
6
+ const { data, ...rest } = useQuery({
7
+ enabled: client != null,
8
+ queryKey: ['list', 'devices', params],
9
+ queryFn: async () => {
10
+ if (client == null)
11
+ return [];
12
+ return await client?.devices.list(params);
13
+ },
14
+ });
15
+ return { ...rest, devices: data };
16
+ }
17
+ //# sourceMappingURL=use-devices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-devices.js","sourceRoot":"","sources":["../../src/lib/hooks/use-devices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAuB,MAAM,uBAAuB,CAAA;AACrE,OAAO,EAIN,MAAM,SAAS,CAAA;AAEhB,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AASvC,MAAM,UAAU,UAAU,CAAC,MAA0B;IACnD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAAA;IAC5B,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAChC;QACE,OAAO,EAAE,MAAM,IAAI,IAAI;QACvB,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC;QACrC,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,MAAM,IAAI,IAAI;gBAAE,OAAO,EAAE,CAAA;YAC7B,OAAO,MAAM,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC3C,CAAC;KACF,CACF,CAAA;IAED,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AACnC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { Seam } from 'seamapi';
2
+ export declare function useSeam(): {
3
+ client: Seam | null;
4
+ isLoading: boolean;
5
+ isError: boolean;
6
+ error: unknown;
7
+ };
@@ -0,0 +1,62 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { useContext } from 'react';
3
+ import { Seam } from 'seamapi';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { seamContext } from '../../lib/provider.js';
6
+ export function useSeam() {
7
+ const { client, clientOptions, publishableKey, ...context } = useContext(seamContext);
8
+ const [clientSession, setClientSession] = useClientSession();
9
+ const userIdentifierKey = useUserIdentifierKey(context.userIdentifierKey);
10
+ const { isLoading, isError, error, data } = useQuery({
11
+ queryKey: ['client', { client, clientSession, userIdentifierKey }],
12
+ queryFn: async () => {
13
+ if (client != null)
14
+ return client;
15
+ if (publishableKey == null) {
16
+ throw new Error('Missing publishableKey');
17
+ }
18
+ // TODO: Check if client session is still valid before resuming it.
19
+ if (clientSession != null) {
20
+ return new Seam({
21
+ ...clientOptions,
22
+ clientSessionToken: clientSession.token,
23
+ });
24
+ }
25
+ const res = await Seam.getClientSessionToken({
26
+ ...clientOptions,
27
+ publishableKey,
28
+ userIdentifierKey,
29
+ });
30
+ if (!res.ok || res.client_session?.token == null) {
31
+ throw new Error('Failed to get client access token');
32
+ }
33
+ setClientSession(res.client_session);
34
+ return new Seam({
35
+ ...clientOptions,
36
+ clientSessionToken: res.client_session.token,
37
+ });
38
+ },
39
+ });
40
+ return { client: data ?? null, isLoading, isError, error };
41
+ }
42
+ function useClientSession() {
43
+ const localStorageKey = 'seam_client_session';
44
+ const setClientSession = (clientSession) => {
45
+ globalThis?.localStorage?.setItem(localStorageKey, JSON.stringify(clientSession));
46
+ };
47
+ const cachedClientSession = globalThis?.localStorage?.getItem(localStorageKey);
48
+ return [
49
+ cachedClientSession != null ? JSON.parse(cachedClientSession) : null,
50
+ setClientSession,
51
+ ];
52
+ }
53
+ function useUserIdentifierKey(userIdentifierKey) {
54
+ if (userIdentifierKey != null) {
55
+ return userIdentifierKey;
56
+ }
57
+ const localStorageKey = 'seam_user_identifier_key';
58
+ const key = globalThis?.localStorage?.getItem(localStorageKey) ?? uuidv4();
59
+ globalThis?.localStorage?.setItem(localStorageKey, key);
60
+ return key;
61
+ }
62
+ //# sourceMappingURL=use-seam.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-seam.js","sourceRoot":"","sources":["../../src/lib/hooks/use-seam.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './components/index.js';
2
+ export * from './hooks/index.js';
3
+ export * from './provider.js';
package/lib/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './components/index.js';
2
+ export * from './hooks/index.js';
3
+ export * from './provider.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA"}
@@ -0,0 +1,20 @@
1
+ import { type ReactElement, type ReactNode } from 'react';
2
+ import { type Seam, type SeamClientOptions } from 'seamapi';
3
+ declare global {
4
+ var seam: Omit<SeamProviderProps, 'children'> | undefined;
5
+ }
6
+ export interface SeamContext {
7
+ client: Seam | null;
8
+ clientOptions?: SeamClientOptions | undefined;
9
+ publishableKey?: string | undefined;
10
+ userIdentifierKey?: string | undefined;
11
+ }
12
+ export interface SeamProviderProps {
13
+ client?: Seam;
14
+ publishableKey?: string;
15
+ userIdentifierKey?: string;
16
+ endpoint?: string;
17
+ children?: ReactNode;
18
+ }
19
+ export declare function SeamProvider({ children, ...props }: SeamProviderProps): ReactElement;
20
+ export declare const seamContext: import("react").Context<SeamContext>;
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
+ import { createContext, useRef } from 'react';
4
+ import {} from 'seamapi';
5
+ export function SeamProvider({ children, ...props }) {
6
+ const { Provider } = seamContext;
7
+ const queryClientRef = useRef(new QueryClient());
8
+ const contextRef = useRef(createSeamContextValue(props));
9
+ if (contextRef.current.client == null &&
10
+ contextRef.current.publishableKey == null) {
11
+ contextRef.current = defaultSeamContextValue;
12
+ }
13
+ if (contextRef.current.client == null &&
14
+ contextRef.current.publishableKey == null) {
15
+ throw new Error('Must provide either a Seam client or a publishableKey.');
16
+ }
17
+ return (_jsx(QueryClientProvider, { client: queryClientRef.current, children: _jsx(Provider, { value: { ...contextRef.current }, children: children }) }));
18
+ }
19
+ const createDefaultSeamContextValue = () => {
20
+ if (globalThis.seam?.client == null &&
21
+ globalThis.seam?.publishableKey == null) {
22
+ return { client: null };
23
+ }
24
+ try {
25
+ return createSeamContextValue(globalThis.seam);
26
+ }
27
+ catch (err) {
28
+ // eslint-disable-next-line no-console
29
+ console.warn(err);
30
+ return { client: null };
31
+ }
32
+ };
33
+ const createSeamContextValue = ({ client, ...options }) => {
34
+ if (client == null && Object.keys(options).length === 0) {
35
+ return { client: null };
36
+ }
37
+ if (client != null && Object.values(options).some((v) => v == null)) {
38
+ throw new Error('Cannot provide both a Seam client along with other options.');
39
+ }
40
+ const { publishableKey, userIdentifierKey, ...clientOptions } = options;
41
+ if (client != null) {
42
+ return { client };
43
+ }
44
+ return {
45
+ client: null,
46
+ publishableKey,
47
+ userIdentifierKey,
48
+ clientOptions,
49
+ };
50
+ };
51
+ const defaultSeamContextValue = createDefaultSeamContextValue();
52
+ export const seamContext = createContext(defaultSeamContextValue);
53
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/lib/provider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,aAAa,EAAqC,MAAM,EAAE,MAAM,OAAO,CAAA;AAChF,OAAO,EAAqC,MAAM,SAAS,CAAA;AAsB3D,MAAM,UAAU,YAAY,CAAC,EAC3B,QAAQ,EACR,GAAG,KAAK,EACU;IAClB,MAAM,EAAE,QAAQ,EAAE,GAAG,WAAW,CAAA;IAEhC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,CAAA;IAEhD,MAAM,UAAU,GAAG,MAAM,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAA;IACxD,IACE,UAAU,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI;QACjC,UAAU,CAAC,OAAO,CAAC,cAAc,IAAI,IAAI,EACzC;QACA,UAAU,CAAC,OAAO,GAAG,uBAAuB,CAAA;KAC7C;IAED,IACE,UAAU,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI;QACjC,UAAU,CAAC,OAAO,CAAC,cAAc,IAAI,IAAI,EACzC;QACA,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAA;KAC1E;IAED,OAAO,CACL,KAAC,mBAAmB,IAAC,MAAM,EAAE,cAAc,CAAC,OAAO,YACjD,KAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,YAAG,QAAQ,GAAY,GAC7C,CACvB,CAAA;AACH,CAAC;AAED,MAAM,6BAA6B,GAAG,GAAgB,EAAE;IACtD,IACE,UAAU,CAAC,IAAI,EAAE,MAAM,IAAI,IAAI;QAC/B,UAAU,CAAC,IAAI,EAAE,cAAc,IAAI,IAAI,EACvC;QACA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;KACxB;IAED,IAAI;QACF,OAAO,sBAAsB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;KAC/C;IAAC,OAAO,GAAG,EAAE;QACZ,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACjB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;KACxB;AACH,CAAC,CAAA;AAED,MAAM,sBAAsB,GAAG,CAAC,EAC9B,MAAM,EACN,GAAG,OAAO,EAC0B,EAAe,EAAE;IACrD,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;QACvD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;KACxB;IAED,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE;QACnE,MAAM,IAAI,KAAK,CACb,6DAA6D,CAC9D,CAAA;KACF;IAED,MAAM,EAAE,cAAc,EAAE,iBAAiB,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAA;IAEvE,IAAI,MAAM,IAAI,IAAI,EAAE;QAClB,OAAO,EAAE,MAAM,EAAE,CAAA;KAClB;IAED,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,cAAc;QACd,iBAAiB;QACjB,aAAa;KACd,CAAA;AACH,CAAC,CAAA;AAED,MAAM,uBAAuB,GAAG,6BAA6B,EAAE,CAAA;AAE/D,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAc,uBAAuB,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,116 @@
1
+ {
2
+ "name": "@seamapi/react",
3
+ "version": "0.1.0",
4
+ "description": "Seam React component library.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "typings": "index.d.ts",
8
+ "exports": {
9
+ "import": "./index.js"
10
+ },
11
+ "module": "index.js",
12
+ "sideEffects": false,
13
+ "keywords": [
14
+ "node"
15
+ ],
16
+ "homepage": "https://github.com/seamapi/react",
17
+ "bugs": "https://github.com/seamapi/react/issues",
18
+ "repository": "seamapi/react",
19
+ "license": "MIT",
20
+ "author": {
21
+ "name": "Seam Labs, Inc.",
22
+ "email": "devops@getseam.com"
23
+ },
24
+ "files": [
25
+ "index.js",
26
+ "index.js.map",
27
+ "index.d.ts",
28
+ "lib",
29
+ "src",
30
+ "!test",
31
+ "!**/*.test.ts",
32
+ "!**/*.test.tsx",
33
+ "!**/*.stories.ts",
34
+ "!**/*.stories.tsx",
35
+ "!src/stories"
36
+ ],
37
+ "scripts": {
38
+ "start": "storybook dev --port 6006",
39
+ "build": "npm run build:ts",
40
+ "postbuild": "node ./index.js",
41
+ "build:ts": "tsc --project tsconfig.build.json",
42
+ "prebuild:ts": "del 'index.*' lib",
43
+ "postbuild:ts": "tsc-alias --project tsconfig.build.json",
44
+ "docs:build": "storybook build --docs --output-dir docs",
45
+ "docs:start": "storybook dev --docs --port 6007",
46
+ "typecheck": "tsc",
47
+ "dependencies": "dpdm --exit-code circular:1 src/index.ts",
48
+ "test": "jest --collect-coverage",
49
+ "test:update": "jest --updateSnapshot",
50
+ "test:watch": "jest --watch",
51
+ "test:inspect": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand",
52
+ "lint": "eslint --ignore-path .gitignore --ext .js,.cjs,.mjs,.ts,.tsx .",
53
+ "prelint": "prettier --check --ignore-path .gitignore .",
54
+ "postversion": "git push --follow-tags",
55
+ "example:basic": "vite --config examples/basic/vite.config.js",
56
+ "format": "eslint --ignore-path .gitignore --fix --ext .js,.cjs,.mjs,.ts,.tsx .",
57
+ "preformat": "prettier --write --ignore-path .gitignore ."
58
+ },
59
+ "engines": {
60
+ "node": ">=16.13.0",
61
+ "npm": ">= 8.1.0"
62
+ },
63
+ "peerDependencies": {
64
+ "react": ">=18.0.0",
65
+ "react-dom": ">=18.0.0",
66
+ "seamapi": ">=5.15.0-alpha.0"
67
+ },
68
+ "dependencies": {
69
+ "@tanstack/react-query": "^4.28.0",
70
+ "uuid": "^9.0.0"
71
+ },
72
+ "devDependencies": {
73
+ "@babel/preset-env": "^7.21.4",
74
+ "@babel/preset-react": "^7.18.6",
75
+ "@babel/preset-typescript": "^7.21.4",
76
+ "@storybook/addon-essentials": "^7.0.2",
77
+ "@storybook/addon-interactions": "^7.0.2",
78
+ "@storybook/addon-links": "^7.0.2",
79
+ "@storybook/blocks": "^7.0.2",
80
+ "@storybook/react": "^7.0.2",
81
+ "@storybook/react-webpack5": "^7.0.2",
82
+ "@storybook/testing-library": "^0.1.0",
83
+ "@testing-library/react": "^14.0.0",
84
+ "@types/jest": "^29.5.0",
85
+ "@types/node": "^18.11.18",
86
+ "@types/react": "^18.0.33",
87
+ "@types/react-dom": "^18.0.11",
88
+ "@types/uuid": "^9.0.1",
89
+ "@vitejs/plugin-react": "^3.1.0",
90
+ "del-cli": "^5.0.0",
91
+ "dpdm": "^3.13.0",
92
+ "eslint": "^8.9.0",
93
+ "eslint-config-prettier": "^8.4.0",
94
+ "eslint-config-standard-jsx": "^11.0.0",
95
+ "eslint-config-standard-react": "^13.0.0",
96
+ "eslint-config-standard-with-typescript": "^34.0.0",
97
+ "eslint-plugin-simple-import-sort": "^10.0.0",
98
+ "eslint-plugin-storybook": "^0.6.11",
99
+ "eslint-plugin-unused-imports": "^2.0.0",
100
+ "http-proxy-middleware": "^2.0.6",
101
+ "jest": "^29.5.0",
102
+ "jest-environment-jsdom": "^29.5.0",
103
+ "prettier": "^2.3.0",
104
+ "prop-types": "^15.8.1",
105
+ "react": "^18.2.0",
106
+ "react-dom": "^18.2.0",
107
+ "seamapi": "^5.15.0-alpha.0",
108
+ "storybook": "^7.0.2",
109
+ "ts-jest": "^29.1.0",
110
+ "tsc-alias": "^1.8.5",
111
+ "tsconfig-paths-webpack-plugin": "^4.0.1",
112
+ "typescript": "^5.0.2",
113
+ "vite": "^4.2.1",
114
+ "vite-tsconfig-paths": "^4.0.8"
115
+ }
116
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'lib/index.js'
@@ -0,0 +1,45 @@
1
+ import { type ReactElement } from 'react'
2
+ import { type CommonDeviceProperties, type Device } from 'seamapi'
3
+
4
+ import { useDevices, type UseDevicesParams } from 'lib/hooks/use-devices.js'
5
+
6
+ export type DeviceManagerProps = UseDevicesParams
7
+
8
+ /**
9
+ * Fetch, list, and manage devices.
10
+ */
11
+ export const DeviceManager = (props: DeviceManagerProps): ReactElement => {
12
+ const { devices, isLoading, isError, error } = useDevices(props)
13
+
14
+ if (isLoading) return <p role='loading'>{t.loading}</p>
15
+ if (isError) return <p>{error?.message}</p>
16
+
17
+ return (
18
+ <ul>
19
+ {devices?.map((device) => (
20
+ <DeviceManagerItem key={device.device_id} {...device} />
21
+ ))}
22
+ </ul>
23
+ )
24
+ }
25
+
26
+ const DeviceManagerItem = (
27
+ props: Device<CommonDeviceProperties>
28
+ ): ReactElement => {
29
+ return (
30
+ <li>
31
+ <p>
32
+ {t.device_id}: {props.device_id}
33
+ </p>
34
+ <p>
35
+ {t.name}: {props.properties.name}
36
+ </p>
37
+ </li>
38
+ )
39
+ }
40
+
41
+ const t = {
42
+ loading: 'Loading devices',
43
+ device_id: 'Device ID',
44
+ name: 'Device name',
45
+ }
@@ -0,0 +1 @@
1
+ export * from './DeviceManager/DeviceManager.js'
@@ -0,0 +1 @@
1
+ export * from './use-seam.js'
@@ -0,0 +1,31 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query'
2
+ import {
3
+ type DevicesListRequest,
4
+ type DevicesListResponse,
5
+ type SeamError,
6
+ } from 'seamapi'
7
+
8
+ import { useSeam } from './use-seam.js'
9
+
10
+ export type UseDevicesParams = DevicesListRequest
11
+ export type UseDevicesData = DevicesListResponse['devices']
12
+
13
+ type Result = UseQueryResult<UseDevicesData, SeamError>
14
+
15
+ type UseDevicesResult = Omit<Result, 'data'> & { devices: Result['data'] }
16
+
17
+ export function useDevices(params: DevicesListRequest): UseDevicesResult {
18
+ const { client } = useSeam()
19
+ const { data, ...rest } = useQuery<DevicesListResponse['devices'], SeamError>(
20
+ {
21
+ enabled: client != null,
22
+ queryKey: ['list', 'devices', params],
23
+ queryFn: async () => {
24
+ if (client == null) return []
25
+ return await client?.devices.list(params)
26
+ },
27
+ }
28
+ )
29
+
30
+ return { ...rest, devices: data }
31
+ }
@@ -0,0 +1,88 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { useContext } from 'react'
3
+ import { type ClientSession, Seam } from 'seamapi'
4
+ import { v4 as uuidv4 } from 'uuid'
5
+
6
+ import { seamContext } from 'lib/provider.js'
7
+
8
+ export function useSeam(): {
9
+ client: Seam | null
10
+ isLoading: boolean
11
+ isError: boolean
12
+ error: unknown
13
+ } {
14
+ const { client, clientOptions, publishableKey, ...context } =
15
+ useContext(seamContext)
16
+ const [clientSession, setClientSession] = useClientSession()
17
+ const userIdentifierKey = useUserIdentifierKey(context.userIdentifierKey)
18
+
19
+ const { isLoading, isError, error, data } = useQuery<Seam>({
20
+ queryKey: ['client', { client, clientSession, userIdentifierKey }],
21
+ queryFn: async () => {
22
+ if (client != null) return client
23
+
24
+ if (publishableKey == null) {
25
+ throw new Error('Missing publishableKey')
26
+ }
27
+
28
+ // TODO: Check if client session is still valid before resuming it.
29
+ if (clientSession != null) {
30
+ return new Seam({
31
+ ...clientOptions,
32
+ clientSessionToken: clientSession.token,
33
+ })
34
+ }
35
+
36
+ const res = await Seam.getClientSessionToken({
37
+ ...clientOptions,
38
+ publishableKey,
39
+ userIdentifierKey,
40
+ })
41
+
42
+ if (!res.ok || res.client_session?.token == null) {
43
+ throw new Error('Failed to get client access token')
44
+ }
45
+
46
+ setClientSession(res.client_session)
47
+
48
+ return new Seam({
49
+ ...clientOptions,
50
+ clientSessionToken: res.client_session.token,
51
+ })
52
+ },
53
+ })
54
+
55
+ return { client: data ?? null, isLoading, isError, error }
56
+ }
57
+
58
+ function useClientSession(): [
59
+ clientSession: ClientSession | null,
60
+ setClientSession: (clientSession: ClientSession) => void
61
+ ] {
62
+ const localStorageKey = 'seam_client_session'
63
+
64
+ const setClientSession = (clientSession: ClientSession): void => {
65
+ globalThis?.localStorage?.setItem(
66
+ localStorageKey,
67
+ JSON.stringify(clientSession)
68
+ )
69
+ }
70
+
71
+ const cachedClientSession = globalThis?.localStorage?.getItem(localStorageKey)
72
+
73
+ return [
74
+ cachedClientSession != null ? JSON.parse(cachedClientSession) : null,
75
+ setClientSession,
76
+ ]
77
+ }
78
+
79
+ function useUserIdentifierKey(userIdentifierKey: string | undefined): string {
80
+ if (userIdentifierKey != null) {
81
+ return userIdentifierKey
82
+ }
83
+
84
+ const localStorageKey = 'seam_user_identifier_key'
85
+ const key = globalThis?.localStorage?.getItem(localStorageKey) ?? uuidv4()
86
+ globalThis?.localStorage?.setItem(localStorageKey, key)
87
+ return key
88
+ }
@@ -0,0 +1,3 @@
1
+ export * from './components/index.js'
2
+ export * from './hooks/index.js'
3
+ export * from './provider.js'
@@ -0,0 +1,102 @@
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2
+ import { createContext, type ReactElement, type ReactNode, useRef } from 'react'
3
+ import { type Seam, type SeamClientOptions } from 'seamapi'
4
+
5
+ declare global {
6
+ // eslint-disable-next-line no-var
7
+ var seam: Omit<SeamProviderProps, 'children'> | undefined
8
+ }
9
+
10
+ export interface SeamContext {
11
+ client: Seam | null
12
+ clientOptions?: SeamClientOptions | undefined
13
+ publishableKey?: string | undefined
14
+ userIdentifierKey?: string | undefined
15
+ }
16
+
17
+ export interface SeamProviderProps {
18
+ client?: Seam
19
+ publishableKey?: string
20
+ userIdentifierKey?: string
21
+ endpoint?: string
22
+ children?: ReactNode
23
+ }
24
+
25
+ export function SeamProvider({
26
+ children,
27
+ ...props
28
+ }: SeamProviderProps): ReactElement {
29
+ const { Provider } = seamContext
30
+
31
+ const queryClientRef = useRef(new QueryClient())
32
+
33
+ const contextRef = useRef(createSeamContextValue(props))
34
+ if (
35
+ contextRef.current.client == null &&
36
+ contextRef.current.publishableKey == null
37
+ ) {
38
+ contextRef.current = defaultSeamContextValue
39
+ }
40
+
41
+ if (
42
+ contextRef.current.client == null &&
43
+ contextRef.current.publishableKey == null
44
+ ) {
45
+ throw new Error('Must provide either a Seam client or a publishableKey.')
46
+ }
47
+
48
+ return (
49
+ <QueryClientProvider client={queryClientRef.current}>
50
+ <Provider value={{ ...contextRef.current }}>{children}</Provider>
51
+ </QueryClientProvider>
52
+ )
53
+ }
54
+
55
+ const createDefaultSeamContextValue = (): SeamContext => {
56
+ if (
57
+ globalThis.seam?.client == null &&
58
+ globalThis.seam?.publishableKey == null
59
+ ) {
60
+ return { client: null }
61
+ }
62
+
63
+ try {
64
+ return createSeamContextValue(globalThis.seam)
65
+ } catch (err) {
66
+ // eslint-disable-next-line no-console
67
+ console.warn(err)
68
+ return { client: null }
69
+ }
70
+ }
71
+
72
+ const createSeamContextValue = ({
73
+ client,
74
+ ...options
75
+ }: Omit<SeamProviderProps, 'children'>): SeamContext => {
76
+ if (client == null && Object.keys(options).length === 0) {
77
+ return { client: null }
78
+ }
79
+
80
+ if (client != null && Object.values(options).some((v) => v == null)) {
81
+ throw new Error(
82
+ 'Cannot provide both a Seam client along with other options.'
83
+ )
84
+ }
85
+
86
+ const { publishableKey, userIdentifierKey, ...clientOptions } = options
87
+
88
+ if (client != null) {
89
+ return { client }
90
+ }
91
+
92
+ return {
93
+ client: null,
94
+ publishableKey,
95
+ userIdentifierKey,
96
+ clientOptions,
97
+ }
98
+ }
99
+
100
+ const defaultSeamContextValue = createDefaultSeamContextValue()
101
+
102
+ export const seamContext = createContext<SeamContext>(defaultSeamContextValue)