@seamapi/http 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connect.cjs +1807 -96
- package/dist/connect.cjs.map +1 -1
- package/dist/connect.d.cts +472 -15
- package/lib/params-serializer.d.ts +5 -0
- package/lib/params-serializer.js +42 -0
- package/lib/params-serializer.js.map +1 -0
- package/lib/seam/connect/auth.d.ts +4 -3
- package/lib/seam/connect/auth.js +59 -8
- package/lib/seam/connect/auth.js.map +1 -1
- package/lib/seam/connect/client.d.ts +10 -10
- package/lib/seam/connect/client.js +16 -49
- package/lib/seam/connect/client.js.map +1 -1
- package/lib/seam/connect/index.d.ts +2 -1
- package/lib/seam/connect/index.js +2 -1
- package/lib/seam/connect/index.js.map +1 -1
- package/lib/seam/connect/options.d.ts +23 -0
- package/lib/seam/connect/options.js +39 -0
- package/lib/seam/connect/options.js.map +1 -0
- package/lib/seam/connect/parse-options.d.ts +6 -2
- package/lib/seam/connect/parse-options.js +35 -10
- package/lib/seam/connect/parse-options.js.map +1 -1
- package/lib/seam/connect/routes/access-codes-unmanaged.d.ts +27 -0
- package/lib/seam/connect/routes/access-codes-unmanaged.js +84 -0
- package/lib/seam/connect/routes/access-codes-unmanaged.js.map +1 -0
- package/lib/seam/connect/routes/access-codes.d.ts +38 -0
- package/lib/seam/connect/routes/access-codes.js +113 -0
- package/lib/seam/connect/routes/access-codes.js.map +1 -0
- package/lib/seam/connect/routes/acs-access-groups.d.ts +36 -0
- package/lib/seam/connect/routes/acs-access-groups.js +107 -0
- package/lib/seam/connect/routes/acs-access-groups.js.map +1 -0
- package/lib/seam/connect/routes/acs-credentials.d.ts +24 -0
- package/lib/seam/connect/routes/acs-credentials.js +78 -0
- package/lib/seam/connect/routes/acs-credentials.js.map +1 -0
- package/lib/seam/connect/routes/acs-systems.d.ts +18 -0
- package/lib/seam/connect/routes/acs-systems.js +63 -0
- package/lib/seam/connect/routes/acs-systems.js.map +1 -0
- package/lib/seam/connect/routes/acs-users.d.ts +39 -0
- package/lib/seam/connect/routes/acs-users.js +113 -0
- package/lib/seam/connect/routes/acs-users.js.map +1 -0
- package/lib/seam/connect/routes/acs.d.ts +18 -0
- package/lib/seam/connect/routes/acs.js +63 -0
- package/lib/seam/connect/routes/acs.js.map +1 -0
- package/lib/seam/connect/routes/action-attempts.d.ts +18 -0
- package/lib/seam/connect/routes/action-attempts.js +63 -0
- package/lib/seam/connect/routes/action-attempts.js.map +1 -0
- package/lib/seam/connect/routes/client-sessions.d.ts +30 -0
- package/lib/seam/connect/routes/client-sessions.js +93 -0
- package/lib/seam/connect/routes/client-sessions.js.map +1 -0
- package/lib/seam/connect/routes/connect-webviews.d.ts +27 -0
- package/lib/seam/connect/routes/connect-webviews.js +85 -0
- package/lib/seam/connect/routes/connect-webviews.js.map +1 -0
- package/lib/seam/connect/routes/connected-accounts.d.ts +21 -0
- package/lib/seam/connect/routes/connected-accounts.js +70 -0
- package/lib/seam/connect/routes/connected-accounts.js.map +1 -0
- package/lib/seam/connect/routes/devices-unmanaged.d.ts +21 -0
- package/lib/seam/connect/routes/devices-unmanaged.js +70 -0
- package/lib/seam/connect/routes/devices-unmanaged.js.map +1 -0
- package/lib/seam/connect/routes/devices.d.ts +29 -0
- package/lib/seam/connect/routes/devices.js +89 -0
- package/lib/seam/connect/routes/devices.js.map +1 -0
- package/lib/seam/connect/routes/events.d.ts +18 -0
- package/lib/seam/connect/routes/events.js +63 -0
- package/lib/seam/connect/routes/events.js.map +1 -0
- package/lib/seam/connect/routes/index.d.ts +21 -0
- package/lib/seam/connect/routes/index.js +22 -0
- package/lib/seam/connect/routes/index.js.map +1 -0
- package/lib/seam/connect/routes/locks.d.ts +24 -0
- package/lib/seam/connect/routes/locks.js +79 -0
- package/lib/seam/connect/routes/locks.js.map +1 -0
- package/lib/seam/connect/routes/noise-sensors-noise-thresholds.d.ts +27 -0
- package/lib/seam/connect/routes/noise-sensors-noise-thresholds.js +84 -0
- package/lib/seam/connect/routes/noise-sensors-noise-thresholds.js.map +1 -0
- package/lib/seam/connect/routes/noise-sensors.d.ts +12 -0
- package/lib/seam/connect/routes/noise-sensors.js +51 -0
- package/lib/seam/connect/routes/noise-sensors.js.map +1 -0
- package/lib/seam/connect/routes/thermostats-climate-setting-schedules.d.ts +27 -0
- package/lib/seam/connect/routes/thermostats-climate-setting-schedules.js +85 -0
- package/lib/seam/connect/routes/thermostats-climate-setting-schedules.js.map +1 -0
- package/lib/seam/connect/routes/thermostats.d.ts +38 -0
- package/lib/seam/connect/routes/thermostats.js +109 -0
- package/lib/seam/connect/routes/thermostats.js.map +1 -0
- package/lib/seam/connect/routes/webhooks.d.ts +24 -0
- package/lib/seam/connect/routes/webhooks.js +78 -0
- package/lib/seam/connect/routes/webhooks.js.map +1 -0
- package/lib/seam/connect/routes/workspaces.d.ts +17 -7
- package/lib/seam/connect/routes/workspaces.js +61 -11
- package/lib/seam/connect/routes/workspaces.js.map +1 -1
- package/lib/seam/connect/seam-http.d.ts +24 -0
- package/lib/seam/connect/seam-http.js +82 -0
- package/lib/seam/connect/seam-http.js.map +1 -0
- package/package.json +9 -3
- package/src/lib/params-serializer.ts +55 -0
- package/src/lib/seam/connect/auth.ts +101 -13
- package/src/lib/seam/connect/client.ts +30 -62
- package/src/lib/seam/connect/env.d.ts +11 -0
- package/src/lib/seam/connect/index.ts +2 -1
- package/src/lib/seam/connect/{client-options.ts → options.ts} +37 -15
- package/src/lib/seam/connect/parse-options.ts +55 -14
- package/src/lib/seam/connect/routes/access-codes-unmanaged.ts +170 -0
- package/src/lib/seam/connect/routes/access-codes.ts +226 -0
- package/src/lib/seam/connect/routes/acs-access-groups.ts +216 -0
- package/src/lib/seam/connect/routes/acs-credentials.ts +151 -0
- package/src/lib/seam/connect/routes/acs-systems.ts +118 -0
- package/src/lib/seam/connect/routes/acs-users.ts +221 -0
- package/src/lib/seam/connect/routes/acs.ts +101 -0
- package/src/lib/seam/connect/routes/action-attempts.ts +118 -0
- package/src/lib/seam/connect/routes/client-sessions.ts +187 -0
- package/src/lib/seam/connect/routes/connect-webviews.ts +170 -0
- package/src/lib/seam/connect/routes/connected-accounts.ts +139 -0
- package/src/lib/seam/connect/routes/devices-unmanaged.ts +134 -0
- package/src/lib/seam/connect/routes/devices.ts +166 -0
- package/src/lib/seam/connect/routes/events.ts +114 -0
- package/src/lib/seam/connect/routes/index.ts +21 -0
- package/src/lib/seam/connect/routes/locks.ts +148 -0
- package/src/lib/seam/connect/routes/noise-sensors-noise-thresholds.ts +170 -0
- package/src/lib/seam/connect/routes/noise-sensors.ts +86 -0
- package/src/lib/seam/connect/routes/thermostats-climate-setting-schedules.ts +192 -0
- package/src/lib/seam/connect/routes/thermostats.ts +208 -0
- package/src/lib/seam/connect/routes/webhooks.ts +151 -0
- package/src/lib/seam/connect/routes/workspaces.ts +118 -22
- package/src/lib/seam/connect/seam-http.ts +141 -0
- package/lib/seam/connect/axios.d.ts +0 -3
- package/lib/seam/connect/axios.js +0 -17
- package/lib/seam/connect/axios.js.map +0 -1
- package/lib/seam/connect/client-options.d.ts +0 -19
- package/lib/seam/connect/client-options.js +0 -29
- package/lib/seam/connect/client-options.js.map +0 -1
- package/lib/seam/connect/legacy/workspaces.d.ts +0 -8
- package/lib/seam/connect/legacy/workspaces.js +0 -10
- package/lib/seam/connect/legacy/workspaces.js.map +0 -1
- package/src/lib/seam/connect/axios.ts +0 -23
- package/src/lib/seam/connect/legacy/workspaces.ts +0 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamapi/http",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "JavaScript HTTP client for the Seam API written in TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"postversion": "git push --follow-tags",
|
|
63
63
|
"example": "tsx examples",
|
|
64
64
|
"example:inspect": "tsx --inspect examples",
|
|
65
|
+
"generate": "tsx generate-routes.ts",
|
|
65
66
|
"format": "eslint --ignore-path .gitignore --fix .",
|
|
66
67
|
"preformat": "prettier --write --ignore-path .gitignore .",
|
|
67
68
|
"report": "c8 report"
|
|
@@ -83,13 +84,17 @@
|
|
|
83
84
|
}
|
|
84
85
|
},
|
|
85
86
|
"dependencies": {
|
|
86
|
-
"axios": "^1.5.0"
|
|
87
|
+
"axios": "^1.5.0",
|
|
88
|
+
"axios-retry": "^3.8.0"
|
|
87
89
|
},
|
|
88
90
|
"devDependencies": {
|
|
89
|
-
"@seamapi/
|
|
91
|
+
"@seamapi/fake-seam-connect": "^1.21.0",
|
|
92
|
+
"@seamapi/types": "^1.24.0",
|
|
93
|
+
"@types/eslint": "^8.44.2",
|
|
90
94
|
"@types/node": "^18.11.18",
|
|
91
95
|
"ava": "^5.0.1",
|
|
92
96
|
"c8": "^8.0.0",
|
|
97
|
+
"change-case": "^5.0.2",
|
|
93
98
|
"concurrently": "^8.2.1",
|
|
94
99
|
"del-cli": "^5.0.0",
|
|
95
100
|
"eslint": "^8.9.0",
|
|
@@ -99,6 +104,7 @@
|
|
|
99
104
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
100
105
|
"eslint-plugin-unused-imports": "^3.0.0",
|
|
101
106
|
"landlubber": "^1.0.0",
|
|
107
|
+
"node-fetch": "^3.3.2",
|
|
102
108
|
"prettier": "^3.0.0",
|
|
103
109
|
"tsc-alias": "^1.8.2",
|
|
104
110
|
"tsup": "^7.2.0",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CustomParamsSerializer } from 'axios'
|
|
2
|
+
|
|
3
|
+
export const paramsSerializer: CustomParamsSerializer = (params) => {
|
|
4
|
+
const searchParams = new URLSearchParams()
|
|
5
|
+
|
|
6
|
+
for (const [name, value] of Object.entries(params)) {
|
|
7
|
+
if (value == null) continue
|
|
8
|
+
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
if (value.length === 0) searchParams.set(name, '')
|
|
11
|
+
if (value.length === 1 && value[0] === '') {
|
|
12
|
+
throw new UnserializableParamError(
|
|
13
|
+
name,
|
|
14
|
+
`is a single element array containing the empty string which is unsupported because it serializes to the empty array`,
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
for (const v of value) {
|
|
18
|
+
throwIfUnserializable(name, v)
|
|
19
|
+
searchParams.append(name, v)
|
|
20
|
+
}
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throwIfUnserializable(name, value)
|
|
25
|
+
searchParams.set(name, value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
searchParams.sort()
|
|
29
|
+
return searchParams.toString()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const throwIfUnserializable = (k: string, v: unknown): void => {
|
|
33
|
+
if (v == null) {
|
|
34
|
+
throw new UnserializableParamError(k, `is ${v} or contains ${v}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof v === 'function') {
|
|
38
|
+
throw new UnserializableParamError(
|
|
39
|
+
k,
|
|
40
|
+
'is a function or contains a function',
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof v === 'object') {
|
|
45
|
+
throw new UnserializableParamError(k, 'is an object or contains an object')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class UnserializableParamError extends Error {
|
|
50
|
+
constructor(name: string, message: string) {
|
|
51
|
+
super(`Could not serialize parameter: '${name}' ${message}`)
|
|
52
|
+
this.name = this.constructor.name
|
|
53
|
+
Error.captureStackTrace(this, this.constructor)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
-
InvalidSeamHttpOptionsError,
|
|
3
2
|
isSeamHttpOptionsWithApiKey,
|
|
4
3
|
isSeamHttpOptionsWithClientSessionToken,
|
|
5
|
-
|
|
4
|
+
SeamHttpInvalidOptionsError,
|
|
6
5
|
type SeamHttpOptionsWithApiKey,
|
|
7
6
|
type SeamHttpOptionsWithClientSessionToken,
|
|
8
|
-
} from './
|
|
7
|
+
} from './options.js'
|
|
8
|
+
import type { Options } from './parse-options.js'
|
|
9
9
|
|
|
10
10
|
type Headers = Record<string, string>
|
|
11
11
|
|
|
12
|
-
export const getAuthHeaders = (options:
|
|
12
|
+
export const getAuthHeaders = (options: Options): Headers => {
|
|
13
|
+
if ('publishableKey' in options) {
|
|
14
|
+
return getAuthHeadersForPublishableKey(options.publishableKey)
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
if (isSeamHttpOptionsWithApiKey(options)) {
|
|
14
18
|
return getAuthHeadersForApiKey(options)
|
|
15
19
|
}
|
|
@@ -18,8 +22,8 @@ export const getAuthHeaders = (options: SeamHttpOptions): Headers => {
|
|
|
18
22
|
return getAuthHeadersForClientSessionToken(options)
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
throw new
|
|
22
|
-
'Must specify an apiKey or
|
|
25
|
+
throw new SeamHttpInvalidOptionsError(
|
|
26
|
+
'Must specify an apiKey, clientSessionToken, or publishableKey',
|
|
23
27
|
)
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -27,19 +31,29 @@ const getAuthHeadersForApiKey = ({
|
|
|
27
31
|
apiKey,
|
|
28
32
|
}: SeamHttpOptionsWithApiKey): Headers => {
|
|
29
33
|
if (isClientSessionToken(apiKey)) {
|
|
30
|
-
throw new
|
|
34
|
+
throw new SeamHttpInvalidTokenError(
|
|
31
35
|
'A Client Session Token cannot be used as an apiKey',
|
|
32
36
|
)
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
if (isJwt(apiKey)) {
|
|
40
|
+
throw new SeamHttpInvalidTokenError('A JWT cannot be used as an apiKey')
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
if (isAccessToken(apiKey)) {
|
|
36
|
-
throw new
|
|
37
|
-
'An
|
|
44
|
+
throw new SeamHttpInvalidTokenError(
|
|
45
|
+
'An Access Token cannot be used as an apiKey',
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isPublishableKey(apiKey)) {
|
|
50
|
+
throw new SeamHttpInvalidTokenError(
|
|
51
|
+
'A Publishable Key cannot be used as an apiKey',
|
|
38
52
|
)
|
|
39
53
|
}
|
|
40
54
|
|
|
41
|
-
if (
|
|
42
|
-
throw new
|
|
55
|
+
if (!isSeamToken(apiKey)) {
|
|
56
|
+
throw new SeamHttpInvalidTokenError(
|
|
43
57
|
`Unknown or invalid apiKey format, expected token to start with ${tokenPrefix}`,
|
|
44
58
|
)
|
|
45
59
|
}
|
|
@@ -52,8 +66,26 @@ const getAuthHeadersForApiKey = ({
|
|
|
52
66
|
const getAuthHeadersForClientSessionToken = ({
|
|
53
67
|
clientSessionToken,
|
|
54
68
|
}: SeamHttpOptionsWithClientSessionToken): Headers => {
|
|
69
|
+
if (isJwt(clientSessionToken)) {
|
|
70
|
+
throw new SeamHttpInvalidTokenError(
|
|
71
|
+
'A JWT cannot be used as a clientSessionToken',
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isAccessToken(clientSessionToken)) {
|
|
76
|
+
throw new SeamHttpInvalidTokenError(
|
|
77
|
+
'An Access Token cannot be used as a clientSessionToken',
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (isPublishableKey(clientSessionToken)) {
|
|
82
|
+
throw new SeamHttpInvalidTokenError(
|
|
83
|
+
'A Publishable Key cannot be used as a clientSessionToken',
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
if (!isClientSessionToken(clientSessionToken)) {
|
|
56
|
-
throw new
|
|
88
|
+
throw new SeamHttpInvalidTokenError(
|
|
57
89
|
`Unknown or invalid clientSessionToken format, expected token to start with ${clientSessionTokenPrefix}`,
|
|
58
90
|
)
|
|
59
91
|
}
|
|
@@ -64,7 +96,37 @@ const getAuthHeadersForClientSessionToken = ({
|
|
|
64
96
|
}
|
|
65
97
|
}
|
|
66
98
|
|
|
67
|
-
|
|
99
|
+
const getAuthHeadersForPublishableKey = (publishableKey: string): Headers => {
|
|
100
|
+
if (isJwt(publishableKey)) {
|
|
101
|
+
throw new SeamHttpInvalidTokenError(
|
|
102
|
+
'A JWT cannot be used as a publishableKey',
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isAccessToken(publishableKey)) {
|
|
107
|
+
throw new SeamHttpInvalidTokenError(
|
|
108
|
+
'An Access Token cannot be used as a publishableKey',
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isClientSessionToken(publishableKey)) {
|
|
113
|
+
throw new SeamHttpInvalidTokenError(
|
|
114
|
+
'A Client Session Token Key cannot be used as a publishableKey',
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!isPublishableKey(publishableKey)) {
|
|
119
|
+
throw new SeamHttpInvalidTokenError(
|
|
120
|
+
`Unknown or invalid publishableKey format, expected token to start with ${publishableKeyTokenPrefix}`,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
'seam-publishable-key': publishableKey,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class SeamHttpInvalidTokenError extends Error {
|
|
68
130
|
constructor(message: string) {
|
|
69
131
|
super(`SeamHttp received an invalid token: ${message}`)
|
|
70
132
|
this.name = this.constructor.name
|
|
@@ -72,10 +134,29 @@ export class InvalidSeamTokenError extends Error {
|
|
|
72
134
|
}
|
|
73
135
|
}
|
|
74
136
|
|
|
137
|
+
export const warnOnInsecureuserIdentifierKey = (
|
|
138
|
+
userIdentifierKey: string,
|
|
139
|
+
): void => {
|
|
140
|
+
if (isEmail(userIdentifierKey)) {
|
|
141
|
+
// eslint-disable-next-line no-console
|
|
142
|
+
console.warn(
|
|
143
|
+
...[
|
|
144
|
+
'Using an email for the userIdentifierKey is insecure and may return an error in the future!',
|
|
145
|
+
'This is insecure because an email is common knowledge or easily guessed.',
|
|
146
|
+
'Use something with sufficient entropy known only to the owner of the client session.',
|
|
147
|
+
'For help choosing a user identifier key see',
|
|
148
|
+
'https://docs.seam.co/latest/seam-components/overview/get-started-with-client-side-components#3-select-a-user-identifier-key',
|
|
149
|
+
],
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
75
154
|
const tokenPrefix = 'seam_'
|
|
76
155
|
|
|
77
156
|
const clientSessionTokenPrefix = 'seam_cst'
|
|
78
157
|
|
|
158
|
+
const publishableKeyTokenPrefix = 'seam_pk'
|
|
159
|
+
|
|
79
160
|
const isClientSessionToken = (token: string): boolean =>
|
|
80
161
|
token.startsWith(clientSessionTokenPrefix)
|
|
81
162
|
|
|
@@ -84,3 +165,10 @@ const isAccessToken = (token: string): boolean => token.startsWith('seam_at')
|
|
|
84
165
|
const isJwt = (token: string): boolean => token.startsWith('ey')
|
|
85
166
|
|
|
86
167
|
const isSeamToken = (token: string): boolean => token.startsWith(tokenPrefix)
|
|
168
|
+
|
|
169
|
+
const isPublishableKey = (token: string): boolean =>
|
|
170
|
+
token.startsWith(publishableKeyTokenPrefix)
|
|
171
|
+
|
|
172
|
+
// SOURCE: https://stackoverflow.com/a/46181
|
|
173
|
+
const isEmail = (value: string): boolean =>
|
|
174
|
+
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
|
|
@@ -1,63 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
client
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const opts = { ...options, apiKey }
|
|
32
|
-
if (!isSeamHttpOptionsWithApiKey(opts)) {
|
|
33
|
-
throw new InvalidSeamHttpOptionsError('Missing apiKey')
|
|
34
|
-
}
|
|
35
|
-
return new SeamHttp(opts)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static fromClientSessionToken(
|
|
39
|
-
clientSessionToken: SeamHttpOptionsWithClientSessionToken['clientSessionToken'],
|
|
40
|
-
options: Omit<
|
|
41
|
-
SeamHttpOptionsWithClientSessionToken,
|
|
42
|
-
'clientSessionToken'
|
|
43
|
-
> = {},
|
|
44
|
-
): SeamHttp {
|
|
45
|
-
const opts = { ...options, clientSessionToken }
|
|
46
|
-
if (!isSeamHttpOptionsWithClientSessionToken(opts)) {
|
|
47
|
-
throw new InvalidSeamHttpOptionsError('Missing clientSessionToken')
|
|
48
|
-
}
|
|
49
|
-
return new SeamHttp(opts)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// TODO
|
|
53
|
-
// static fromPublishableKey and deprecate getClientSessionToken
|
|
54
|
-
|
|
55
|
-
// TODO: Should we keep makeRequest?
|
|
56
|
-
// Better to implement error handling and wrapping in an error handler.
|
|
57
|
-
// makeRequest
|
|
58
|
-
|
|
59
|
-
get workspaces(): WorkspacesHttp {
|
|
60
|
-
if (this.#legacy) return new LegacyWorkspacesHttp(this.client)
|
|
61
|
-
return new WorkspacesHttp(this.client)
|
|
62
|
-
}
|
|
1
|
+
import axios, { type Axios, type AxiosRequestConfig } from 'axios'
|
|
2
|
+
import axiosRetry, { type AxiosRetry, exponentialDelay } from 'axios-retry'
|
|
3
|
+
|
|
4
|
+
import { paramsSerializer } from 'lib/params-serializer.js'
|
|
5
|
+
|
|
6
|
+
export type Client = Axios
|
|
7
|
+
|
|
8
|
+
export interface ClientOptions {
|
|
9
|
+
axiosOptions?: AxiosRequestConfig
|
|
10
|
+
axiosRetryOptions?: AxiosRetryConfig
|
|
11
|
+
client?: Client
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type AxiosRetryConfig = Parameters<AxiosRetry>[1]
|
|
15
|
+
|
|
16
|
+
export const createClient = (options: ClientOptions): Axios => {
|
|
17
|
+
if (options.client != null) return options.client
|
|
18
|
+
|
|
19
|
+
const client = axios.create({
|
|
20
|
+
paramsSerializer,
|
|
21
|
+
...options.axiosOptions,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
axiosRetry(client, {
|
|
25
|
+
retries: 2,
|
|
26
|
+
retryDelay: exponentialDelay,
|
|
27
|
+
...options.axiosRetryOptions,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
return client
|
|
63
31
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './options.js'
|
|
2
|
+
export * from './seam-http.js'
|
|
@@ -1,13 +1,39 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Client, ClientOptions } from './client.js'
|
|
2
2
|
|
|
3
3
|
export type SeamHttpOptions =
|
|
4
|
+
| SeamHttpOptionsFromEnv
|
|
5
|
+
| SeamHttpOptionsWithClient
|
|
4
6
|
| SeamHttpOptionsWithApiKey
|
|
5
7
|
| SeamHttpOptionsWithClientSessionToken
|
|
6
8
|
|
|
7
|
-
interface SeamHttpCommonOptions {
|
|
9
|
+
interface SeamHttpCommonOptions extends ClientOptions {
|
|
8
10
|
endpoint?: string
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type SeamHttpFromPublishableKeyOptions = SeamHttpCommonOptions
|
|
14
|
+
|
|
15
|
+
export type SeamHttpOptionsFromEnv = SeamHttpCommonOptions
|
|
16
|
+
|
|
17
|
+
export interface SeamHttpOptionsWithClient {
|
|
18
|
+
client: Client
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const isSeamHttpOptionsWithClient = (
|
|
22
|
+
options: SeamHttpOptions,
|
|
23
|
+
): options is SeamHttpOptionsWithClient => {
|
|
24
|
+
if (!('client' in options)) return false
|
|
25
|
+
if (options.client == null) return false
|
|
26
|
+
|
|
27
|
+
const keys = Object.keys(options).filter((k) => k !== 'client')
|
|
28
|
+
if (keys.length > 0) {
|
|
29
|
+
throw new SeamHttpInvalidOptionsError(
|
|
30
|
+
`The client option cannot be used with any other option, but received: ${keys.join(
|
|
31
|
+
', ',
|
|
32
|
+
)}`,
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return true
|
|
11
37
|
}
|
|
12
38
|
|
|
13
39
|
export interface SeamHttpOptionsWithApiKey extends SeamHttpCommonOptions {
|
|
@@ -18,10 +44,11 @@ export const isSeamHttpOptionsWithApiKey = (
|
|
|
18
44
|
options: SeamHttpOptions,
|
|
19
45
|
): options is SeamHttpOptionsWithApiKey => {
|
|
20
46
|
if (!('apiKey' in options)) return false
|
|
47
|
+
if (options.apiKey == null) return false
|
|
21
48
|
|
|
22
49
|
if ('clientSessionToken' in options && options.clientSessionToken != null) {
|
|
23
|
-
throw new
|
|
24
|
-
'The clientSessionToken option cannot be used with the apiKey option
|
|
50
|
+
throw new SeamHttpInvalidOptionsError(
|
|
51
|
+
'The clientSessionToken option cannot be used with the apiKey option',
|
|
25
52
|
)
|
|
26
53
|
}
|
|
27
54
|
|
|
@@ -37,26 +64,21 @@ export const isSeamHttpOptionsWithClientSessionToken = (
|
|
|
37
64
|
options: SeamHttpOptions,
|
|
38
65
|
): options is SeamHttpOptionsWithClientSessionToken => {
|
|
39
66
|
if (!('clientSessionToken' in options)) return false
|
|
67
|
+
if (options.clientSessionToken == null) return false
|
|
40
68
|
|
|
41
69
|
if ('apiKey' in options && options.apiKey != null) {
|
|
42
|
-
throw new
|
|
43
|
-
'The clientSessionToken option cannot be used with the apiKey option
|
|
70
|
+
throw new SeamHttpInvalidOptionsError(
|
|
71
|
+
'The clientSessionToken option cannot be used with the apiKey option',
|
|
44
72
|
)
|
|
45
73
|
}
|
|
46
74
|
|
|
47
75
|
return true
|
|
48
76
|
}
|
|
49
77
|
|
|
50
|
-
export class
|
|
78
|
+
export class SeamHttpInvalidOptionsError extends Error {
|
|
51
79
|
constructor(message: string) {
|
|
52
80
|
super(`SeamHttp received invalid options: ${message}`)
|
|
53
81
|
this.name = this.constructor.name
|
|
54
82
|
Error.captureStackTrace(this, this.constructor)
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
|
-
|
|
58
|
-
// TODO: withSessionToken { sessionToken } or withMultiWorkspaceApiKey { apiKey }?
|
|
59
|
-
// export interface SeamHttpOptionsWithSessionToken extends SeamHttpCommonOptions {
|
|
60
|
-
// workspaceId: string
|
|
61
|
-
// apiKey: string
|
|
62
|
-
// }
|
|
@@ -1,28 +1,69 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getAuthHeaders } from './auth.js'
|
|
2
|
+
import type { ClientOptions } from './client.js'
|
|
3
|
+
import {
|
|
4
|
+
isSeamHttpOptionsWithClient,
|
|
5
|
+
isSeamHttpOptionsWithClientSessionToken,
|
|
6
|
+
type SeamHttpOptions,
|
|
7
|
+
} from './options.js'
|
|
8
|
+
|
|
9
|
+
const defaultEndpoint = 'https://connect.getseam.com'
|
|
10
|
+
|
|
11
|
+
export type Options = SeamHttpOptions & { publishableKey?: string }
|
|
12
|
+
|
|
2
13
|
export const parseOptions = (
|
|
3
|
-
apiKeyOrOptions: string |
|
|
4
|
-
):
|
|
14
|
+
apiKeyOrOptions: string | Options,
|
|
15
|
+
): ClientOptions => {
|
|
16
|
+
const options = getNormalizedOptions(apiKeyOrOptions)
|
|
17
|
+
|
|
18
|
+
if (isSeamHttpOptionsWithClient(options)) return options
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
axiosOptions: {
|
|
22
|
+
baseURL: options.endpoint ?? getEndpointFromEnv() ?? defaultEndpoint,
|
|
23
|
+
withCredentials: isSeamHttpOptionsWithClientSessionToken(options),
|
|
24
|
+
...options.axiosOptions,
|
|
25
|
+
headers: {
|
|
26
|
+
...getAuthHeaders(options),
|
|
27
|
+
...options.axiosOptions?.headers,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
axiosRetryOptions: {
|
|
31
|
+
...options.axiosRetryOptions,
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const getNormalizedOptions = (
|
|
37
|
+
apiKeyOrOptions: string | Options,
|
|
38
|
+
): SeamHttpOptions => {
|
|
5
39
|
const options =
|
|
6
40
|
typeof apiKeyOrOptions === 'string'
|
|
7
41
|
? { apiKey: apiKeyOrOptions }
|
|
8
42
|
: apiKeyOrOptions
|
|
9
43
|
|
|
10
|
-
|
|
11
|
-
options.endpoint ??
|
|
12
|
-
globalThis.process?.env?.['SEAM_ENDPOINT'] ??
|
|
13
|
-
globalThis.process?.env?.['SEAM_API_URL'] ??
|
|
14
|
-
'https://connect.getseam.com'
|
|
44
|
+
if (isSeamHttpOptionsWithClient(options)) return options
|
|
15
45
|
|
|
16
46
|
const apiKey =
|
|
17
|
-
'apiKey' in options
|
|
18
|
-
? options.apiKey
|
|
19
|
-
: globalThis.process?.env?.['SEAM_API_KEY']
|
|
47
|
+
'apiKey' in options ? options.apiKey : getApiKeyFromEnv(options)
|
|
20
48
|
|
|
21
49
|
return {
|
|
22
50
|
...options,
|
|
23
51
|
...(apiKey != null ? { apiKey } : {}),
|
|
24
|
-
endpoint,
|
|
25
|
-
axiosOptions: options.axiosOptions ?? {},
|
|
26
|
-
enableLegacyMethodBehaivor: false,
|
|
27
52
|
}
|
|
28
53
|
}
|
|
54
|
+
|
|
55
|
+
const getApiKeyFromEnv = (
|
|
56
|
+
options: SeamHttpOptions,
|
|
57
|
+
): string | null | undefined => {
|
|
58
|
+
if ('clientSessionToken' in options && options.clientSessionToken != null) {
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
return globalThis.process?.env?.SEAM_API_KEY
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const getEndpointFromEnv = (): string | null | undefined => {
|
|
65
|
+
return (
|
|
66
|
+
globalThis.process?.env?.SEAM_ENDPOINT ??
|
|
67
|
+
globalThis.process?.env?.SEAM_API_URL
|
|
68
|
+
)
|
|
69
|
+
}
|