diff --git a/packages/cli-kit/src/private/node/clients/identity/identity-client.ts b/packages/cli-kit/src/private/node/clients/identity/identity-client.ts new file mode 100644 index 00000000000..f1252eb8bfe --- /dev/null +++ b/packages/cli-kit/src/private/node/clients/identity/identity-client.ts @@ -0,0 +1,23 @@ +import {ApplicationToken, IdentityToken} from '../../session/schema.js' +import {ExchangeScopes} from '../../session/exchange.js' +import {API} from '../../api.js' + +export abstract class IdentityClient { + abstract requestAccessToken(scopes: string[]): Promise + + abstract exchangeAccessForApplicationTokens( + identityToken: IdentityToken, + scopes: ExchangeScopes, + store?: string, + ): Promise<{[x: string]: ApplicationToken}> + + abstract refreshAccessToken(currentToken: IdentityToken): Promise + + clientId(): string { + return '' + } + + applicationId(_api: API): string { + return '' + } +} diff --git a/packages/cli-kit/src/private/node/clients/identity/identity-mock-client.ts b/packages/cli-kit/src/private/node/clients/identity/identity-mock-client.ts new file mode 100644 index 00000000000..29a8c40f829 --- /dev/null +++ b/packages/cli-kit/src/private/node/clients/identity/identity-mock-client.ts @@ -0,0 +1,21 @@ +import {IdentityClient} from './identity-client.js' +import {ApplicationToken, IdentityToken} from '../../session/schema.js' +import {ExchangeScopes} from '../../session/exchange.js' + +export class IdentityMockClient extends IdentityClient { + async requestAccessToken(_scopes: string[]): Promise { + return {} as IdentityToken + } + + async exchangeAccessForApplicationTokens( + _identityToken: IdentityToken, + _scopes: ExchangeScopes, + _store?: string, + ): Promise<{[x: string]: ApplicationToken}> { + return {} + } + + async refreshAccessToken(_currentToken: IdentityToken): Promise { + return {} as IdentityToken + } +} diff --git a/packages/cli-kit/src/private/node/clients/identity/identity-service-client.ts b/packages/cli-kit/src/private/node/clients/identity/identity-service-client.ts new file mode 100644 index 00000000000..309ea41a8c6 --- /dev/null +++ b/packages/cli-kit/src/private/node/clients/identity/identity-service-client.ts @@ -0,0 +1,21 @@ +import {IdentityClient} from './identity-client.js' +import {ApplicationToken, IdentityToken} from '../../session/schema.js' +import {ExchangeScopes} from '../../session/exchange.js' + +export class IdentityServiceClient extends IdentityClient { + async requestAccessToken(_scopes: string[]): Promise { + return {} as IdentityToken + } + + async exchangeAccessForApplicationTokens( + _identityToken: IdentityToken, + _scopes: ExchangeScopes, + _store?: string, + ): Promise<{[x: string]: ApplicationToken}> { + return {} + } + + async refreshAccessToken(_currentToken: IdentityToken): Promise { + return {} as IdentityToken + } +} diff --git a/packages/cli-kit/src/private/node/clients/identity/instance.test.ts b/packages/cli-kit/src/private/node/clients/identity/instance.test.ts new file mode 100644 index 00000000000..35aec51f7ad --- /dev/null +++ b/packages/cli-kit/src/private/node/clients/identity/instance.test.ts @@ -0,0 +1,77 @@ +import {Environment} from '../../context/service.js' +import {describe, expect, test, vi, beforeEach} from 'vitest' + +const mockServiceEnvironment = vi.fn() +const mockIsRunning2024 = vi.fn() + +vi.mock('../../context/service.js', async () => { + const actual = await vi.importActual('../../context/service.js') + return { + ...actual, + serviceEnvironment: (...args: unknown[]) => mockServiceEnvironment(...args), + } +}) + +vi.mock('../../../../public/node/vendor/dev_server/dev-server-2024.js', async () => { + const actual = await vi.importActual('../../../../public/node/vendor/dev_server/dev-server-2024.js') + return { + ...actual, + isRunning2024: (...args: unknown[]) => mockIsRunning2024(...args), + } +}) + +describe('getIdentityClient', () => { + beforeEach(async () => { + mockServiceEnvironment.mockReset() + mockIsRunning2024.mockReset() + vi.resetModules() + }) + + test('returns IdentityServiceClient when environment is Production', async () => { + mockServiceEnvironment.mockReturnValue(Environment.Production) + mockIsRunning2024.mockReturnValue(false) + + const {getIdentityClient} = await import('./instance.js') + + const instance = getIdentityClient() + + expect(instance.constructor.name).toBe('IdentityServiceClient') + }) + + test('returns IdentityServiceClient when environment is Local and identity service is running', async () => { + mockServiceEnvironment.mockReturnValue(Environment.Local) + mockIsRunning2024.mockReturnValue(true) + + const {getIdentityClient} = await import('./instance.js') + + const instance = getIdentityClient() + + expect(instance.constructor.name).toBe('IdentityServiceClient') + expect(mockIsRunning2024).toHaveBeenCalledWith('identity') + }) + + test('returns IdentityMockClient when environment is Local and identity service is not running', async () => { + mockServiceEnvironment.mockReturnValue(Environment.Local) + mockIsRunning2024.mockReturnValue(false) + + const {getIdentityClient} = await import('./instance.js') + + const instance = getIdentityClient() + + expect(instance.constructor.name).toBe('IdentityMockClient') + expect(mockIsRunning2024).toHaveBeenCalledWith('identity') + }) + + test('returns the same instance on subsequent calls (singleton pattern)', async () => { + mockServiceEnvironment.mockReturnValue(Environment.Production) + mockIsRunning2024.mockReturnValue(false) + + const {getIdentityClient} = await import('./instance.js') + + const firstInstance = getIdentityClient() + const secondInstance = getIdentityClient() + + expect(firstInstance).toBe(secondInstance) + expect(mockServiceEnvironment).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/cli-kit/src/private/node/clients/identity/instance.ts b/packages/cli-kit/src/private/node/clients/identity/instance.ts new file mode 100644 index 00000000000..7aa05c2cf46 --- /dev/null +++ b/packages/cli-kit/src/private/node/clients/identity/instance.ts @@ -0,0 +1,18 @@ +import {IdentityClient} from './identity-client.js' +import {IdentityMockClient} from './identity-mock-client.js' +import {IdentityServiceClient} from './identity-service-client.js' +import {Environment, serviceEnvironment} from '../../context/service.js' +import {isRunning2024} from '../../../../public/node/vendor/dev_server/dev-server-2024.js' + +let _identityClient: IdentityClient | undefined + +export function getIdentityClient() { + if (!_identityClient) { + const isLocal = serviceEnvironment() === Environment.Local + const identityServiceRunning = isRunning2024('identity') + const client = isLocal && !identityServiceRunning ? new IdentityMockClient() : new IdentityServiceClient() + _identityClient = client + } + + return _identityClient +} diff --git a/packages/cli-kit/src/public/node/vendor/dev_server/dev-server-2024.ts b/packages/cli-kit/src/public/node/vendor/dev_server/dev-server-2024.ts index 2882024d84b..79ce264c54f 100644 --- a/packages/cli-kit/src/public/node/vendor/dev_server/dev-server-2024.ts +++ b/packages/cli-kit/src/public/node/vendor/dev_server/dev-server-2024.ts @@ -46,6 +46,15 @@ function assertRunning2024(projectName: string): void { }) } +export function isRunning2024(projectName: string) { + try { + assertRunning2024(projectName) + return true + } catch (_) { + return false + } +} + function getBackendIp(projectName: string): string { try { const backendIp = resolveBackendHost(projectName)