Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom app config relies on base domains now #713

Merged
merged 2 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,11 @@
"default": false,
"markdownDescription": "Whether to use full path to integrated runme executable when running CLI commands. This is mostly useful for development purposes."
},
"runme.app.apiUrl": {
"runme.app.baseDomain": {
"type": "string",
"default": "",
"markdownDescription": "App API URL"
"scope": "window",
"markdownDescription": "Base domain to be use for Runme app"
},
"runme.app.enableShare": {
"type": "boolean",
Expand Down
6 changes: 4 additions & 2 deletions src/extension/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'
import fetch from 'cross-fetch'
import { setContext } from '@apollo/client/link/context'
import { Uri } from 'vscode'

import { getRunmeApiUrl } from '../../utils/configuration'
import { getRunmeAppUrl } from '../../utils/configuration'

export function InitializeClient({
uri,
Expand All @@ -19,7 +20,8 @@ export function InitializeClient({
},
}
})
const link = new HttpLink({ fetch, uri: uri || `${getRunmeApiUrl()}/graphql` })
const appApiUrl = Uri.joinPath(Uri.parse(getRunmeAppUrl(['api']), true), '/graphql').toString()
const link = new HttpLink({ fetch, uri: uri || appApiUrl })
const client = new ApolloClient({
cache: new InMemoryCache(),
credentials: 'include',
Expand Down
3 changes: 2 additions & 1 deletion src/extension/panels/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Observable, Subscription } from 'rxjs'
import { fetchStaticHtml, getAuthSession } from '../utils'
import { IAppToken, RunmeService } from '../services/runme'
import { SyncSchemaBus } from '../../types'
import { getRunmeAppUrl } from '../../utils/configuration'

export type DefaultUx = 'panels'
export interface InitPayload {
Expand All @@ -16,7 +17,7 @@ export interface InitPayload {
}

class PanelBase extends TelemetryViewProvider implements Disposable {
protected readonly appUrl: string = 'http://localhost:4001'
protected readonly appUrl: string = getRunmeAppUrl(['app'])
protected readonly defaultUx: DefaultUx = 'panels'

constructor(protected readonly context: ExtensionContext) {
Expand Down
11 changes: 8 additions & 3 deletions src/extension/services/runme.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fetch from 'cross-fetch'
import { Uri } from 'vscode'

import { getRunmeApiUrl } from '../../utils/configuration'
import { getRunmeAppUrl } from '../../utils/configuration'

export interface IUserToken {
token: string
Expand All @@ -12,13 +13,16 @@ export interface IAppToken {

export class RunmeService {
protected githubAccessToken: string
private readonly apiBase = Uri.parse(getRunmeAppUrl(['api']), true)

constructor({ githubAccessToken }: { githubAccessToken: string }) {
this.githubAccessToken = githubAccessToken
}
async getUserToken(): Promise<IUserToken> {
const userAuthEndpoint = Uri.joinPath(this.apiBase, '/auth/vscode').toString()
let response
try {
response = await fetch(`${getRunmeApiUrl()}/auth/vscode`, {
response = await fetch(userAuthEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -40,9 +44,10 @@ export class RunmeService {
return response.json()
}
async getAppToken(userToken: IUserToken): Promise<IAppToken> {
const appAuthEndpoint = Uri.joinPath(this.apiBase, '/auth/user/app').toString()
let response
try {
response = await fetch(`${getRunmeApiUrl()}/auth/user/app`, {
response = await fetch(appAuthEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${userToken.token}`,
Expand Down
39 changes: 35 additions & 4 deletions src/utils/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ const APP_SECTION_NAME = 'runme.app'
export const OpenViewInEditorAction = z.enum(['split', 'toggle'])
export const DEFAULT_TLS_DIR = path.join(os.tmpdir(), 'runme', uuidv4(), 'tls')
const DEFAULT_WORKSPACE_FILE_ORDER = ['.env.local', '.env']
const DEFAULT_RUNME_APP_API_URL = 'https://api.runme.dev/graphql'
const DEFAULT_RUNME_APP_API_URL = 'https://api.runme.dev'
const DEFAULT_RUNME_BASE_DOMAIN = 'runme.dev'
const APP_LOOPBACKS = ['127.0.0.1', 'localhost']
const APP_LOOPBACK_MAPPING = new Map<string, string>([
['api.', ':4000'],
['app.', ':4001'],
])

type NotebookTerminalValue = keyof typeof configurationSchema.notebookTerminal

Expand Down Expand Up @@ -54,6 +60,7 @@ const configurationSchema = {
},
app: {
apiUrl: z.string().default(DEFAULT_RUNME_APP_API_URL),
baseDomain: z.string().default(DEFAULT_RUNME_BASE_DOMAIN),
enableShare: z.boolean().default(false),
},
}
Expand Down Expand Up @@ -252,8 +259,32 @@ const getCLIUseIntegratedRunme = (): boolean => {
return getCLIConfigurationValue('useIntegratedRunme', false)
}

const getRunmeApiUrl = (): string => {
return getCloudConfigurationValue('apiUrl', DEFAULT_RUNME_APP_API_URL)
const getRunmeAppUrl = (subdomain: string[]): string => {
const base = getRunmeBaseDomain()
const isLoopback = APP_LOOPBACKS.map((host) => base.includes(host)).reduce((p, c) => p || c)
const scheme = isLoopback ? 'http' : 'https'

let sub = subdomain.join('.')
if (sub.length > 0) {
sub = `${sub}.`
}

let port = ''
if (isLoopback && sub.length > 0) {
port = APP_LOOPBACK_MAPPING.get(sub) ?? ''
sub = ''
}

const url = Uri.parse(`${scheme}://${sub}${base}${port}`, true)
return url.toString()
}

const getRunmeBaseDomain = (): string => {
const baseDomain = getCloudConfigurationValue('baseDomain', DEFAULT_RUNME_BASE_DOMAIN)
if (baseDomain.length === 0) {
return DEFAULT_RUNME_BASE_DOMAIN
}
return baseDomain
}

const isRunmeApiEnabled = (): boolean => {
Expand All @@ -279,6 +310,6 @@ export {
getEnvWorkspaceFileOrder,
getEnvLoadWorkspaceFiles,
getCLIUseIntegratedRunme,
getRunmeApiUrl,
getRunmeAppUrl,
isRunmeApiEnabled,
}
73 changes: 58 additions & 15 deletions tests/extension/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { suite, test, expect, vi, beforeEach, afterEach } from 'vitest'
import { Uri, workspace } from 'vscode'

import {
getRunmeAppUrl,
getPortNumber,
enableServerLogs,
getBinaryPath,
Expand All @@ -29,11 +30,13 @@ const SETTINGS_MOCK:
binaryPath: string | undefined
enableLogger: string | boolean | undefined
tlsDir: string | undefined
baseDomain: string | undefined
} = {
port: undefined,
binaryPath: undefined,
enableLogger: undefined,
tlsDir: undefined
tlsDir: undefined,
baseDomain: undefined,
}

beforeEach(() => {
Expand Down Expand Up @@ -82,7 +85,7 @@ function platformPathMocks(platform: path.PlatformPath) {

afterEach(() => {
Object.keys(SETTINGS_MOCK).forEach(key => {
SETTINGS_MOCK[key] = undefined
SETTINGS_MOCK[key] = undefined
})
})

Expand All @@ -97,34 +100,34 @@ suite('Configuration', () => {
expect(fontSize).toBeUndefined()
})

test('Should default to a valid port number', () => {
test('should default to a valid port number', () => {
const portNumber = getPortNumber()
expect(portNumber).toStrictEqual(7863)
})

test('Should use a valid specified port number', () => {
test('should use a valid specified port number', () => {
const portNumber = getPortNumber()
expect(portNumber).toStrictEqual(SERVER_PORT)
})

test('Should disable server logs with an invalid value', () => {
test('should disable server logs with an invalid value', () => {
SETTINGS_MOCK.enableLogger = undefined
const path = enableServerLogs()
expect(path).toBeFalsy()
})

test('Should disable server logs with an invalid string', () => {
test('should disable server logs with an invalid string', () => {
SETTINGS_MOCK.enableLogger = 'true'
const path = enableServerLogs()
expect(path).toBeFalsy()
})

test('Should get default TLS dir by default', () => {
test('should get default TLS dir by default', () => {
SETTINGS_MOCK.tlsDir = undefined
expect(getTLSDir()).toBe(DEFAULT_TLS_DIR)
})

test('Should get set TLS dir if set', () => {
test('should get set TLS dir if set', () => {
SETTINGS_MOCK.tlsDir = '/tmp/runme/tls'
expect(getTLSDir()).toBe('/tmp/runme/tls')
})
Expand Down Expand Up @@ -160,26 +163,26 @@ suite('Configuration', () => {
platformPathMocks(path.posix)
})

test('Should default to a valid binaryPath', () => {
test('should default to a valid binaryPath', () => {
const binary = getBinaryPath(Uri.file(FAKE_UNIX_EXT_PATH), 'linux')
expect(binary.fsPath).toStrictEqual('/Users/user/.vscode/extension/stateful.runme/bin/runme')
})

test('Should default to a valid relative binaryPath when specified', () => {
test('should default to a valid relative binaryPath when specified', () => {
SETTINGS_MOCK.binaryPath = 'newBin'
// @ts-expect-error
workspace.workspaceFolders = [{ uri: Uri.file('/Users/user/Projects/project') }]
const binary = getBinaryPath(Uri.file(FAKE_UNIX_EXT_PATH), 'linux')
expect(binary.fsPath).toStrictEqual('/Users/user/Projects/project/newBin')
})

test('Should default to a valid absolute binaryPath when specified', () => {
test('should default to a valid absolute binaryPath when specified', () => {
SETTINGS_MOCK.binaryPath = '/opt/homebrew/bin/runme'
const binary = getBinaryPath(Uri.file(FAKE_UNIX_EXT_PATH), 'linux')
expect(binary.fsPath).toStrictEqual('/opt/homebrew/bin/runme')
})

test('Should use runme for non-windows platforms', () => {
test('should use runme for non-windows platforms', () => {
SETTINGS_MOCK.binaryPath = '/opt/homebrew/bin/runme'
const binary = getBinaryPath(Uri.file(FAKE_UNIX_EXT_PATH), 'darwin')
expect(binary.fsPath).toStrictEqual('/opt/homebrew/bin/runme')
Expand All @@ -191,26 +194,66 @@ suite('Configuration', () => {
platformPathMocks(path.win32)
})

test('Should default to a valid binaryPath exe on windows', () => {
test('should default to a valid binaryPath exe on windows', () => {
const binary = getBinaryPath(Uri.file(FAKE_WIN_EXT_PATH), 'win')
expect(binary.fsPath).toStrictEqual(
'c:\\Users\\.vscode\\extensions\\stateful.runme\\bin\\runme.exe'
)
})

test('Should use runme.exe for windows platforms with absolute path', () => {
test('should use runme.exe for windows platforms with absolute path', () => {
SETTINGS_MOCK.binaryPath = 'C:\\custom\\path\\to\\bin\\runme.exe'

const binary = getBinaryPath(Uri.file(FAKE_WIN_EXT_PATH), 'win32')
expect(binary.fsPath).toStrictEqual('c:\\custom\\path\\to\\bin\\runme.exe')
})

test('Should use runme.exe for windows platforms with relative path', () => {
test('should use runme.exe for windows platforms with relative path', () => {
SETTINGS_MOCK.binaryPath = 'newBin.exe'
// @ts-expect-error
workspace.workspaceFolders = [{ uri: Uri.file('c:\\Users\\Projects\\project') }]
const binary = getBinaryPath(Uri.file(FAKE_WIN_EXT_PATH), 'win32')
expect(binary.fsPath).toStrictEqual('c:\\Users\\Projects\\project\\newBin.exe')
})
})

suite('app domain resolution', () => {
test('should return URL for api with subdomain', () => {
const url = getRunmeAppUrl(['api'])
expect(url).toStrictEqual('https://api.runme.dev/')
})

test('should return URL for api with deep subdomain', () => {
const url = getRunmeAppUrl(['l4', 'l3', 'api'])
expect(url).toStrictEqual('https://l4.l3.api.runme.dev/')
})

test('should return URL without subdomain', () => {
const url = getRunmeAppUrl([])
expect(url).toStrictEqual('https://runme.dev/')
})

test('should return URL without subdomain', () => {
const url = getRunmeAppUrl([])
expect(url).toStrictEqual('https://runme.dev/')
})

test('should allow api URL with http for 127.0.0.1', async () => {
SETTINGS_MOCK.baseDomain = '127.0.0.1'
const url = getRunmeAppUrl(['api'])
expect(url).toStrictEqual('http://127.0.0.1:4000/')
})

test('should allow app URL with http for localhost', async () => {
SETTINGS_MOCK.baseDomain = 'localhost'
const url = getRunmeAppUrl(['app'])
expect(url).toStrictEqual('http://localhost:4001/')
})

test('should allow app URL with http for localhost without subdomain', async () => {
SETTINGS_MOCK.baseDomain = 'localhost'
const url = getRunmeAppUrl([])
expect(url).toStrictEqual('http://localhost/')
})
})
})
Loading