@seamapi/react 4.14.2 → 5.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.
Files changed (191) hide show
  1. package/README.md +111 -3
  2. package/dist/ecosystem.js +45 -0
  3. package/dist/ecosystem.js.map +1 -0
  4. package/dist/elements.js +11464 -11007
  5. package/dist/elements.js.map +1 -1
  6. package/dist/index.css +469 -345
  7. package/dist/index.css.map +1 -1
  8. package/dist/index.min.css +1 -1
  9. package/dist/index.min.css.map +1 -1
  10. package/lib/seam/SeamProvider.d.ts +10 -3
  11. package/lib/seam/SeamProvider.js +5 -3
  12. package/lib/seam/SeamProvider.js.map +1 -1
  13. package/lib/seam/components/CompatibleApps/CompatibleApps.d.ts +26 -0
  14. package/lib/seam/components/CompatibleApps/CompatibleApps.js +126 -0
  15. package/lib/seam/components/CompatibleApps/CompatibleApps.js.map +1 -0
  16. package/lib/seam/components/SupportedDevices/DeviceModelRow.d.ts +8 -0
  17. package/lib/seam/components/SupportedDevices/DeviceModelRow.js +31 -0
  18. package/lib/seam/components/SupportedDevices/DeviceModelRow.js.map +1 -0
  19. package/lib/seam/components/SupportedDevices/FilterSelect.d.ts +12 -0
  20. package/lib/seam/components/SupportedDevices/FilterSelect.js +9 -0
  21. package/lib/seam/components/SupportedDevices/FilterSelect.js.map +1 -0
  22. package/lib/seam/components/SupportedDevices/ManufacturerSection.d.ts +10 -0
  23. package/lib/seam/components/SupportedDevices/ManufacturerSection.js +35 -0
  24. package/lib/seam/components/SupportedDevices/ManufacturerSection.js.map +1 -0
  25. package/lib/seam/components/SupportedDevices/SupportedDevices.d.ts +24 -0
  26. package/lib/seam/components/SupportedDevices/SupportedDevices.js +151 -0
  27. package/lib/seam/components/SupportedDevices/SupportedDevices.js.map +1 -0
  28. package/lib/seam/components/SupportedDevices/device-models.d.ts +47 -0
  29. package/lib/seam/components/SupportedDevices/device-models.js +217 -0
  30. package/lib/seam/components/SupportedDevices/device-models.js.map +1 -0
  31. package/lib/seam/components/index.d.ts +2 -2
  32. package/lib/seam/components/index.js +2 -2
  33. package/lib/seam/components/index.js.map +1 -1
  34. package/lib/seam/ecosystem/EcosystemProvider.d.ts +37 -0
  35. package/lib/seam/ecosystem/EcosystemProvider.js +56 -0
  36. package/lib/seam/ecosystem/EcosystemProvider.js.map +1 -0
  37. package/lib/seam/ecosystem/client.d.ts +28 -0
  38. package/lib/seam/ecosystem/client.js +74 -0
  39. package/lib/seam/ecosystem/client.js.map +1 -0
  40. package/lib/seam/ecosystem/index.d.ts +9 -0
  41. package/lib/seam/ecosystem/index.js +8 -0
  42. package/lib/seam/ecosystem/index.js.map +1 -0
  43. package/lib/seam/ecosystem/types.d.ts +253 -0
  44. package/lib/seam/ecosystem/types.js +10 -0
  45. package/lib/seam/ecosystem/types.js.map +1 -0
  46. package/lib/seam/ecosystem/use-compatible-partners.d.ts +21 -0
  47. package/lib/seam/ecosystem/use-compatible-partners.js +36 -0
  48. package/lib/seam/ecosystem/use-compatible-partners.js.map +1 -0
  49. package/lib/seam/ecosystem/use-ecosystem-device-models.d.ts +20 -0
  50. package/lib/seam/ecosystem/use-ecosystem-device-models.js +51 -0
  51. package/lib/seam/ecosystem/use-ecosystem-device-models.js.map +1 -0
  52. package/lib/seam/ecosystem/use-ecosystem-manufacturers.d.ts +11 -0
  53. package/lib/seam/ecosystem/use-ecosystem-manufacturers.js +49 -0
  54. package/lib/seam/ecosystem/use-ecosystem-manufacturers.js.map +1 -0
  55. package/lib/seam/ecosystem/use-ecosystem-query-result.d.ts +7 -0
  56. package/lib/seam/ecosystem/use-ecosystem-query-result.js +7 -0
  57. package/lib/seam/ecosystem/use-ecosystem-query-result.js.map +1 -0
  58. package/lib/seam/ecosystem/use-embed-key-resolution.d.ts +20 -0
  59. package/lib/seam/ecosystem/use-embed-key-resolution.js +51 -0
  60. package/lib/seam/ecosystem/use-embed-key-resolution.js.map +1 -0
  61. package/lib/seam/index.d.ts +1 -0
  62. package/lib/seam/index.js +1 -0
  63. package/lib/seam/index.js.map +1 -1
  64. package/lib/seam/seam-components-class-name.d.ts +1 -0
  65. package/lib/seam/seam-components-class-name.js +2 -0
  66. package/lib/seam/seam-components-class-name.js.map +1 -0
  67. package/lib/ui/ecosystem/SearchField.d.ts +9 -0
  68. package/lib/ui/ecosystem/SearchField.js +8 -0
  69. package/lib/ui/ecosystem/SearchField.js.map +1 -0
  70. package/lib/ui/ecosystem/StateMessage.d.ts +8 -0
  71. package/lib/ui/ecosystem/StateMessage.js +5 -0
  72. package/lib/ui/ecosystem/StateMessage.js.map +1 -0
  73. package/lib/ui/ecosystem/errors.d.ts +5 -0
  74. package/lib/ui/ecosystem/errors.js +25 -0
  75. package/lib/ui/ecosystem/errors.js.map +1 -0
  76. package/lib/ui/ecosystem/storybook.d.ts +15 -0
  77. package/lib/ui/ecosystem/storybook.js +16 -0
  78. package/lib/ui/ecosystem/storybook.js.map +1 -0
  79. package/lib/ui/ecosystem/theme.d.ts +6 -0
  80. package/lib/ui/ecosystem/theme.js +11 -0
  81. package/lib/ui/ecosystem/theme.js.map +1 -0
  82. package/lib/version.d.ts +1 -1
  83. package/lib/version.js +1 -1
  84. package/lib/version.js.map +1 -1
  85. package/package.json +11 -3
  86. package/src/ecosystem.d.ts +2 -0
  87. package/src/ecosystem.ts +12 -0
  88. package/src/elements.ts +4 -0
  89. package/src/lib/ecosystem-element.tsx +154 -0
  90. package/src/lib/element.tsx +6 -0
  91. package/src/lib/seam/SeamProvider.tsx +18 -2
  92. package/src/lib/seam/components/CompatibleApps/CompatibleApps.element.ts +19 -0
  93. package/src/lib/seam/components/CompatibleApps/CompatibleApps.tsx +344 -0
  94. package/src/lib/seam/components/SupportedDevices/DeviceModelRow.tsx +105 -0
  95. package/src/lib/seam/components/SupportedDevices/FilterSelect.tsx +43 -0
  96. package/src/lib/seam/components/SupportedDevices/ManufacturerSection.tsx +114 -0
  97. package/src/lib/seam/components/SupportedDevices/SupportedDevices.element.ts +17 -0
  98. package/src/lib/seam/components/SupportedDevices/SupportedDevices.tsx +382 -0
  99. package/src/lib/seam/components/SupportedDevices/device-models.ts +326 -0
  100. package/src/lib/seam/components/ecosystem-elements.ts +2 -0
  101. package/src/lib/seam/components/elements.ts +0 -2
  102. package/src/lib/seam/components/index.ts +2 -2
  103. package/src/lib/seam/ecosystem/EcosystemProvider.tsx +133 -0
  104. package/src/lib/seam/ecosystem/client.ts +116 -0
  105. package/src/lib/seam/ecosystem/index.ts +19 -0
  106. package/src/lib/seam/ecosystem/types.ts +334 -0
  107. package/src/lib/seam/ecosystem/use-compatible-partners.ts +80 -0
  108. package/src/lib/seam/ecosystem/use-ecosystem-device-models.ts +96 -0
  109. package/src/lib/seam/ecosystem/use-ecosystem-manufacturers.ts +86 -0
  110. package/src/lib/seam/ecosystem/use-ecosystem-query-result.ts +19 -0
  111. package/src/lib/seam/ecosystem/use-embed-key-resolution.ts +80 -0
  112. package/src/lib/seam/index.ts +1 -0
  113. package/src/lib/seam/seam-components-class-name.ts +1 -0
  114. package/src/lib/ui/ecosystem/SearchField.tsx +32 -0
  115. package/src/lib/ui/ecosystem/StateMessage.tsx +29 -0
  116. package/src/lib/ui/ecosystem/errors.ts +30 -0
  117. package/src/lib/ui/ecosystem/storybook.tsx +40 -0
  118. package/src/lib/ui/ecosystem/theme.ts +14 -0
  119. package/src/lib/version.ts +1 -1
  120. package/src/styles/_compatible-apps.scss +102 -0
  121. package/src/styles/_ecosystem.scss +295 -0
  122. package/src/styles/_main.scss +6 -4
  123. package/src/styles/_supported-devices.scss +153 -0
  124. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.d.ts +0 -20
  125. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js +0 -22
  126. package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js.map +0 -1
  127. package/lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.d.ts +0 -6
  128. package/lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.js +0 -8
  129. package/lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.js.map +0 -1
  130. package/lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.d.ts +0 -9
  131. package/lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.js +0 -14
  132. package/lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.js.map +0 -1
  133. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.d.ts +0 -13
  134. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js +0 -58
  135. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js.map +0 -1
  136. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContentRows.d.ts +0 -7
  137. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContentRows.js +0 -6
  138. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContentRows.js.map +0 -1
  139. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.d.ts +0 -12
  140. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.js +0 -58
  141. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.js.map +0 -1
  142. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.d.ts +0 -8
  143. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.js +0 -39
  144. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.js.map +0 -1
  145. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.d.ts +0 -10
  146. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js +0 -35
  147. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js.map +0 -1
  148. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.d.ts +0 -11
  149. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.js +0 -20
  150. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.js.map +0 -1
  151. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTableManufacturerKeys.d.ts +0 -6
  152. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTableManufacturerKeys.js +0 -21
  153. package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTableManufacturerKeys.js.map +0 -1
  154. package/lib/seam/components/SupportedDeviceTable/use-device-model.d.ts +0 -7
  155. package/lib/seam/components/SupportedDeviceTable/use-device-model.js +0 -21
  156. package/lib/seam/components/SupportedDeviceTable/use-device-model.js.map +0 -1
  157. package/lib/seam/components/SupportedDeviceTable/use-device-models.d.ts +0 -7
  158. package/lib/seam/components/SupportedDeviceTable/use-device-models.js +0 -30
  159. package/lib/seam/components/SupportedDeviceTable/use-device-models.js.map +0 -1
  160. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.d.ts +0 -15
  161. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js +0 -45
  162. package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js.map +0 -1
  163. package/lib/seam/components/SupportedDeviceTable/use-filtered-manufacturers.d.ts +0 -10
  164. package/lib/seam/components/SupportedDeviceTable/use-filtered-manufacturers.js +0 -42
  165. package/lib/seam/components/SupportedDeviceTable/use-filtered-manufacturers.js.map +0 -1
  166. package/lib/seam/components/SupportedDeviceTable/use-manufacturer.d.ts +0 -7
  167. package/lib/seam/components/SupportedDeviceTable/use-manufacturer.js +0 -17
  168. package/lib/seam/components/SupportedDeviceTable/use-manufacturer.js.map +0 -1
  169. package/lib/seam/components/SupportedDeviceTable/use-manufacturers.d.ts +0 -7
  170. package/lib/seam/components/SupportedDeviceTable/use-manufacturers.js +0 -26
  171. package/lib/seam/components/SupportedDeviceTable/use-manufacturers.js.map +0 -1
  172. package/src/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.tsx +0 -78
  173. package/src/lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.tsx +0 -13
  174. package/src/lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.tsx +0 -32
  175. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.tsx +0 -161
  176. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContentRows.tsx +0 -22
  177. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.tsx +0 -145
  178. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.tsx +0 -109
  179. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.tsx +0 -93
  180. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.element.ts +0 -15
  181. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.tsx +0 -70
  182. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTableManufacturerKeys.element.ts +0 -9
  183. package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTableManufacturerKeys.tsx +0 -64
  184. package/src/lib/seam/components/SupportedDeviceTable/use-device-model.ts +0 -44
  185. package/src/lib/seam/components/SupportedDeviceTable/use-device-models.ts +0 -57
  186. package/src/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.ts +0 -88
  187. package/src/lib/seam/components/SupportedDeviceTable/use-filtered-manufacturers.ts +0 -69
  188. package/src/lib/seam/components/SupportedDeviceTable/use-manufacturer.ts +0 -40
  189. package/src/lib/seam/components/SupportedDeviceTable/use-manufacturers.ts +0 -53
  190. package/src/styles/_supported-device-table-manufacturer-keys.scss +0 -20
  191. package/src/styles/_supported-device-table.scss +0 -412
