@seamapi/react 5.0.0 → 5.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.
Files changed (125) hide show
  1. package/README.md +111 -2
  2. package/dist/ecosystem.js +45 -0
  3. package/dist/ecosystem.js.map +1 -0
  4. package/dist/elements.js +12872 -11737
  5. package/dist/elements.js.map +1 -1
  6. package/dist/index.css +469 -0
  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 -0
  32. package/lib/seam/components/index.js +2 -0
  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 +312 -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-model-summaries.d.ts +11 -0
  50. package/lib/seam/ecosystem/use-ecosystem-device-model-summaries.js +53 -0
  51. package/lib/seam/ecosystem/use-ecosystem-device-model-summaries.js.map +1 -0
  52. package/lib/seam/ecosystem/use-ecosystem-device-models.d.ts +20 -0
  53. package/lib/seam/ecosystem/use-ecosystem-device-models.js +51 -0
  54. package/lib/seam/ecosystem/use-ecosystem-device-models.js.map +1 -0
  55. package/lib/seam/ecosystem/use-ecosystem-manufacturers.d.ts +11 -0
  56. package/lib/seam/ecosystem/use-ecosystem-manufacturers.js +49 -0
  57. package/lib/seam/ecosystem/use-ecosystem-manufacturers.js.map +1 -0
  58. package/lib/seam/ecosystem/use-ecosystem-query-result.d.ts +7 -0
  59. package/lib/seam/ecosystem/use-ecosystem-query-result.js +7 -0
  60. package/lib/seam/ecosystem/use-ecosystem-query-result.js.map +1 -0
  61. package/lib/seam/ecosystem/use-embed-key-resolution.d.ts +20 -0
  62. package/lib/seam/ecosystem/use-embed-key-resolution.js +51 -0
  63. package/lib/seam/ecosystem/use-embed-key-resolution.js.map +1 -0
  64. package/lib/seam/index.d.ts +1 -0
  65. package/lib/seam/index.js +1 -0
  66. package/lib/seam/index.js.map +1 -1
  67. package/lib/seam/seam-components-class-name.d.ts +1 -0
  68. package/lib/seam/seam-components-class-name.js +2 -0
  69. package/lib/seam/seam-components-class-name.js.map +1 -0
  70. package/lib/ui/ecosystem/SearchField.d.ts +9 -0
  71. package/lib/ui/ecosystem/SearchField.js +8 -0
  72. package/lib/ui/ecosystem/SearchField.js.map +1 -0
  73. package/lib/ui/ecosystem/StateMessage.d.ts +8 -0
  74. package/lib/ui/ecosystem/StateMessage.js +5 -0
  75. package/lib/ui/ecosystem/StateMessage.js.map +1 -0
  76. package/lib/ui/ecosystem/errors.d.ts +5 -0
  77. package/lib/ui/ecosystem/errors.js +25 -0
  78. package/lib/ui/ecosystem/errors.js.map +1 -0
  79. package/lib/ui/ecosystem/storybook.d.ts +15 -0
  80. package/lib/ui/ecosystem/storybook.js +16 -0
  81. package/lib/ui/ecosystem/storybook.js.map +1 -0
  82. package/lib/ui/ecosystem/theme.d.ts +6 -0
  83. package/lib/ui/ecosystem/theme.js +11 -0
  84. package/lib/ui/ecosystem/theme.js.map +1 -0
  85. package/lib/version.d.ts +1 -1
  86. package/lib/version.js +1 -1
  87. package/package.json +11 -3
  88. package/src/ecosystem.d.ts +2 -0
  89. package/src/ecosystem.ts +12 -0
  90. package/src/elements.ts +4 -0
  91. package/src/lib/ecosystem-element.tsx +154 -0
  92. package/src/lib/element.tsx +6 -0
  93. package/src/lib/seam/SeamProvider.tsx +18 -2
  94. package/src/lib/seam/components/CompatibleApps/CompatibleApps.element.ts +19 -0
  95. package/src/lib/seam/components/CompatibleApps/CompatibleApps.tsx +344 -0
  96. package/src/lib/seam/components/SupportedDevices/DeviceModelRow.tsx +108 -0
  97. package/src/lib/seam/components/SupportedDevices/FilterSelect.tsx +43 -0
  98. package/src/lib/seam/components/SupportedDevices/ManufacturerSection.tsx +114 -0
  99. package/src/lib/seam/components/SupportedDevices/SupportedDevices.element.ts +17 -0
  100. package/src/lib/seam/components/SupportedDevices/SupportedDevices.tsx +382 -0
  101. package/src/lib/seam/components/SupportedDevices/device-models.ts +326 -0
  102. package/src/lib/seam/components/ecosystem-elements.ts +2 -0
  103. package/src/lib/seam/components/index.ts +2 -0
  104. package/src/lib/seam/ecosystem/EcosystemProvider.tsx +133 -0
  105. package/src/lib/seam/ecosystem/client.ts +116 -0
  106. package/src/lib/seam/ecosystem/index.ts +19 -0
  107. package/src/lib/seam/ecosystem/types.ts +402 -0
  108. package/src/lib/seam/ecosystem/use-compatible-partners.ts +80 -0
  109. package/src/lib/seam/ecosystem/use-ecosystem-device-model-summaries.ts +89 -0
  110. package/src/lib/seam/ecosystem/use-ecosystem-device-models.ts +96 -0
  111. package/src/lib/seam/ecosystem/use-ecosystem-manufacturers.ts +86 -0
  112. package/src/lib/seam/ecosystem/use-ecosystem-query-result.ts +19 -0
  113. package/src/lib/seam/ecosystem/use-embed-key-resolution.ts +80 -0
  114. package/src/lib/seam/index.ts +1 -0
  115. package/src/lib/seam/seam-components-class-name.ts +1 -0
  116. package/src/lib/ui/ecosystem/SearchField.tsx +32 -0
  117. package/src/lib/ui/ecosystem/StateMessage.tsx +29 -0
  118. package/src/lib/ui/ecosystem/errors.ts +30 -0
  119. package/src/lib/ui/ecosystem/storybook.tsx +40 -0
  120. package/src/lib/ui/ecosystem/theme.ts +14 -0
  121. package/src/lib/version.ts +1 -1
  122. package/src/styles/_compatible-apps.scss +102 -0
  123. package/src/styles/_ecosystem.scss +295 -0
  124. package/src/styles/_main.scss +6 -0
  125. package/src/styles/_supported-devices.scss +153 -0
