Skip to content

Commit 7a3b99a

Browse files
andrii-balitskyiseambotrazor-x
authored
feat: Read SEAM_PERSONAL_ACCESS_TOKEN and SEAM_WORKSPACE_ID from environment (#320)
* Parse PAT and workspace id from env * Update SeamHttpMultiWorkspace options type * Test reading PAT and WS ID from env * ci: Format code * ci: Format code * Update readme * Add wokrspaceId override test * Update README.md Co-authored-by: Evan Sosenko <[email protected]> --------- Co-authored-by: Seam Bot <[email protected]> Co-authored-by: Evan Sosenko <[email protected]>
1 parent bfa1402 commit 7a3b99a

File tree

5 files changed

+148
-3
lines changed

5 files changed

+148
-3
lines changed

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,10 @@ A workspace ID must be provided when using this method
174174
and all requests will be scoped to that workspace.
175175

176176
```ts
177-
// Pass as an option to the constructor
177+
// Set the `SEAM_PERSONAL_ACCESS_TOKEN` and `SEAM_WORKSPACE_ID` environment variables
178+
const seam = new SeamHttp()
178179

180+
// Pass as an option to the constructor
179181
const seam = new SeamHttp({
180182
personalAccessToken: 'your-personal-access-token',
181183
workspaceId: 'your-workspace-id',
@@ -407,6 +409,9 @@ A Personal Access Token is scoped to a Seam Console user.
407409
Obtain one from the Seam Console.
408410

409411
```ts
412+
// Set the `SEAM_PERSONAL_ACCESS_TOKEN` environment variable
413+
const seam = new SeamHttpMultiWorkspace()
414+
410415
// Pass as an option to the constructor
411416
const seam = new SeamHttpMultiWorkspace({
412417
personalAccessToken: 'your-personal-access-token',

src/lib/seam/connect/options.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isSeamHttpRequestOption } from './parse-options.js'
33
import type { ResolveActionAttemptOptions } from './resolve-action-attempt.js'
44

55
export type SeamHttpMultiWorkspaceOptions =
6+
| SeamHttpMultiWorkspaceOptionsFromEnv
67
| SeamHttpMultiWorkspaceOptionsWithClient
78
| SeamHttpMultiWorkspaceOptionsWithConsoleSessionToken
89
| SeamHttpMultiWorkspaceOptionsWithPersonalAccessToken
@@ -28,6 +29,9 @@ export interface SeamHttpFromPublishableKeyOptions
2829

2930
export interface SeamHttpOptionsFromEnv extends SeamHttpCommonOptions {}
3031

32+
export interface SeamHttpMultiWorkspaceOptionsFromEnv
33+
extends SeamHttpCommonOptions {}
34+
3135
export interface SeamHttpMultiWorkspaceOptionsWithClient
3236
extends SeamHttpRequestOptions {
3337
client: Client

src/lib/seam/connect/parse-options.ts

+42
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
isSeamHttpMultiWorkspaceOptionsWithClient,
88
isSeamHttpOptionsWithClient,
99
isSeamHttpOptionsWithClientSessionToken,
10+
SeamHttpInvalidOptionsError,
1011
type SeamHttpMultiWorkspaceOptions,
1112
type SeamHttpOptions,
1213
type SeamHttpRequestOptions,
@@ -76,9 +77,31 @@ const getNormalizedOptions = (
7677
const apiKey =
7778
'apiKey' in options ? options.apiKey : getApiKeyFromEnv(options)
7879

80+
const personalAccessToken =
81+
'personalAccessToken' in options
82+
? options.personalAccessToken
83+
: getPersonalAccessTokenFromEnv(options)
84+
85+
const workspaceId =
86+
'workspaceId' in options ? options.workspaceId : getWorkspaceIdFromEnv()
87+
88+
if (
89+
apiKey != null &&
90+
personalAccessToken != null &&
91+
!('apiKey' in options) &&
92+
!('personalAccessToken' in options)
93+
) {
94+
throw new SeamHttpInvalidOptionsError(
95+
'Both SEAM_API_KEY and SEAM_PERSONAL_ACCESS_TOKEN environment variables are defined. Please use only one authentication method.',
96+
)
97+
}
98+
7999
return {
80100
...options,
81101
...(apiKey != null ? { apiKey } : {}),
102+
...(personalAccessToken != null && workspaceId != null
103+
? { personalAccessToken, workspaceId }
104+
: {}),
82105
...requestOptions,
83106
}
84107
}
@@ -98,6 +121,25 @@ const getApiKeyFromEnv = (
98121
return globalThis.process?.env?.SEAM_API_KEY
99122
}
100123

124+
const getPersonalAccessTokenFromEnv = (
125+
options: SeamHttpOptions,
126+
): string | null | undefined => {
127+
if ('apiKey' in options && options.apiKey != null) {
128+
return null
129+
}
130+
if ('clientSessionToken' in options && options.clientSessionToken != null) {
131+
return null
132+
}
133+
if ('consoleSessionToken' in options && options.consoleSessionToken != null) {
134+
return null
135+
}
136+
return globalThis.process?.env?.['SEAM_PERSONAL_ACCESS_TOKEN']
137+
}
138+
139+
const getWorkspaceIdFromEnv = (): string | null | undefined => {
140+
return globalThis.process?.env?.['SEAM_WORKSPACE_ID']
141+
}
142+
101143
const getEndpointFromEnv = (): string | null | undefined => {
102144
if (globalThis.process?.env?.SEAM_API_URL != null) {
103145
// eslint-disable-next-line no-console

src/lib/seam/connect/seam-http-multi-workspace.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class SeamHttpMultiWorkspace {
1717
client: Client
1818
readonly defaults: Required<SeamHttpRequestOptions>
1919

20-
constructor(options: SeamHttpMultiWorkspaceOptions) {
20+
constructor(options: SeamHttpMultiWorkspaceOptions = {}) {
2121
const opts = parseOptions(options)
2222
this.client = 'client' in opts ? opts.client : createClient(opts)
2323
this.defaults = limitToSeamHttpRequestOptions(opts)

test/seam/connect/env.test.ts

+95-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import test from 'ava'
44
import { getTestServer } from 'fixtures/seam/connect/api.js'
55
import jwt from 'jsonwebtoken'
66

7-
import { SeamHttp, SeamHttpInvalidOptionsError } from '@seamapi/http/connect'
7+
import {
8+
SeamHttp,
9+
SeamHttpInvalidOptionsError,
10+
SeamHttpMultiWorkspace,
11+
} from '@seamapi/http/connect'
812

913
/*
1014
* Tests in this file must run serially to ensure a clean environment for each test.
@@ -273,3 +277,93 @@ test.serial(
273277
t.is(device.device_id, seed.august_device_1)
274278
},
275279
)
280+
281+
test.serial(
282+
'SeamHttp: constructor uses SEAM_PERSONAL_ACCESS_TOKEN and SEAM_WORKSPACE_ID environment variables',
283+
async (t) => {
284+
const { seed, endpoint } = await getTestServer(t)
285+
env['SEAM_PERSONAL_ACCESS_TOKEN'] = seed.seam_at1_token
286+
env['SEAM_WORKSPACE_ID'] = seed.seed_workspace_1
287+
const seam = new SeamHttp({ endpoint })
288+
const device = await seam.devices.get({
289+
device_id: seed.august_device_1,
290+
})
291+
t.is(device.workspace_id, seed.seed_workspace_1)
292+
t.is(device.device_id, seed.august_device_1)
293+
},
294+
)
295+
296+
test.serial(
297+
'SeamHttp: throws error when both SEAM_API_KEY and SEAM_PERSONAL_ACCESS_TOKEN are defined',
298+
(t) => {
299+
env.SEAM_API_KEY = 'some-api-key'
300+
env['SEAM_PERSONAL_ACCESS_TOKEN'] = 'some-access-token'
301+
env['SEAM_WORKSPACE_ID'] = 'some-workspace-id'
302+
t.throws(() => new SeamHttp(), {
303+
instanceOf: SeamHttpInvalidOptionsError,
304+
message:
305+
/Both SEAM_API_KEY and SEAM_PERSONAL_ACCESS_TOKEN environment variables are defined/,
306+
})
307+
},
308+
)
309+
310+
test.serial(
311+
'SeamHttp: personalAccessToken option overrides environment variables',
312+
async (t) => {
313+
const { seed, endpoint } = await getTestServer(t)
314+
env['SEAM_PERSONAL_ACCESS_TOKEN'] = 'some-invalid-token'
315+
env['SEAM_WORKSPACE_ID'] = seed.seed_workspace_1
316+
const seam = new SeamHttp({
317+
personalAccessToken: seed.seam_at1_token,
318+
endpoint,
319+
})
320+
const device = await seam.devices.get({
321+
device_id: seed.august_device_1,
322+
})
323+
t.is(device.workspace_id, seed.seed_workspace_1)
324+
t.is(device.device_id, seed.august_device_1)
325+
},
326+
)
327+
328+
test.serial(
329+
'SeamHttp: workspaceId option overrides environment variables',
330+
async (t) => {
331+
const { seed, endpoint } = await getTestServer(t)
332+
env['SEAM_PERSONAL_ACCESS_TOKEN'] = seed.seam_at1_token
333+
env['SEAM_WORKSPACE_ID'] = 'some-invalid-workspace'
334+
const seam = new SeamHttp({
335+
workspaceId: seed.seed_workspace_1,
336+
endpoint,
337+
})
338+
const device = await seam.devices.get({
339+
device_id: seed.august_device_1,
340+
})
341+
t.is(device.workspace_id, seed.seed_workspace_1)
342+
t.is(device.device_id, seed.august_device_1)
343+
},
344+
)
345+
346+
test.serial(
347+
'SeamHttpMultiWorkspace: constructor uses SEAM_PERSONAL_ACCESS_TOKEN environment variable',
348+
async (t) => {
349+
const { seed, endpoint } = await getTestServer(t)
350+
env['SEAM_PERSONAL_ACCESS_TOKEN'] = seed.seam_at1_token
351+
const multiWorkspace = new SeamHttpMultiWorkspace({ endpoint })
352+
const workspaces = await multiWorkspace.workspaces.list()
353+
t.true(workspaces.length > 0)
354+
},
355+
)
356+
357+
test.serial(
358+
'SeamHttpMultiWorkspace: personalAccessToken option overrides environment variables',
359+
async (t) => {
360+
const { seed, endpoint } = await getTestServer(t)
361+
env['SEAM_PERSONAL_ACCESS_TOKEN'] = 'some-invalid-token'
362+
const multiWorkspace = new SeamHttpMultiWorkspace({
363+
personalAccessToken: seed.seam_at1_token,
364+
endpoint,
365+
})
366+
const workspaces = await multiWorkspace.workspaces.list()
367+
t.true(workspaces.length > 0)
368+
},
369+
)

0 commit comments

Comments
 (0)