@@ -5,5 +5,3 @@ export * as CreateAccessCodeForm from './CreateAccessCodeForm/CreateAccessCodeFo
5
5
  export * as DeviceDetails from './DeviceDetails/DeviceDetails.element.js'
6
6
  export * as DeviceTable from './DeviceTable/DeviceTable.element.js'
7
7
  export * as EditAccessCodeForm from './EditAccessCodeForm/EditAccessCodeForm.element.js'
8
- export * as SupportedDeviceTable from './SupportedDeviceTable/SupportedDeviceTable.element.js'
9
- export * as SupportedDeviceTableManufacturerKeys from './SupportedDeviceTable/SupportedDeviceTableManufacturerKeys.element.js'
@@ -1,10 +1,10 @@
1
1
  export * from './AccessCodeDetails/AccessCodeDetails.js'
2
2
  export * from './AccessCodeTable/AccessCodeTable.js'
3
3
  export * from './common-props.js'
4
+ export * from './CompatibleApps/CompatibleApps.js'
4
5
  export * from './ConnectAccountButton/ConnectAccountButton.js'
5
6
  export * from './CreateAccessCodeForm/CreateAccessCodeForm.js'
6
7
  export * from './DeviceDetails/DeviceDetails.js'
7
8
  export * from './DeviceTable/DeviceTable.js'