@@ -1,8 +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'
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,402 @@
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 055335c, 2026-10-17) 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
+ /** The manufacturer a manufacturer-owned key scopes DeviceDB widgets to (DEVDB-439). */
116
+ export interface EmbedManufacturerContext {
117
+ manufacturer_slug: string
118
+ }
119
+
120
+ export interface EmbedResolution {
121
+ embed_key: EmbedKey
122
+ /** Set for manufacturer-owned keys; null for partner and Seam keys. */
123
+ manufacturer_context?: EmbedManufacturerContext | null
124
+ exclusions: EmbedExclusions
125
+ }
126
+
127
+ // Devices (DeviceDB)
128
+
129
+ export interface MediaFormat {
130
+ object_key: string
131
+ url: string
132
+ mime: string | null
133
+ width: number | null
134
+ height: number | null
135
+ bytes: number | null
136
+ sha256: string | null
137
+ }
138
+
139
+ export type MediaFormatName = 'thumbnail' | 'small' | 'medium' | 'large'
140
+
141
+ export type DeviceAssetKind =
142
+ | 'square_logo'
143
+ | 'square_logo_mono'
144
+ | 'horizontal_logo'
145
+ | 'horizontal_logo_mono'
146
+ | 'default_device_image'
147
+ | 'featured'
148
+ | 'variant_image'
149
+
150
+ export interface DeviceAsset {
151
+ asset_id: string
152
+ kind: DeviceAssetKind
153
+ object_key: string
154
+ url: string
155
+ mime: string | null
156
+ width: number | null
157
+ height: number | null
158
+ bytes: number | null
159
+ sha256: string | null
160
+ sort_order: number
161
+ formats: Partial<Record<MediaFormatName, MediaFormat>>
162
+ created_at: string
163
+ updated_at: string
164
+ }
165
+
166
+ export interface DeviceTypeRef {
167
+ device_type_id: string
168
+ slug: string
169
+ display_name: string
170
+ is_accessory: boolean
171
+ }
172
+
173
+ export interface ManufacturerRef {
174
+ manufacturer_id: string
175
+ slug: string
176
+ display_name: string | null
177
+ }
178
+
179
+ export interface ProviderRef {
180
+ provider_id: string
181
+ slug: string
182
+ display_name: string | null
183
+ }
184
+
185
+ export type CapabilityName =
186
+ | 'device_connectivity'
187
+ | 'can_program_online_access_codes'
188
+ | 'can_program_offline_access_codes'
189
+ | 'can_remotely_unlock'
190
+ | 'can_remotely_lock'
191
+ | 'can_encode_plastic_cards'
192
+ | 'can_provision_mobile_keys'
193
+ | 'can_provision_wallet_keys'
194
+ | 'can_hvac_cool'
195
+ | 'can_hvac_heat'
196
+ | 'can_hvac_auto'
197
+ | 'can_hvac_fan'
198
+ | 'can_program_thermostat_weekly_programs'
199
+ | 'can_monitor'
200
+
201
+ export type CapabilitySupported = 'yes' | 'no' | 'conditionally'
202
+
203
+ export type IntegrationSupportLevel =
204
+ | 'inquire'
205
+ | 'unavailable'
206
+ | 'planned'
207
+ | 'in-progress'
208
+ | 'beta'
209
+ | 'stable'
210
+
211
+ export interface CapabilityReport {
212
+ details: string
213
+ requirements?: Record<
214
+ string,
215
+ { message: string; device_slugs?: string[]; [key: string]: unknown }
216
+ >
217
+ dependencies?: string[]
218
+ [key: string]: unknown
219
+ }
220
+
221
+ export interface CapabilityDetail {
222
+ capability_id: string
223
+ name: CapabilityName
224
+ supported: CapabilitySupported
225
+ integration_support_level: IntegrationSupportLevel | null
226
+ report: CapabilityReport | null
227
+ report_raw: CapabilityReport | null
228
+ locale: string
229
+ computed_at: string
230
+ }
231
+
232
+ export interface ProviderCapabilities {
233
+ provider: ProviderRef
234
+ capabilities: CapabilityDetail[]
235
+ }
236
+
237
+ export interface DeviceModelVariant {
238
+ device_model_variant_id: string
239
+ color_name: string | null
240
+ style_name: string | null
241
+ primary_color_hex: string | null
242
+ manufacturer_model_id: string | null
243
+ asin: string | null
244
+ gtins: unknown
245
+ marketplace_listings: unknown
246
+ sort_order: number
247
+ images: DeviceAsset[]
248
+ created_at: string
249
+ updated_at: string
250
+ }
251
+
252
+ export interface DeviceModel {
253
+ device_model_id: string
254
+ slug: string
255
+ display_name: string | null
256
+ description_md: string | null
257
+ product_url: string | null
258
+ is_forbidden_to_3dparty: boolean | null
259
+ no_longer_sold: boolean | null
260
+ shop_listings: unknown
261
+ last_commerce_search_at: string | null
262
+ manufacturer: ManufacturerRef | null
263
+ device_type: DeviceTypeRef | null
264
+ device_type_properties: unknown
265
+ variants: DeviceModelVariant[]
266
+ featured_image: DeviceAsset | null
267
+ providers: ProviderRef[]
268
+ capabilities_by_provider: ProviderCapabilities[]
269
+ capabilities_stale: boolean
270
+ created_at: string
271
+ updated_at: string
272
+ }
273
+
274
+ // Device model summaries (`device_models/summaries/list`, DEVDB-437):
275
+ // the same models in a slim shape, without storage internals or commerce data.
276
+
277
+ export interface DeviceImageFormat {
278
+ url: string
279
+ width: number | null
280
+ height: number | null
281
+ }
282
+
283
+ export interface DeviceImage {
284
+ url: string
285
+ mime: string | null
286
+ width: number | null
287
+ height: number | null
288
+ formats: Partial<Record<MediaFormatName, DeviceImageFormat>>
289
+ }
290
+
291
+ export interface CapabilitySummary {
292
+ name: CapabilityName
293
+ supported: CapabilitySupported
294
+ integration_support_level: IntegrationSupportLevel | null
295
+ report: CapabilityReport | null
296
+ }
297
+
298
+ export interface DeviceModelSummaryVariant {
299
+ device_model_variant_id: string
300
+ color_name: string | null
301
+ style_name: string | null
302
+ primary_color_hex: string | null
303
+ manufacturer_model_id: string | null
304
+ images: DeviceImage[]
305
+ /** Only with `include=commerce`. */
306
+ asin?: string | null
307
+ /** Only with `include=commerce`. */
308
+ gtins?: unknown
309
+ /** Only with `include=commerce`. */
310
+ marketplace_listings?: unknown
311
+ }
312
+
313
+ export interface DeviceModelSummary {
314
+ device_model_id: string
315
+ slug: string
316
+ display_name: string | null
317
+ description_md: string | null
318
+ product_url: string | null
319
+ no_longer_sold: boolean | null
320
+ is_forbidden_to_3dparty: boolean | null
321
+ manufacturer: ManufacturerRef | null
322
+ device_type: DeviceTypeRef | null
323
+ device_type_properties: unknown
324
+ featured_image: DeviceImage | null
325
+ variants: DeviceModelSummaryVariant[]
326
+ providers: ProviderRef[]
327
+ capabilities_by_provider: Array<{
328
+ provider: ProviderRef
329
+ capabilities: CapabilitySummary[]
330
+ }>
331
+ capabilities_stale: boolean
332
+ /** Only with `include=commerce`. */
333
+ shop_listings?: unknown
334
+ /** Only with `include=commerce`. */
335
+ last_commerce_search_at?: string | null
336
+ updated_at: string
337
+ }
338
+
339
+ export interface ManufacturerDefaultDeviceImage {
340
+ device_type: DeviceTypeRef
341
+ asset: DeviceAsset
342
+ }
343
+
344
+ export interface Manufacturer {
345
+ manufacturer_id: string
346
+ slug: string
347
+ display_name: string | null
348
+ website: string | null
349
+ headquarters: string | null
350
+ country_of_origin: string | null
351
+ parent_org: string | null
352
+ inc_date: string | null
353
+ customer_support_phone: string | null
354
+ customer_support_email: string | null
355
+ customer_support_url: string | null
356
+ description_md: string | null
357
+ seam_api_guide: string | null
358
+ commerce_footprint: unknown
359
+ last_commerce_search_at: string | null
360
+ logos: DeviceAsset[]
361
+ default_device_images: ManufacturerDefaultDeviceImage[]
362
+ providers: ProviderRef[]
363
+ device_model_count: number
364
+ created_at: string
365
+ updated_at: string
366
+ }
367
+
368
+ export interface Pagination {
369
+ has_next_page: boolean
370
+ next_page_cursor: string | null
371
+ }
372
+
373
+ // Responses
374
+
375
+ export interface CompatibilityResponse {
376
+ partners: CompatiblePartner[]
377
+ }
378
+
379
+ export interface DeviceModelsListResponse {
380
+ device_models: DeviceModel[]
381
+ pagination: Pagination
382
+ }
383
+
384
+ export interface DeviceModelSummariesListResponse {
385
+ device_models: DeviceModelSummary[]
386
+ pagination: Pagination
387
+ }
388
+
389
+ export interface ManufacturersListResponse {
390
+ manufacturers: Manufacturer[]
391
+ pagination: Pagination
392
+ }
393
+
394
+ export interface EcosystemErrorResponse {
395
+ ok: false
396
+ error: {
397
+ type: string
398
+ message: string
399
+ data?: Record<string, unknown>
400
+ request_id?: string
401
+ }
402
+ }