diff --git a/generate-routes.ts b/generate-routes.ts index 4f9eb0b5..a0424f32 100644 --- a/generate-routes.ts +++ b/generate-routes.ts @@ -233,15 +233,24 @@ ${renderExports(route)} const renderImports = ({ namespace, subresources }: Route): string => ` -import type { RouteRequestParams, RouteResponse, RouteRequestBody } from '@seamapi/types/connect' -import { Axios } from 'axios' +import type { + RouteRequestBody, + RouteRequestParams, + RouteResponse, +} from '@seamapi/types/connect' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { + type Client, + type ClientOptions, + createClient, +} from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -249,6 +258,12 @@ import { type SeamHttpOptionsWithClientSessionToken, } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' + +${ + namespace === 'client_sessions' + ? '' + : "import { SeamHttpClientSessions } from './client-sessions.js'" +} ${subresources .map((subresource) => renderSubresourceImport(subresource, namespace)) .join('\n')} @@ -268,11 +283,15 @@ const renderClass = ( ): string => ` export class SeamHttp${pascalCase(namespace)} { - client: Axios + client: Client ${constructors - .replace(/.*this\.#legacy.*\n/, '') .replaceAll(': SeamHttp ', `: SeamHttp${pascalCase(namespace)} `) + .replaceAll('', ``) + .replaceAll( + 'SeamHttp.fromClientSessionToken', + `SeamHttp${pascalCase(namespace)}.fromClientSessionToken`, + ) .replaceAll('new SeamHttp(', `new SeamHttp${pascalCase(namespace)}(`)} ${subresources diff --git a/package-lock.json b/package-lock.json index 2123ffb2..6ac31f56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,8 @@ "axios-retry": "^3.8.0" }, "devDependencies": { - "@seamapi/fake-seam-connect": "^1.18.0", - "@seamapi/types": "^1.14.0", + "@seamapi/fake-seam-connect": "^1.21.0", + "@seamapi/types": "^1.24.0", "@types/eslint": "^8.44.2", "@types/node": "^18.11.18", "ava": "^5.0.1", @@ -762,9 +762,9 @@ } }, "node_modules/@seamapi/fake-seam-connect": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@seamapi/fake-seam-connect/-/fake-seam-connect-1.18.0.tgz", - "integrity": "sha512-fdhf39yLbRQaRjv15EalePK4HsKO4bUzNwyFJK2knxNw2yaMB2rf/lFkar9wmRaiuTBcKa6vEyNhYYs3OpD0mw==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@seamapi/fake-seam-connect/-/fake-seam-connect-1.21.0.tgz", + "integrity": "sha512-UVEztR4QdCJjEYDm9Wtix8zvX/qpnvY4qu6Q15/HwNFevl3qCPPDURHv/eWSEnM5EMzrOScVNYsEU0HGAF35PQ==", "dev": true, "bin": { "fake-seam-connect": "dist/server.js" @@ -779,9 +779,9 @@ } }, "node_modules/@seamapi/types": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.23.0.tgz", - "integrity": "sha512-f0525i690fNhb1R2+98vb4rhra7TjTpSRRjWSSo28QjA2z3UtPlWCiCxPWSifHA+ZvSvcPtJnv4gMlnHfwQeIg==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.24.0.tgz", + "integrity": "sha512-x420Tq60z3/rjdx2naY+KlwziT7HjjQkO9Qp1T/KVjOP0TpoxagYjA0BJ7ypYrZvqO2TZ71dnMrpAreVyqnEDQ==", "dev": true, "engines": { "node": ">=16.13.0", diff --git a/package.json b/package.json index fd41897a..dacc704e 100644 --- a/package.json +++ b/package.json @@ -88,8 +88,8 @@ "axios-retry": "^3.8.0" }, "devDependencies": { - "@seamapi/fake-seam-connect": "^1.18.0", - "@seamapi/types": "^1.14.0", + "@seamapi/fake-seam-connect": "^1.21.0", + "@seamapi/types": "^1.24.0", "@types/eslint": "^8.44.2", "@types/node": "^18.11.18", "ava": "^5.0.1", diff --git a/src/lib/seam/connect/auth.ts b/src/lib/seam/connect/auth.ts index 2fb452c1..a49459f2 100644 --- a/src/lib/seam/connect/auth.ts +++ b/src/lib/seam/connect/auth.ts @@ -2,14 +2,18 @@ import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClientSessionToken, SeamHttpInvalidOptionsError, - type SeamHttpOptions, type SeamHttpOptionsWithApiKey, type SeamHttpOptionsWithClientSessionToken, } from './options.js' +import type { Options } from './parse-options.js' type Headers = Record -export const getAuthHeaders = (options: SeamHttpOptions): Headers => { +export const getAuthHeaders = (options: Options): Headers => { + if ('publishableKey' in options) { + return getAuthHeadersForPublishableKey(options.publishableKey) + } + if (isSeamHttpOptionsWithApiKey(options)) { return getAuthHeadersForApiKey(options) } @@ -19,7 +23,7 @@ export const getAuthHeaders = (options: SeamHttpOptions): Headers => { } throw new SeamHttpInvalidOptionsError( - 'Must specify an apiKey or clientSessionToken', + 'Must specify an apiKey, clientSessionToken, or publishableKey', ) } @@ -42,6 +46,12 @@ const getAuthHeadersForApiKey = ({ ) } + if (isPublishableKey(apiKey)) { + throw new SeamHttpInvalidTokenError( + 'A Publishable Key cannot be used as an apiKey', + ) + } + if (!isSeamToken(apiKey)) { throw new SeamHttpInvalidTokenError( `Unknown or invalid apiKey format, expected token to start with ${tokenPrefix}`, @@ -68,6 +78,12 @@ const getAuthHeadersForClientSessionToken = ({ ) } + if (isPublishableKey(clientSessionToken)) { + throw new SeamHttpInvalidTokenError( + 'A Publishable Key cannot be used as a clientSessionToken', + ) + } + if (!isClientSessionToken(clientSessionToken)) { throw new SeamHttpInvalidTokenError( `Unknown or invalid clientSessionToken format, expected token to start with ${clientSessionTokenPrefix}`, @@ -80,6 +96,36 @@ const getAuthHeadersForClientSessionToken = ({ } } +const getAuthHeadersForPublishableKey = (publishableKey: string): Headers => { + if (isJwt(publishableKey)) { + throw new SeamHttpInvalidTokenError( + 'A JWT cannot be used as a publishableKey', + ) + } + + if (isAccessToken(publishableKey)) { + throw new SeamHttpInvalidTokenError( + 'An Access Token cannot be used as a publishableKey', + ) + } + + if (isClientSessionToken(publishableKey)) { + throw new SeamHttpInvalidTokenError( + 'A Client Session Token Key cannot be used as a publishableKey', + ) + } + + if (!isPublishableKey(publishableKey)) { + throw new SeamHttpInvalidTokenError( + `Unknown or invalid publishableKey format, expected token to start with ${publishableKeyTokenPrefix}`, + ) + } + + return { + 'seam-publishable-key': publishableKey, + } +} + export class SeamHttpInvalidTokenError extends Error { constructor(message: string) { super(`SeamHttp received an invalid token: ${message}`) @@ -88,10 +134,29 @@ export class SeamHttpInvalidTokenError extends Error { } } +export const warnOnInsecureuserIdentifierKey = ( + userIdentifierKey: string, +): void => { + if (isEmail(userIdentifierKey)) { + // eslint-disable-next-line no-console + console.warn( + ...[ + 'Using an email for the userIdentifierKey is insecure and may return an error in the future!', + 'This is insecure because an email is common knowledge or easily guessed.', + 'Use something with sufficient entropy known only to the owner of the client session.', + 'For help choosing a user identifier key see', + 'https://docs.seam.co/latest/seam-components/overview/get-started-with-client-side-components#3-select-a-user-identifier-key', + ], + ) + } +} + const tokenPrefix = 'seam_' const clientSessionTokenPrefix = 'seam_cst' +const publishableKeyTokenPrefix = 'seam_pk' + const isClientSessionToken = (token: string): boolean => token.startsWith(clientSessionTokenPrefix) @@ -100,3 +165,10 @@ const isAccessToken = (token: string): boolean => token.startsWith('seam_at') const isJwt = (token: string): boolean => token.startsWith('ey') const isSeamToken = (token: string): boolean => token.startsWith(tokenPrefix) + +const isPublishableKey = (token: string): boolean => + token.startsWith(publishableKeyTokenPrefix) + +// SOURCE: https://stackoverflow.com/a/46181 +const isEmail = (value: string): boolean => + /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) diff --git a/src/lib/seam/connect/client.ts b/src/lib/seam/connect/client.ts index c1eaccfb..5c875088 100644 --- a/src/lib/seam/connect/client.ts +++ b/src/lib/seam/connect/client.ts @@ -1,27 +1,24 @@ -import axios, { type Axios } from 'axios' -import axiosRetry, { exponentialDelay } from 'axios-retry' +import axios, { type Axios, type AxiosRequestConfig } from 'axios' +import axiosRetry, { type AxiosRetry, exponentialDelay } from 'axios-retry' import { paramsSerializer } from 'lib/params-serializer.js' -import { getAuthHeaders } from './auth.js' -import { - isSeamHttpOptionsWithClient, - isSeamHttpOptionsWithClientSessionToken, - type SeamHttpOptions, -} from './options.js' +export type Client = Axios -export const createClient = (options: Required): Axios => { - if (isSeamHttpOptionsWithClient(options)) return options.client +export interface ClientOptions { + axiosOptions?: AxiosRequestConfig + axiosRetryOptions?: AxiosRetryConfig + client?: Client +} + +type AxiosRetryConfig = Parameters[1] + +export const createClient = (options: ClientOptions): Axios => { + if (options.client != null) return options.client const client = axios.create({ - baseURL: options.endpoint, - withCredentials: isSeamHttpOptionsWithClientSessionToken(options), paramsSerializer, ...options.axiosOptions, - headers: { - ...getAuthHeaders(options), - ...options.axiosOptions.headers, - }, }) axiosRetry(client, { diff --git a/src/lib/seam/connect/options.ts b/src/lib/seam/connect/options.ts index 064577db..f44465ce 100644 --- a/src/lib/seam/connect/options.ts +++ b/src/lib/seam/connect/options.ts @@ -1,5 +1,4 @@ -import type { Axios, AxiosRequestConfig } from 'axios' -import type { AxiosRetry } from 'axios-retry' +import type { Client, ClientOptions } from './client.js' export type SeamHttpOptions = | SeamHttpOptionsFromEnv @@ -7,20 +6,16 @@ export type SeamHttpOptions = | SeamHttpOptionsWithApiKey | SeamHttpOptionsWithClientSessionToken -interface SeamHttpCommonOptions { +interface SeamHttpCommonOptions extends ClientOptions { endpoint?: string - axiosOptions?: AxiosRequestConfig - axiosRetryOptions?: AxiosRetryConfig - enableLegacyMethodBehaivor?: boolean } -type AxiosRetryConfig = Parameters[1] +export type SeamHttpFromPublishableKeyOptions = SeamHttpCommonOptions export type SeamHttpOptionsFromEnv = SeamHttpCommonOptions -export interface SeamHttpOptionsWithClient - extends Pick { - client: Axios +export interface SeamHttpOptionsWithClient { + client: Client } export const isSeamHttpOptionsWithClient = ( @@ -29,12 +24,10 @@ export const isSeamHttpOptionsWithClient = ( if (!('client' in options)) return false if (options.client == null) return false - const keys = Object.keys(options).filter( - (k) => !['client', 'enableLegacyMethodBehaivor'].includes(k), - ) + const keys = Object.keys(options).filter((k) => k !== 'client') if (keys.length > 0) { throw new SeamHttpInvalidOptionsError( - `The client option cannot be used with any other option except enableLegacyMethodBehaivor, but received: ${keys.join( + `The client option cannot be used with any other option, but received: ${keys.join( ', ', )}`, ) @@ -55,7 +48,7 @@ export const isSeamHttpOptionsWithApiKey = ( if ('clientSessionToken' in options && options.clientSessionToken != null) { throw new SeamHttpInvalidOptionsError( - 'The clientSessionToken option cannot be used with the apiKey option.', + 'The clientSessionToken option cannot be used with the apiKey option', ) } @@ -75,7 +68,7 @@ export const isSeamHttpOptionsWithClientSessionToken = ( if ('apiKey' in options && options.apiKey != null) { throw new SeamHttpInvalidOptionsError( - 'The clientSessionToken option cannot be used with the apiKey option.', + 'The clientSessionToken option cannot be used with the apiKey option', ) } diff --git a/src/lib/seam/connect/parse-options.ts b/src/lib/seam/connect/parse-options.ts index 56c779f5..3cc43d91 100644 --- a/src/lib/seam/connect/parse-options.ts +++ b/src/lib/seam/connect/parse-options.ts @@ -1,38 +1,69 @@ -import { isSeamHttpOptionsWithClient, type SeamHttpOptions } from './options.js' +import { getAuthHeaders } from './auth.js' +import type { ClientOptions } from './client.js' +import { + isSeamHttpOptionsWithClient, + isSeamHttpOptionsWithClientSessionToken, + type SeamHttpOptions, +} from './options.js' -const enableLegacyMethodBehaivorDefault = true +const defaultEndpoint = 'https://connect.getseam.com' + +export type Options = SeamHttpOptions & { publishableKey?: string } export const parseOptions = ( - apiKeyOrOptions: string | SeamHttpOptions, -): Required => { + apiKeyOrOptions: string | Options, +): ClientOptions => { + const options = getNormalizedOptions(apiKeyOrOptions) + + if (isSeamHttpOptionsWithClient(options)) return options + + return { + axiosOptions: { + baseURL: options.endpoint ?? getEndpointFromEnv() ?? defaultEndpoint, + withCredentials: isSeamHttpOptionsWithClientSessionToken(options), + ...options.axiosOptions, + headers: { + ...getAuthHeaders(options), + ...options.axiosOptions?.headers, + }, + }, + axiosRetryOptions: { + ...options.axiosRetryOptions, + }, + } +} + +const getNormalizedOptions = ( + apiKeyOrOptions: string | Options, +): SeamHttpOptions => { const options = typeof apiKeyOrOptions === 'string' ? { apiKey: apiKeyOrOptions } : apiKeyOrOptions - if (isSeamHttpOptionsWithClient(options)) - return { - ...options, - enableLegacyMethodBehaivor: - options.enableLegacyMethodBehaivor ?? enableLegacyMethodBehaivorDefault, - } - - const endpoint = - options.endpoint ?? - globalThis.process?.env?.SEAM_ENDPOINT ?? - globalThis.process?.env?.SEAM_API_URL ?? - 'https://connect.getseam.com' + if (isSeamHttpOptionsWithClient(options)) return options const apiKey = - 'apiKey' in options ? options.apiKey : globalThis.process?.env?.SEAM_API_KEY + 'apiKey' in options ? options.apiKey : getApiKeyFromEnv(options) return { ...options, ...(apiKey != null ? { apiKey } : {}), - endpoint, - axiosOptions: options.axiosOptions ?? {}, - axiosRetryOptions: options.axiosRetryOptions ?? {}, - enableLegacyMethodBehaivor: - options.enableLegacyMethodBehaivor ?? enableLegacyMethodBehaivorDefault, } } + +const getApiKeyFromEnv = ( + options: SeamHttpOptions, +): string | null | undefined => { + if ('clientSessionToken' in options && options.clientSessionToken != null) { + return null + } + return globalThis.process?.env?.SEAM_API_KEY +} + +const getEndpointFromEnv = (): string | null | undefined => { + return ( + globalThis.process?.env?.SEAM_ENDPOINT ?? + globalThis.process?.env?.SEAM_API_URL + ) +} diff --git a/src/lib/seam/connect/routes/access-codes-unmanaged.ts b/src/lib/seam/connect/routes/access-codes-unmanaged.ts index fba79f3c..665824d9 100644 --- a/src/lib/seam/connect/routes/access-codes-unmanaged.ts +++ b/src/lib/seam/connect/routes/access-codes-unmanaged.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpAccessCodesUnmanaged { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpAccessCodesUnmanaged { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpAccessCodesUnmanaged(opts) + return new SeamHttpAccessCodesUnmanaged(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpAccessCodesUnmanaged { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpAccessCodesUnmanaged(opts) + return new SeamHttpAccessCodesUnmanaged(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpAccessCodesUnmanaged { 'clientSessionToken' > = {}, ): SeamHttpAccessCodesUnmanaged { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpAccessCodesUnmanaged(opts) + return new SeamHttpAccessCodesUnmanaged(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpAccessCodesUnmanaged.fromClientSessionToken(token, options) } async convertToManaged( diff --git a/src/lib/seam/connect/routes/access-codes.ts b/src/lib/seam/connect/routes/access-codes.ts index 76d451bc..45d5ce21 100644 --- a/src/lib/seam/connect/routes/access-codes.ts +++ b/src/lib/seam/connect/routes/access-codes.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -21,35 +22,36 @@ import { import { parseOptions } from 'lib/seam/connect/parse-options.js' import { SeamHttpAccessCodesUnmanaged } from './access-codes-unmanaged.js' +import { SeamHttpClientSessions } from './client-sessions.js' export class SeamHttpAccessCodes { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpAccessCodes { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpAccessCodes(opts) + return new SeamHttpAccessCodes(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpAccessCodes { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpAccessCodes(opts) + return new SeamHttpAccessCodes(constructorOptions) } static fromClientSessionToken( @@ -59,11 +61,26 @@ export class SeamHttpAccessCodes { 'clientSessionToken' > = {}, ): SeamHttpAccessCodes { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpAccessCodes(opts) + return new SeamHttpAccessCodes(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpAccessCodes.fromClientSessionToken(token, options) } get unmanaged(): SeamHttpAccessCodesUnmanaged { diff --git a/src/lib/seam/connect/routes/acs-access-groups.ts b/src/lib/seam/connect/routes/acs-access-groups.ts index 98a4189f..86c9ce6d 100644 --- a/src/lib/seam/connect/routes/acs-access-groups.ts +++ b/src/lib/seam/connect/routes/acs-access-groups.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpAcsAccessGroups { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpAcsAccessGroups { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpAcsAccessGroups(opts) + return new SeamHttpAcsAccessGroups(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpAcsAccessGroups { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpAcsAccessGroups(opts) + return new SeamHttpAcsAccessGroups(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpAcsAccessGroups { 'clientSessionToken' > = {}, ): SeamHttpAcsAccessGroups { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpAcsAccessGroups(opts) + return new SeamHttpAcsAccessGroups(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpAcsAccessGroups.fromClientSessionToken(token, options) } async addUser(body: AcsAccessGroupsAddUserBody): Promise { diff --git a/src/lib/seam/connect/routes/acs-credentials.ts b/src/lib/seam/connect/routes/acs-credentials.ts index 9f4a614f..b982bc81 100644 --- a/src/lib/seam/connect/routes/acs-credentials.ts +++ b/src/lib/seam/connect/routes/acs-credentials.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpAcsCredentials { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpAcsCredentials { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpAcsCredentials(opts) + return new SeamHttpAcsCredentials(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpAcsCredentials { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpAcsCredentials(opts) + return new SeamHttpAcsCredentials(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpAcsCredentials { 'clientSessionToken' > = {}, ): SeamHttpAcsCredentials { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpAcsCredentials(opts) + return new SeamHttpAcsCredentials(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpAcsCredentials.fromClientSessionToken(token, options) } async create( diff --git a/src/lib/seam/connect/routes/acs-systems.ts b/src/lib/seam/connect/routes/acs-systems.ts index 3fa51f2c..acdf205a 100644 --- a/src/lib/seam/connect/routes/acs-systems.ts +++ b/src/lib/seam/connect/routes/acs-systems.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpAcsSystems { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpAcsSystems { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpAcsSystems(opts) + return new SeamHttpAcsSystems(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpAcsSystems { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpAcsSystems(opts) + return new SeamHttpAcsSystems(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpAcsSystems { 'clientSessionToken' > = {}, ): SeamHttpAcsSystems { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpAcsSystems(opts) + return new SeamHttpAcsSystems(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpAcsSystems.fromClientSessionToken(token, options) } async get( diff --git a/src/lib/seam/connect/routes/acs-users.ts b/src/lib/seam/connect/routes/acs-users.ts index 50c5eb44..91656540 100644 --- a/src/lib/seam/connect/routes/acs-users.ts +++ b/src/lib/seam/connect/routes/acs-users.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpAcsUsers { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpAcsUsers { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpAcsUsers(opts) + return new SeamHttpAcsUsers(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpAcsUsers { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpAcsUsers(opts) + return new SeamHttpAcsUsers(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpAcsUsers { 'clientSessionToken' > = {}, ): SeamHttpAcsUsers { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpAcsUsers(opts) + return new SeamHttpAcsUsers(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpAcsUsers.fromClientSessionToken(token, options) } async addToAccessGroup(body: AcsUsersAddToAccessGroupBody): Promise { diff --git a/src/lib/seam/connect/routes/acs.ts b/src/lib/seam/connect/routes/acs.ts index 881e090e..e979244b 100644 --- a/src/lib/seam/connect/routes/acs.ts +++ b/src/lib/seam/connect/routes/acs.ts @@ -3,13 +3,13 @@ * Do not edit this file or add other files to this directory. */ -import type { Axios } from 'axios' - -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -22,35 +22,36 @@ import { SeamHttpAcsAccessGroups } from './acs-access-groups.js' import { SeamHttpAcsCredentials } from './acs-credentials.js' import { SeamHttpAcsSystems } from './acs-systems.js' import { SeamHttpAcsUsers } from './acs-users.js' +import { SeamHttpClientSessions } from './client-sessions.js' export class SeamHttpAcs { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpAcs { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpAcs(opts) + return new SeamHttpAcs(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpAcs { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpAcs(opts) + return new SeamHttpAcs(constructorOptions) } static fromClientSessionToken( @@ -60,11 +61,26 @@ export class SeamHttpAcs { 'clientSessionToken' > = {}, ): SeamHttpAcs { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpAcs(opts) + return new SeamHttpAcs(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpAcs.fromClientSessionToken(token, options) } get accessGroups(): SeamHttpAcsAccessGroups { diff --git a/src/lib/seam/connect/routes/action-attempts.ts b/src/lib/seam/connect/routes/action-attempts.ts index 7c79c039..f418ce53 100644 --- a/src/lib/seam/connect/routes/action-attempts.ts +++ b/src/lib/seam/connect/routes/action-attempts.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpActionAttempts { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpActionAttempts { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpActionAttempts(opts) + return new SeamHttpActionAttempts(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpActionAttempts { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpActionAttempts(opts) + return new SeamHttpActionAttempts(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpActionAttempts { 'clientSessionToken' > = {}, ): SeamHttpActionAttempts { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpActionAttempts(opts) + return new SeamHttpActionAttempts(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpActionAttempts.fromClientSessionToken(token, options) } async get( diff --git a/src/lib/seam/connect/routes/client-sessions.ts b/src/lib/seam/connect/routes/client-sessions.ts index 66d87d71..7d938823 100644 --- a/src/lib/seam/connect/routes/client-sessions.ts +++ b/src/lib/seam/connect/routes/client-sessions.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -21,33 +22,33 @@ import { import { parseOptions } from 'lib/seam/connect/parse-options.js' export class SeamHttpClientSessions { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpClientSessions { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpClientSessions(opts) + return new SeamHttpClientSessions(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpClientSessions { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpClientSessions(opts) + return new SeamHttpClientSessions(constructorOptions) } static fromClientSessionToken( @@ -57,11 +58,26 @@ export class SeamHttpClientSessions { 'clientSessionToken' > = {}, ): SeamHttpClientSessions { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpClientSessions(opts) + return new SeamHttpClientSessions(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpClientSessions.fromClientSessionToken(token, options) } async create( @@ -94,6 +110,18 @@ export class SeamHttpClientSessions { return data.client_session } + async getOrCreate( + body: ClientSessionsGetOrCreateBody, + ): Promise { + const { data } = + await this.client.request({ + url: '/client_sessions/get_or_create', + method: 'post', + data: body, + }) + return data.client_session + } + async grantAccess( body: ClientSessionsGrantAccessBody, ): Promise { @@ -138,6 +166,13 @@ export type ClientSessionsGetResponse = SetNonNullable< Required> > +export type ClientSessionsGetOrCreateBody = + RouteRequestBody<'/client_sessions/get_or_create'> + +export type ClientSessionsGetOrCreateResponse = SetNonNullable< + Required> +> + export type ClientSessionsGrantAccessBody = RouteRequestBody<'/client_sessions/grant_access'> diff --git a/src/lib/seam/connect/routes/connect-webviews.ts b/src/lib/seam/connect/routes/connect-webviews.ts index 89f88475..e4802631 100644 --- a/src/lib/seam/connect/routes/connect-webviews.ts +++ b/src/lib/seam/connect/routes/connect-webviews.ts @@ -8,14 +8,15 @@ import type { RouteRequestParams, RouteResponse, } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -24,34 +25,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpConnectWebviews { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpConnectWebviews { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpConnectWebviews(opts) + return new SeamHttpConnectWebviews(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpConnectWebviews { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpConnectWebviews(opts) + return new SeamHttpConnectWebviews(constructorOptions) } static fromClientSessionToken( @@ -61,11 +64,26 @@ export class SeamHttpConnectWebviews { 'clientSessionToken' > = {}, ): SeamHttpConnectWebviews { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpConnectWebviews(opts) + return new SeamHttpConnectWebviews(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpConnectWebviews.fromClientSessionToken(token, options) } async create( diff --git a/src/lib/seam/connect/routes/connected-accounts.ts b/src/lib/seam/connect/routes/connected-accounts.ts index b6b80e93..9b4499a3 100644 --- a/src/lib/seam/connect/routes/connected-accounts.ts +++ b/src/lib/seam/connect/routes/connected-accounts.ts @@ -8,14 +8,15 @@ import type { RouteRequestParams, RouteResponse, } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -24,34 +25,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpConnectedAccounts { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpConnectedAccounts { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpConnectedAccounts(opts) + return new SeamHttpConnectedAccounts(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpConnectedAccounts { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpConnectedAccounts(opts) + return new SeamHttpConnectedAccounts(constructorOptions) } static fromClientSessionToken( @@ -61,11 +64,26 @@ export class SeamHttpConnectedAccounts { 'clientSessionToken' > = {}, ): SeamHttpConnectedAccounts { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpConnectedAccounts(opts) + return new SeamHttpConnectedAccounts(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpConnectedAccounts.fromClientSessionToken(token, options) } async delete(body: ConnectedAccountsDeleteBody): Promise { diff --git a/src/lib/seam/connect/routes/devices-unmanaged.ts b/src/lib/seam/connect/routes/devices-unmanaged.ts index 6f6a6f9c..bf6d9941 100644 --- a/src/lib/seam/connect/routes/devices-unmanaged.ts +++ b/src/lib/seam/connect/routes/devices-unmanaged.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpDevicesUnmanaged { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpDevicesUnmanaged { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpDevicesUnmanaged(opts) + return new SeamHttpDevicesUnmanaged(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpDevicesUnmanaged { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpDevicesUnmanaged(opts) + return new SeamHttpDevicesUnmanaged(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpDevicesUnmanaged { 'clientSessionToken' > = {}, ): SeamHttpDevicesUnmanaged { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpDevicesUnmanaged(opts) + return new SeamHttpDevicesUnmanaged(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpDevicesUnmanaged.fromClientSessionToken(token, options) } async get( diff --git a/src/lib/seam/connect/routes/devices.ts b/src/lib/seam/connect/routes/devices.ts index cd68da53..b468f2d1 100644 --- a/src/lib/seam/connect/routes/devices.ts +++ b/src/lib/seam/connect/routes/devices.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,36 +21,37 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' import { SeamHttpDevicesUnmanaged } from './devices-unmanaged.js' export class SeamHttpDevices { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpDevices { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpDevices(opts) + return new SeamHttpDevices(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpDevices { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpDevices(opts) + return new SeamHttpDevices(constructorOptions) } static fromClientSessionToken( @@ -59,11 +61,26 @@ export class SeamHttpDevices { 'clientSessionToken' > = {}, ): SeamHttpDevices { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpDevices(opts) + return new SeamHttpDevices(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpDevices.fromClientSessionToken(token, options) } get unmanaged(): SeamHttpDevicesUnmanaged { diff --git a/src/lib/seam/connect/routes/events.ts b/src/lib/seam/connect/routes/events.ts index d627d464..bfd24d38 100644 --- a/src/lib/seam/connect/routes/events.ts +++ b/src/lib/seam/connect/routes/events.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpEvents { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpEvents { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpEvents(opts) + return new SeamHttpEvents(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpEvents { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpEvents(opts) + return new SeamHttpEvents(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpEvents { 'clientSessionToken' > = {}, ): SeamHttpEvents { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpEvents(opts) + return new SeamHttpEvents(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpEvents.fromClientSessionToken(token, options) } async get(body: EventsGetBody): Promise { diff --git a/src/lib/seam/connect/routes/locks.ts b/src/lib/seam/connect/routes/locks.ts index f2107956..b59a9245 100644 --- a/src/lib/seam/connect/routes/locks.ts +++ b/src/lib/seam/connect/routes/locks.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpLocks { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpLocks { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpLocks(opts) + return new SeamHttpLocks(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpLocks { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpLocks(opts) + return new SeamHttpLocks(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,26 @@ export class SeamHttpLocks { 'clientSessionToken' > = {}, ): SeamHttpLocks { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpLocks(opts) + return new SeamHttpLocks(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpLocks.fromClientSessionToken(token, options) } async get(body: LocksGetBody): Promise { diff --git a/src/lib/seam/connect/routes/noise-sensors-noise-thresholds.ts b/src/lib/seam/connect/routes/noise-sensors-noise-thresholds.ts index d764182b..0e6905f7 100644 --- a/src/lib/seam/connect/routes/noise-sensors-noise-thresholds.ts +++ b/src/lib/seam/connect/routes/noise-sensors-noise-thresholds.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpNoiseSensorsNoiseThresholds { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpNoiseSensorsNoiseThresholds { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpNoiseSensorsNoiseThresholds(opts) + return new SeamHttpNoiseSensorsNoiseThresholds(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpNoiseSensorsNoiseThresholds { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpNoiseSensorsNoiseThresholds(opts) + return new SeamHttpNoiseSensorsNoiseThresholds(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,29 @@ export class SeamHttpNoiseSensorsNoiseThresholds { 'clientSessionToken' > = {}, ): SeamHttpNoiseSensorsNoiseThresholds { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpNoiseSensorsNoiseThresholds(opts) + return new SeamHttpNoiseSensorsNoiseThresholds(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpNoiseSensorsNoiseThresholds.fromClientSessionToken( + token, + options, + ) } async create(body: NoiseSensorsNoiseThresholdsCreateBody): Promise { diff --git a/src/lib/seam/connect/routes/noise-sensors.ts b/src/lib/seam/connect/routes/noise-sensors.ts index d457d539..e2e27f29 100644 --- a/src/lib/seam/connect/routes/noise-sensors.ts +++ b/src/lib/seam/connect/routes/noise-sensors.ts @@ -3,13 +3,13 @@ * Do not edit this file or add other files to this directory. */ -import type { Axios } from 'axios' - -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -18,36 +18,37 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' import { SeamHttpNoiseSensorsNoiseThresholds } from './noise-sensors-noise-thresholds.js' export class SeamHttpNoiseSensors { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpNoiseSensors { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpNoiseSensors(opts) + return new SeamHttpNoiseSensors(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpNoiseSensors { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpNoiseSensors(opts) + return new SeamHttpNoiseSensors(constructorOptions) } static fromClientSessionToken( @@ -57,11 +58,26 @@ export class SeamHttpNoiseSensors { 'clientSessionToken' > = {}, ): SeamHttpNoiseSensors { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpNoiseSensors(opts) + return new SeamHttpNoiseSensors(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpNoiseSensors.fromClientSessionToken(token, options) } get noiseThresholds(): SeamHttpNoiseSensorsNoiseThresholds { diff --git a/src/lib/seam/connect/routes/thermostats-climate-setting-schedules.ts b/src/lib/seam/connect/routes/thermostats-climate-setting-schedules.ts index 98316a92..3f36b825 100644 --- a/src/lib/seam/connect/routes/thermostats-climate-setting-schedules.ts +++ b/src/lib/seam/connect/routes/thermostats-climate-setting-schedules.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,34 +21,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpThermostatsClimateSettingSchedules { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpThermostatsClimateSettingSchedules { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpThermostatsClimateSettingSchedules(opts) + return new SeamHttpThermostatsClimateSettingSchedules(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpThermostatsClimateSettingSchedules { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpThermostatsClimateSettingSchedules(opts) + return new SeamHttpThermostatsClimateSettingSchedules(constructorOptions) } static fromClientSessionToken( @@ -57,11 +60,29 @@ export class SeamHttpThermostatsClimateSettingSchedules { 'clientSessionToken' > = {}, ): SeamHttpThermostatsClimateSettingSchedules { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpThermostatsClimateSettingSchedules(opts) + return new SeamHttpThermostatsClimateSettingSchedules(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpThermostatsClimateSettingSchedules.fromClientSessionToken( + token, + options, + ) } async create( diff --git a/src/lib/seam/connect/routes/thermostats.ts b/src/lib/seam/connect/routes/thermostats.ts index 5bd23137..05eaefcc 100644 --- a/src/lib/seam/connect/routes/thermostats.ts +++ b/src/lib/seam/connect/routes/thermostats.ts @@ -4,14 +4,15 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -20,36 +21,37 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' import { SeamHttpThermostatsClimateSettingSchedules } from './thermostats-climate-setting-schedules.js' export class SeamHttpThermostats { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpThermostats { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpThermostats(opts) + return new SeamHttpThermostats(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpThermostats { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpThermostats(opts) + return new SeamHttpThermostats(constructorOptions) } static fromClientSessionToken( @@ -59,11 +61,26 @@ export class SeamHttpThermostats { 'clientSessionToken' > = {}, ): SeamHttpThermostats { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpThermostats(opts) + return new SeamHttpThermostats(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpThermostats.fromClientSessionToken(token, options) } get climateSettingSchedules(): SeamHttpThermostatsClimateSettingSchedules { diff --git a/src/lib/seam/connect/routes/webhooks.ts b/src/lib/seam/connect/routes/webhooks.ts index 354a4a0f..9b27077d 100644 --- a/src/lib/seam/connect/routes/webhooks.ts +++ b/src/lib/seam/connect/routes/webhooks.ts @@ -8,14 +8,15 @@ import type { RouteRequestParams, RouteResponse, } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -24,34 +25,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpWebhooks { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpWebhooks { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpWebhooks(opts) + return new SeamHttpWebhooks(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpWebhooks { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpWebhooks(opts) + return new SeamHttpWebhooks(constructorOptions) } static fromClientSessionToken( @@ -61,11 +64,26 @@ export class SeamHttpWebhooks { 'clientSessionToken' > = {}, ): SeamHttpWebhooks { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpWebhooks(opts) + return new SeamHttpWebhooks(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpWebhooks.fromClientSessionToken(token, options) } async create( diff --git a/src/lib/seam/connect/routes/workspaces.ts b/src/lib/seam/connect/routes/workspaces.ts index 8947ed03..f69cd4da 100644 --- a/src/lib/seam/connect/routes/workspaces.ts +++ b/src/lib/seam/connect/routes/workspaces.ts @@ -8,14 +8,15 @@ import type { RouteRequestParams, RouteResponse, } from '@seamapi/types/connect' -import type { Axios } from 'axios' import type { SetNonNullable } from 'type-fest' -import { createClient } from 'lib/seam/connect/client.js' +import { warnOnInsecureuserIdentifierKey } from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -24,34 +25,36 @@ import { } from 'lib/seam/connect/options.js' import { parseOptions } from 'lib/seam/connect/parse-options.js' +import { SeamHttpClientSessions } from './client-sessions.js' + export class SeamHttpWorkspaces { - client: Axios + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttpWorkspaces { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttpWorkspaces(opts) + return new SeamHttpWorkspaces(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttpWorkspaces { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttpWorkspaces(opts) + return new SeamHttpWorkspaces(constructorOptions) } static fromClientSessionToken( @@ -61,11 +64,26 @@ export class SeamHttpWorkspaces { 'clientSessionToken' > = {}, ): SeamHttpWorkspaces { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttpWorkspaces(opts) + return new SeamHttpWorkspaces(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpWorkspaces.fromClientSessionToken(token, options) } async get( diff --git a/src/lib/seam/connect/seam-http.ts b/src/lib/seam/connect/seam-http.ts index 566e3ffd..326d44be 100644 --- a/src/lib/seam/connect/seam-http.ts +++ b/src/lib/seam/connect/seam-http.ts @@ -1,10 +1,10 @@ -import type { Axios } from 'axios' - -import { createClient } from './client.js' +import { warnOnInsecureuserIdentifierKey } from './auth.js' +import { type Client, createClient } from './client.js' import { isSeamHttpOptionsWithApiKey, isSeamHttpOptionsWithClient, isSeamHttpOptionsWithClientSessionToken, + type SeamHttpFromPublishableKeyOptions, SeamHttpInvalidOptionsError, type SeamHttpOptions, type SeamHttpOptionsWithApiKey, @@ -29,36 +29,33 @@ import { } from './routes/index.js' export class SeamHttp { - client: Axios - - // #legacy: boolean + client: Client constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { - const options = parseOptions(apiKeyOrOptions) - // this.#legacy = options.enableLegacyMethodBehaivor - this.client = createClient(options) + const clientOptions = parseOptions(apiKeyOrOptions) + this.client = createClient(clientOptions) } static fromClient( client: SeamHttpOptionsWithClient['client'], options: Omit = {}, ): SeamHttp { - const opts = { ...options, client } - if (!isSeamHttpOptionsWithClient(opts)) { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing client') } - return new SeamHttp(opts) + return new SeamHttp(constructorOptions) } static fromApiKey( apiKey: SeamHttpOptionsWithApiKey['apiKey'], options: Omit = {}, ): SeamHttp { - const opts = { ...options, apiKey } - if (!isSeamHttpOptionsWithApiKey(opts)) { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing apiKey') } - return new SeamHttp(opts) + return new SeamHttp(constructorOptions) } static fromClientSessionToken( @@ -68,11 +65,26 @@ export class SeamHttp { 'clientSessionToken' > = {}, ): SeamHttp { - const opts = { ...options, clientSessionToken } - if (!isSeamHttpOptionsWithClientSessionToken(opts)) { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') } - return new SeamHttp(opts) + return new SeamHttp(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttp.fromClientSessionToken(token, options) } get accessCodes(): SeamHttpAccessCodes { diff --git a/test/seam/connect/env.test.ts b/test/seam/connect/env.test.ts index 686e2a53..34b6f1f8 100644 --- a/test/seam/connect/env.test.ts +++ b/test/seam/connect/env.test.ts @@ -8,11 +8,13 @@ import { SeamHttp, SeamHttpInvalidOptionsError } from '@seamapi/http/connect' /* * Tests in this file must run serially to ensure a clean environment for each test. */ -test.afterEach(() => { +const cleanupEnv = (): void => { delete env.SEAM_API_KEY delete env.SEAM_ENDPOINT delete env.SEAM_API_URL -}) +} +test.afterEach(cleanupEnv) +test.beforeEach(cleanupEnv) test.serial( 'SeamHttp: constructor uses SEAM_API_KEY environment variable', @@ -32,7 +34,7 @@ test.serial( 'SeamHttp: apiKey option overrides environment variables', async (t) => { const { seed, endpoint } = await getTestServer(t) - env.SEAM_API_KEY = 'some-invalid-api-key' + env.SEAM_API_KEY = 'some-invalid-api-key-1' const seam = new SeamHttp({ apiKey: seed.seam_apikey1_token, endpoint }) const device = await seam.devices.get({ device_id: seed.august_device_1, @@ -45,7 +47,7 @@ test.serial( test.serial( 'SeamHttp: apiKey option as first argument overrides environment variables', (t) => { - env.SEAM_API_KEY = 'some-invalid-api-key' + env.SEAM_API_KEY = 'some-invalid-api-key-2' const seam = new SeamHttp('seam_apikey_token') t.truthy(seam) }, @@ -70,17 +72,6 @@ test.serial( }, ) -test.serial( - 'SeamHttp: constructor throws if SEAM_API_KEY environment variable is used with clientSessionToken', - (t) => { - env.SEAM_API_KEY = 'seam_apikey_token' - t.throws(() => new SeamHttp({ clientSessionToken: 'seam_cst1_token' }), { - instanceOf: SeamHttpInvalidOptionsError, - message: /apiKey/, - }) - }, -) - test.serial( 'SeamHttp: SEAM_ENDPOINT environment variable is used first', async (t) => { @@ -140,6 +131,76 @@ test.serial( }, ) +test.serial( + 'SeamHttp: constructor ignores SEAM_API_KEY environment variable if used with clientSessionToken', + async (t) => { + const { seed, endpoint } = await getTestServer(t) + env.SEAM_API_KEY = 'some-invalid-api-key-3' + const seam = new SeamHttp({ + clientSessionToken: seed.seam_cst1_token, + endpoint, + }) + const device = await seam.devices.get({ + device_id: seed.august_device_1, + }) + t.is(device.workspace_id, seed.seed_workspace_1) + t.is(device.device_id, seed.august_device_1) + }, +) + +test.serial( + 'SeamHttp: SEAM_API_KEY environment variable is ignored with fromClientSessionToken', + async (t) => { + const { seed, endpoint } = await getTestServer(t) + env.SEAM_API_KEY = 'some-invalid-api-key-4' + const seam = SeamHttp.fromClientSessionToken(seed.seam_cst1_token, { + endpoint, + }) + const device = await seam.devices.get({ + device_id: seed.august_device_1, + }) + t.is(device.workspace_id, seed.seed_workspace_1) + t.is(device.device_id, seed.august_device_1) + }, +) + +test.serial( + 'SeamHttp: SEAM_API_KEY environment variable is ignored with fromPublishableKey', + async (t) => { + const { seed, endpoint } = await getTestServer(t) + env.SEAM_API_KEY = 'some-invalid-api-key-5' + const seam = await SeamHttp.fromPublishableKey( + seed.seam_pk1_token, + seed.john_user_identifier_key, + { + endpoint, + }, + ) + const device = await seam.devices.get({ + device_id: seed.august_device_1, + }) + t.is(device.workspace_id, seed.seed_workspace_1) + t.is(device.device_id, seed.august_device_1) + }, +) + +test.serial( + 'SeamHttp: SEAM_ENDPOINT environment variable is used with fromPublishableKey', + async (t) => { + const { seed, endpoint } = await getTestServer(t) + env.SEAM_ENDPOINT = endpoint + const seam = await SeamHttp.fromPublishableKey( + seed.seam_pk1_token, + seed.john_user_identifier_key, + ) + const device = await seam.devices.get({ + device_id: seed.august_device_1, + }) + t.is(device.workspace_id, seed.seed_workspace_1) + t.is(device.device_id, seed.august_device_1) + }, +) + test.serial( 'SeamHttp: SEAM_ENDPOINT environment variable is used with fromClientSessionToken', async (t) => { diff --git a/test/seam/connect/publishable-key.test.ts b/test/seam/connect/publishable-key.test.ts new file mode 100644 index 00000000..adb458bd --- /dev/null +++ b/test/seam/connect/publishable-key.test.ts @@ -0,0 +1,71 @@ +import test from 'ava' +import { getTestServer } from 'fixtures/seam/connect/api.js' + +import { SeamHttp } from '@seamapi/http/connect' + +import { SeamHttpInvalidTokenError } from 'lib/seam/connect/auth.js' + +test('SeamHttp: fromPublishableKey returns instance authorized with clientSessionToken', async (t) => { + const { seed, endpoint } = await getTestServer(t) + const seam = await SeamHttp.fromPublishableKey( + seed.seam_pk1_token, + seed.john_user_identifier_key, + { + endpoint, + }, + ) + const device = await seam.devices.get({ + device_id: seed.august_device_1, + }) + t.is(device.workspace_id, seed.seed_workspace_1) + t.is(device.device_id, seed.august_device_1) +}) + +test('SeamHttp: checks publishableKey format', async (t) => { + await t.throwsAsync( + async () => + await SeamHttp.fromPublishableKey( + 'some-invalid-key-format', + 'some-user-identifier-key', + ), + { + instanceOf: SeamHttpInvalidTokenError, + message: /Unknown/, + }, + ) + await t.throwsAsync( + async () => + await SeamHttp.fromPublishableKey( + 'seam_apikey_token', + 'some-user-identifier-key', + ), + { + instanceOf: SeamHttpInvalidTokenError, + message: /Unknown/, + }, + ) + await t.throwsAsync( + async () => + await SeamHttp.fromPublishableKey('seam_cst', 'some-user-identifier-key'), + { + instanceOf: SeamHttpInvalidTokenError, + message: /Client Session Token/, + }, + ) + await t.throwsAsync( + async () => + await SeamHttp.fromPublishableKey('ey', 'some-user-identifier-key'), + { + instanceOf: SeamHttpInvalidTokenError, + message: /JWT/, + }, + ) + await t.throwsAsync( + async () => + await SeamHttp.fromPublishableKey('seam_at', 'some-user-identifier-key'), + { + instanceOf: SeamHttpInvalidTokenError, + message: /Access Token/, + }, + ) +})