8
9
  export * from './EditAccessCodeForm/EditAccessCodeForm.js'
9
- export * from './SupportedDeviceTable/SupportedDeviceTable.js'
10
- export * from './SupportedDeviceTable/SupportedDeviceTableManufacturerKeys.js'
10
+ export * from './SupportedDevices/SupportedDevices.js'
@@ -0,0 +1,133 @@
1
+ import {
2
+ QueryClient,
3
+ QueryClientContext,
4
+ QueryClientProvider,
5
+ } from '@tanstack/react-query'
6
+ import {
7
+ createContext,
8
+ type PropsWithChildren,
9
+ useContext,
10
+ useMemo,
11
+ } from 'react'
12
+
13
+ import { defaultEcosystemEndpoint } from 'lib/seam/ecosystem/client.js'
14
+ import { seamComponentsClassName } from 'lib/seam/seam-components-class-name.js'
15
+ import { useSeamFont } from 'lib/seam/use-seam-font.js'
16
+ import { useSeamStyles } from 'lib/seam/use-seam-styles.js'
17
+
18
+ export interface EcosystemContext {
19
+ /** The embed key sent as `X-Embed-Key`, if any. */
20
+ embedKey: string | undefined
21
+ /** The Ecosystem API endpoint, ending in `/`. */
22
+ endpoint: string
23
+ }
24
+
25
+ export interface EcosystemOptions {
26
+ /**
27
+ * Seam Ecosystem embed key (`eco_live_…`).
28
+ * Required outside seam.co: the key's allowed origins must include the page origin.
29
+ */
30
+ embedKey?: string | undefined
31
+ /** Seam Ecosystem API endpoint. Defaults to `https://ecosystem.seam.co/v1/`. */
32
+ ecosystemEndpoint?: string | undefined
33
+ }
34
+
35
+ export interface EcosystemProviderProps
36
+ extends EcosystemOptions,
37
+ PropsWithChildren {
38
+ queryClient?: QueryClient | undefined
39
+ disableCssInjection?: boolean | undefined
40
+ disableFontInjection?: boolean | undefined
41
+ unminifiyCss?: boolean | undefined
42
+ }
43
+
44
+ const defaultQueryClient = new QueryClient()
45
+
46
+ /**
47
+ * Provides the Seam Ecosystem context for `CompatibleApps` and `SupportedDevices`.
48
+ *
49
+ * Unlike `SeamProvider`, this does not need a Seam publishable key
50
+ * and does not load the Seam API client, so it stays lightweight.
51
+ * Inside a `SeamProvider`, pass `embedKey` to the `SeamProvider` instead.
52
+ */
53
+ export function EcosystemProvider({
54
+ children,
55
+ queryClient,
56
+ disableCssInjection = false,
57
+ disableFontInjection = false,
58
+ unminifiyCss = false,
59
+ ...options
60
+ }: EcosystemProviderProps): JSX.Element {
61
+ useSeamStyles({ disabled: disableCssInjection, unminified: unminifiyCss })
62
+ useSeamFont({ disabled: disableFontInjection })
63
+
64
+ const queryClientFromContext = useContext(QueryClientContext)
65
+
66
+ return (
67
+ <div className={seamComponentsClassName}>
68
+ <QueryClientProvider
69
+ client={
70
+ queryClient ??
71
+ queryClientFromContext ??
72
+ globalThis.seamQueryClient ??
73
+ defaultQueryClient
74
+ }
75
+ >
76
+ <EcosystemContextProvider {...options}>
77
+ {children}
78
+ </EcosystemContextProvider>
79
+ </QueryClientProvider>
80
+ </div>
81
+ )
82
+ }
83
+
84
+ /**
85
+ * Provides only the Ecosystem context; used by `SeamProvider`.
86
+ * Unset options inherit from an enclosing provider.
87
+ */
88
+ export function EcosystemContextProvider({
89
+ children,
90
+ embedKey,
91
+ ecosystemEndpoint,
92
+ }: EcosystemOptions & PropsWithChildren): JSX.Element {
93
+ const parent = useContext(ecosystemContext)
94
+
95
+ const value = useMemo(
96
+ () => ({
97
+ embedKey: normalizeEmbedKey(embedKey) ?? parent.embedKey,
98
+ endpoint: normalizeEndpoint(ecosystemEndpoint) ?? parent.endpoint,
99
+ }),
100
+ [embedKey, ecosystemEndpoint, parent]
101
+ )
102
+
103
+ return (
104
+ <ecosystemContext.Provider value={value}>
105
+ {children}
106
+ </ecosystemContext.Provider>
107
+ )
108
+ }
109
+
110
+ export function useEcosystemContext(): EcosystemContext {
111
+ return useContext(ecosystemContext)
112
+ }
113
+
114
+ const normalizeEmbedKey = (
115
+ embedKey: string | undefined
116
+ ): string | undefined => {
117
+ const key = embedKey?.trim()
118
+ if (key == null || key.length === 0) return undefined
119
+ return key
120
+ }
121
+
122
+ const normalizeEndpoint = (
123
+ endpoint: string | undefined
124
+ ): string | undefined => {
125
+ const value = endpoint?.trim()
126
+ if (value == null || value.length === 0) return undefined
127
+ return value.endsWith('/') ? value : `${value}/`
128
+ }
129
+
130
+ const ecosystemContext = createContext<EcosystemContext>({
131
+ embedKey: undefined,
132
+ endpoint: defaultEcosystemEndpoint,
133
+ })
@@ -0,0 +1,116 @@
1
+ import type { EcosystemErrorResponse } from './types.js'
2
+
3
+ export const defaultEcosystemEndpoint = 'https://ecosystem.seam.co/v1/'
4
+
5
+ export type EcosystemQueryParams = Record<
6
+ string,
7
+ string | number | boolean | string[] | null | undefined
8
+ >
9
+
10
+ export interface EcosystemRequestOptions {
11
+ endpoint: string
12
+ embedKey?: string | undefined
13
+ params?: EcosystemQueryParams
14
+ signal?: AbortSignal
15
+ }
16
+
17
+ /**
18
+ * An error response from the Ecosystem API.
19
+ * The `type` is the API error type, e.g., `embed_key_required`,
20
+ * `invalid_embed_key` or `origin_not_allowed`.
21
+ */
22
+ export class EcosystemApiError extends Error {
23
+ readonly status: number
24
+ readonly type: string
25
+ readonly requestId: string | null
26
+
27
+ constructor({
28
+ status,
29
+ type,
30
+ message,
31
+ requestId = null,
32
+ }: {
33
+ status: number
34
+ type: string
35
+ message: string
36
+ requestId?: string | null
37
+ }) {
38
+ super(message)
39
+ this.name = 'EcosystemApiError'
40
+ this.status = status
41
+ this.type = type
42
+ this.requestId = requestId
43
+ }
44
+ }
45
+
46
+ /** True when the error means the embed key is missing, invalid or not allowed here. */
47
+ export const isEmbedKeyError = (error: unknown): boolean =>
48
+ error instanceof EcosystemApiError &&
49
+ (error.status === 401 || error.status === 403)
50
+
51
+ export const getEcosystemUrl = (
52
+ endpoint: string,
53
+ path: string,
54
+ params: EcosystemQueryParams = {}
55
+ ): URL => {
56
+ const base = endpoint.endsWith('/') ? endpoint : `${endpoint}/`
57
+ const url = new URL(path, new URL(base, globalThis.location?.href))
58
+ for (const [key, value] of Object.entries(params)) {
59
+ if (value == null) continue
60
+ if (Array.isArray(value)) {
61
+ if (value.length === 0) continue
62
+ url.searchParams.set(key, value.join(','))
63
+ continue
64
+ }
65
+ if (typeof value === 'string' && value.length === 0) continue
66
+ url.searchParams.set(key, String(value))
67
+ }
68
+ return url
69
+ }
70
+
71
+ export const fetchEcosystem = async <T>(
72
+ path: string,
73
+ { endpoint, embedKey, params, signal }: EcosystemRequestOptions
74
+ ): Promise<T> => {
75
+ const url = getEcosystemUrl(endpoint, path, params)
76
+
77
+ const headers: Record<string, string> = { Accept: 'application/json' }
78
+ if (embedKey != null && embedKey.length > 0) {
79
+ headers['X-Embed-Key'] = embedKey
80
+ }
81
+
82
+ const res = await globalThis.fetch(url, {
83
+ headers,
84
+ ...(signal == null ? {} : { signal }),
85
+ })
86
+
87
+ const body = (await res.json().catch(() => null)) as unknown
88
+
89
+ if (!res.ok) {
90
+ const error = isErrorResponse(body) ? body.error : null
91
+ throw new EcosystemApiError({
92
+ status: res.status,
93
+ type: error?.type ?? 'unknown_error',
94
+ message:
95
+ error?.message ?? `Ecosystem request failed with status ${res.status}`,
96
+ requestId: error?.request_id ?? null,
97
+ })
98
+ }
99
+
100
+ if (body == null || typeof body !== 'object') {
101
+ throw new EcosystemApiError({
102
+ status: res.status,
103
+ type: 'invalid_response',
104
+ message: 'Ecosystem returned an invalid response',
105
+ })
106
+ }
107
+
108
+ return body as T
109
+ }
110
+
111
+ const isErrorResponse = (body: unknown): body is EcosystemErrorResponse =>
112
+ body != null &&
113
+ typeof body === 'object' &&
114
+ 'error' in body &&
115
+ body.error != null &&
116
+ typeof body.error === 'object'
@@ -0,0 +1,19 @@
1
+ export {
2
+ defaultEcosystemEndpoint,
3
+ EcosystemApiError,
4
+ isEmbedKeyError,
5
+ } from './client.js'
6
+ export {
7
+ type EcosystemContext,
8
+ type EcosystemOptions,
9
+ EcosystemProvider,
10
+ type EcosystemProviderProps,
11
+ useEcosystemContext,
12
+ } from './EcosystemProvider.js'
13
+ export * from './types.js'
14
+ export * from './use-compatible-partners.js'
15
+ export * from './use-ecosystem-device-models.js'
16
+ export * from './use-ecosystem-manufacturers.js'
17
+ export type { UseEcosystemQueryResult } from './use-ecosystem-query-result.js'
18
+ export * from './use-embed-key-resolution.js'
19
+ export type { EcosystemTheme } from 'lib/ui/ecosystem/theme.js'
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Response types for the public Seam Ecosystem API (PartnerDB and DeviceDB).
3
+ *
4
+ * Hand-copied from the zod schemas in `seamapi/ecosystem` `public-types/`
5
+ * (origin/main 733edf3, 2026-10-16) to avoid a zod runtime dependency.
6
+ * Fields stored verbatim from the source as JSON are typed as `unknown`.
7
+ * Keep in sync with Ecosystem until it publishes its types as a package.
8
+ */
9
+
10
+ // Partners (PartnerDB)
11
+
12
+ export interface EcosystemIndustry {
13
+ industry_id: string
14
+ slug: string
15
+ display_name: string
16
+ short_name: string | null
17
+ }
18
+
19
+ export interface EcosystemSolution {
20
+ solution_id: string
21
+ slug: string
22
+ display_name: string
23
+ short_name: string | null
24
+ type: 'software' | 'service'
25
+ }
26
+
27
+ export type PartnerAssetKind =
28
+ | 'square_logo'
29
+ | 'square_logo_mono'
30
+ | 'horizontal_logo'
31
+ | 'horizontal_logo_mono'
32
+ | 'product_image'
33
+
34
+ export interface PartnerAsset {
35
+ partner_asset_id: string
36
+ kind: PartnerAssetKind
37
+ url: string
38
+ mime: string
39
+ width: number | null
40
+ height: number | null
41
+ sort_order: number
42
+ }
43
+
44
+ export interface Partner {
45
+ partner_id: string
46
+ slug: string
47
+ display_name: string
48
+ website: string | null
49
+ description_md: string | null
50
+ is_live: boolean
51
+ is_featured: boolean
52
+ legacy_industry_featured: boolean
53
+ industries: EcosystemIndustry[]
54
+ solutions: EcosystemSolution[]
55
+ assets: PartnerAsset[]
56
+ created_at: string
57
+ updated_at: string
58
+ }
59
+
60
+ export type SupportLevel =
61
+ | 'device_category'
62
+ | 'manufacturer'
63
+ | 'provider'
64
+ | 'capability'
65
+
66
+ export type ObservedStatus = 'active' | 'trace' | 'none' | 'unknown'
67
+
68
+ export type DeclaredStatus = 'enabled' | 'disabled' | 'not_via_seam'
69
+
70
+ export interface SupportResolution {
71
+ supported: boolean
72
+ resolved_level: SupportLevel | null
73
+ declared_status: DeclaredStatus | null
74
+ observed_status: ObservedStatus | null
75
+ note: string | null
76
+ }
77
+
78
+ export interface CompatiblePartner extends Partner {
79
+ support: SupportResolution
80
+ }
81
+
82
+ // Embed keys
83
+
84
+ export type EmbedKeyOwnerKind = 'partner' | 'manufacturer' | 'seam'
85
+
86
+ /** The public record of an embed key, as returned by `embed/resolve`. */
87
+ export interface EmbedKey {
88
+ embed_key_id: string
89
+ display_name: string
90
+ owner_kind: EmbedKeyOwnerKind
91
+ partner_id: string | null
92
+ manufacturer_slug: string | null
93
+ allowed_origins: string[]
94
+ config: Record<string, unknown>
95
+ revoked_at: string | null
96
+ created_at: string
97
+ }
98
+
99
+ export interface EmbedCapabilityExclusion {
100
+ device_category: string
101
+ /** Empty means every manufacturer in the category. */
102
+ manufacturer_slug: string
103
+ /** Empty means every provider in the category. */
104
+ provider_slug: string
105
+ }
106
+
107
+ export interface EmbedExclusions {
108
+ excluded_manufacturers: string[]
109
+ excluded_device_types: string[]
110
+ unsupported_providers: string[]
111
+ capability: string
112
+ capability_exclusions: EmbedCapabilityExclusion[]
113
+ }
114
+
115
+ /**
116
+ * Manufacturer context for manufacturer-owned keys.
117
+ * Not returned by Ecosystem yet (DEVDB-439); read when present.
118
+ */
119
+ export interface EmbedManufacturerContext {
120
+ manufacturer_slug: string
121
+ }
122
+
123
+ export interface EmbedResolution {
124
+ embed_key: EmbedKey
125
+ exclusions: EmbedExclusions
126
+ manufacturer_context?: EmbedManufacturerContext | null
127
+ }
128
+
129
+ // Devices (DeviceDB)
130
+
131
+ export interface MediaFormat {
132
+ object_key: string
133
+ url: string
134
+ mime: string | null
135
+ width: number | null
136
+ height: number | null
137
+ bytes: number | null
138
+ sha256: string | null
139
+ }
140
+
141
+ export type MediaFormatName = 'thumbnail' | 'small' | 'medium' | 'large'
142
+
143
+ export type DeviceAssetKind =
144
+ | 'square_logo'
145
+ | 'square_logo_mono'
146
+ | 'horizontal_logo'
147
+ | 'horizontal_logo_mono'
148
+ | 'default_device_image'
149
+ | 'featured'
150
+ | 'variant_image'
151
+
152
+ export interface DeviceAsset {
153
+ asset_id: string
154
+ kind: DeviceAssetKind
155
+ object_key: string
156
+ url: string
157
+ mime: string | null
158
+ width: number | null
159
+ height: number | null
160
+ bytes: number | null
161
+ sha256: string | null
162
+ sort_order: number
163
+ formats: Partial<Record<MediaFormatName, MediaFormat>>
164
+ created_at: string
165
+ updated_at: string
166
+ }
167
+
168
+ export interface DeviceTypeRef {
169
+ device_type_id: string
170
+ slug: string
171
+ display_name: string
172
+ is_accessory: boolean
173
+ }
174
+
175
+ export interface ManufacturerRef {
176
+ manufacturer_id: string
177
+ slug: string
178
+ display_name: string | null
179
+ }
180
+
181
+ export interface ProviderRef {
182
+ provider_id: string
183
+ slug: string
184
+ display_name: string | null
185
+ }
186
+
187
+ export type CapabilityName =
188
+ | 'device_connectivity'
189
+ | 'can_program_online_access_codes'
190
+ | 'can_program_offline_access_codes'
191
+ | 'can_remotely_unlock'
192
+ | 'can_remotely_lock'
193
+ | 'can_encode_plastic_cards'
194
+ | 'can_provision_mobile_keys'
195
+ | 'can_provision_wallet_keys'
196
+ | 'can_hvac_cool'
197
+ | 'can_hvac_heat'
198
+ | 'can_hvac_auto'
199
+ | 'can_hvac_fan'
200
+ | 'can_program_thermostat_weekly_programs'
201
+ | 'can_monitor'
202
+
203
+ export type CapabilitySupported = 'yes' | 'no' | 'conditionally'
204
+
205
+ export type IntegrationSupportLevel =
206
+ | 'inquire'
207
+ | 'unavailable'
208
+ | 'planned'
209
+ | 'in-progress'
210
+ | 'beta'
211
+ | 'stable'
212
+
213
+ export interface CapabilityReport {
214
+ details: string
215
+ requirements?: Record<
216
+ string,
217
+ { message: string; device_slugs?: string[]; [key: string]: unknown }
218
+ >
219
+ dependencies?: string[]
220
+ [key: string]: unknown
221
+ }
222
+
223
+ export interface CapabilityDetail {
224
+ capability_id: string
225
+ name: CapabilityName
226
+ supported: CapabilitySupported
227
+ integration_support_level: IntegrationSupportLevel | null
228
+ report: CapabilityReport | null
229
+ report_raw: CapabilityReport | null
230
+ locale: string
231
+ computed_at: string
232
+ }
233
+
234
+ export interface ProviderCapabilities {
235
+ provider: ProviderRef
236
+ capabilities: CapabilityDetail[]
237
+ }
238
+
239
+ export interface DeviceModelVariant {
240
+ device_model_variant_id: string
241
+ color_name: string | null
242
+ style_name: string | null
243
+ primary_color_hex: string | null
244
+ manufacturer_model_id: string | null
245
+ asin: string | null
246
+ gtins: unknown
247
+ marketplace_listings: unknown
248
+ sort_order: number
249
+ images: DeviceAsset[]
250
+ created_at: string
251
+ updated_at: string
252
+ }
253
+
254
+ export interface DeviceModel {
255
+ device_model_id: string
256
+ slug: string
257
+ display_name: string | null
258
+ description_md: string | null
259
+ product_url: string | null
260
+ is_forbidden_to_3dparty: boolean | null
261
+ no_longer_sold: boolean | null
262
+ shop_listings: unknown
263
+ last_commerce_search_at: string | null
264
+ manufacturer: ManufacturerRef | null
265
+ device_type: DeviceTypeRef | null
266
+ device_type_properties: unknown
267
+ variants: DeviceModelVariant[]
268
+ featured_image: DeviceAsset | null
269
+ providers: ProviderRef[]
270
+ capabilities_by_provider: ProviderCapabilities[]
271
+ capabilities_stale: boolean
272
+ created_at: string
273
+ updated_at: string
274
+ }
275
+
276
+ export interface ManufacturerDefaultDeviceImage {
277
+ device_type: DeviceTypeRef
278
+ asset: DeviceAsset
279
+ }
280
+
281
+ export interface Manufacturer {
282
+ manufacturer_id: string
283
+ slug: string
284
+ display_name: string | null
285
+ website: string | null
286
+ headquarters: string | null
287
+ country_of_origin: string | null
288
+ parent_org: string | null
289
+ inc_date: string | null
290
+ customer_support_phone: string | null
291
+ customer_support_email: string | null
292
+ customer_support_url: string | null
293
+ description_md: string | null
294
+ seam_api_guide: string | null
295
+ commerce_footprint: unknown
296
+ last_commerce_search_at: string | null
297
+ logos: DeviceAsset[]
298
+ default_device_images: ManufacturerDefaultDeviceImage[]
299
+ providers: ProviderRef[]
300
+ device_model_count: number
301
+ created_at: string
302
+ updated_at: string
303
+ }
304
+
305
+ export interface Pagination {
306
+ has_next_page: boolean
307
+ next_page_cursor: string | null
308
+ }
309
+
310
+ // Responses
311
+
312
+ export interface CompatibilityResponse {
313
+ partners: CompatiblePartner[]
314
+ }
315
+
316
+ export interface DeviceModelsListResponse {
317
+ device_models: DeviceModel[]
318
+ pagination: Pagination
319
+ }
320
+
321
+ export interface ManufacturersListResponse {
322
+ manufacturers: Manufacturer[]
323
+ pagination: Pagination
324
+ }
325
+
326
+ export interface EcosystemErrorResponse {
327
+ ok: false
328
+ error: {
329
+ type: string
330
+ message: string
331
+ data?: Record<string, unknown>
332
+ request_id?: string
333
+ }
334
+ }
@@ -0,0 +1,80 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+
3
+ import {
4
+ type EcosystemApiError,
5
+ fetchEcosystem,
6
+ } from 'lib/seam/ecosystem/client.js'
7
+ import { useEcosystemContext } from 'lib/seam/ecosystem/EcosystemProvider.js'
8
+ import type {
9
+ CompatibilityResponse,
10
+ CompatiblePartner,
11
+ } from 'lib/seam/ecosystem/types.js'
12
+ import {
13
+ retryEcosystemQuery,
14
+ type UseEcosystemQueryResult,
15
+ } from 'lib/seam/ecosystem/use-ecosystem-query-result.js'
16
+
17
+ export interface UseCompatiblePartnersParams {
18
+ /** Device category, e.g., `smartlock`, `thermostat`, `sensor`, `intercom`. */
19
+ deviceType: string
20
+ /** DeviceDB manufacturer slug, e.g., `schlage`. */
21
+ manufacturer?: string | undefined
22
+ /** DeviceDB provider slug, e.g., `salto-ks`. */
23
+ provider?: string | undefined
24
+ /** DeviceDB capability, e.g., `can_provision_mobile_keys`. */
25
+ capability?: string | undefined
26
+ /** Industry slug, e.g., `short-term-rental`. */
27
+ industry?: string | undefined
28
+ }
29
+
30
+ export type UseCompatiblePartnersData = CompatiblePartner[]
31
+
32
+ /**
33
+ * The live partners that support a device category, manufacturer, provider
34
+ * or capability through Seam, from Ecosystem `GET /v1/compatibility`.
35
+ * Declared-first: a partner is listed only where Seam or the partner declared support.
36
+ */
37
+ export function useCompatiblePartners({
38
+ deviceType,
39
+ manufacturer = '',
40
+ provider = '',
41
+ capability = '',
42
+ industry = '',
43
+ }: UseCompatiblePartnersParams): UseEcosystemQueryResult<
44
+ 'partners',
45
+ UseCompatiblePartnersData
46
+ > {
47
+ const { embedKey, endpoint } = useEcosystemContext()
48
+
49
+ const params = {
50
+ device_category: deviceType.trim(),
51
+ manufacturer_slug: manufacturer.trim(),
52
+ provider_slug: provider.trim(),
53
+ capability: capability.trim(),
54
+ industry: industry.trim(),
55
+ }
56
+
57
+ const { data, ...rest } = useQuery<
58
+ UseCompatiblePartnersData,
59
+ EcosystemApiError
60
+ >({
61
+ enabled: params.device_category.length > 0,
62
+ queryKey: [
63
+ 'ecosystem',
64
+ endpoint,
65
+ embedKey ?? null,
66
+ 'compatibility',
67
+ params,
68
+ ],
69
+ queryFn: async ({ signal }) => {
70
+ const { partners } = await fetchEcosystem<CompatibilityResponse>(
71
+ 'compatibility',
72
+ { endpoint, embedKey, params, signal }
73
+ )
74
+ return partners
75
+ },
76
+ retry: retryEcosystemQuery,
77
+ })
78
+
79
+ return { ...rest, partners: data }
80
+ }