Skip to content
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oh-my-github",
"version": "0.0.9",
"version": "0.0.10",
"private": true,
"description": "A faster GitHub workspace for notifications, pull requests, reviews, and actions.",
"packageManager": "pnpm@11.7.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oh-my-github/api",
"version": "0.0.9",
"version": "0.0.10",
"private": true,
"type": "module",
"main": "./src/index.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function createGitHubApi(options: GitHubApiOptions): GitHubApi {
const accounts = new AccountsApi(octokit)
const actions = new ActionsApi(octokit)
const activity = new ActivityApi(octokit)
const auth = new AuthApi({ octokit, proxyUrl: options.proxyUrl })
const auth = new AuthApi({ octokit, proxyUrl: options.proxyUrl, ca: options.ca })
const deployments = new DeploymentsApi(octokit)
const inbox = new InboxApi(octokit)
const issues = new IssuesApi(octokit)
Expand Down
21 changes: 13 additions & 8 deletions packages/api/src/modules/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { GitHubOctokit } from '../transport'
import { createOctokit, createProxyFetch } from '../transport'
import type { GitHubOctokit, GitHubTransportOptions } from '../transport'
import { createGitHubFetch, createOctokit } from '../transport'
import type {
GitHubAuthViewer,
GitHubDeviceAuthorization,
Expand Down Expand Up @@ -51,6 +51,7 @@ export const defaultGitHubOAuthScopes = [
interface AuthApiOptions {
octokit?: GitHubOctokit
proxyUrl?: string
ca?: string | string[]
}

export class AuthApi {
Expand All @@ -65,7 +66,7 @@ export class AuthApi {
client_id: options.clientId,
scope: options.scopes.join(' ')
},
this.options.proxyUrl
this.transportOptions
)

return {
Expand All @@ -88,7 +89,7 @@ export class AuthApi {
device_code: options.deviceCode,
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
},
this.options.proxyUrl
this.transportOptions
)

if (response.access_token) {
Expand All @@ -115,9 +116,13 @@ export class AuthApi {
}
}

private get transportOptions(): GitHubTransportOptions {
return { proxyUrl: this.options.proxyUrl, ca: this.options.ca }
}

async getViewer(token?: string): Promise<GitHubAuthViewer> {
const octokit = token
? createOctokit({ token, proxyUrl: this.options.proxyUrl })
? createOctokit({ token, ...this.transportOptions })
: this.options.octokit

if (!octokit) {
Expand All @@ -138,10 +143,10 @@ export class AuthApi {
async function postGitHubOAuth<T>(
url: string,
body: Record<string, string>,
proxyUrl: string | undefined
transport: GitHubTransportOptions
): Promise<T> {
const fetchWithProxy = proxyUrl ? createProxyFetch(proxyUrl) : fetch
const response = await fetchWithProxy(url, {
const fetchWithTransport = createGitHubFetch(transport) ?? fetch
const response = await fetchWithTransport(url, {
method: 'POST',
headers: {
Accept: 'application/json',
Expand Down
39 changes: 31 additions & 8 deletions packages/api/src/transport.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,50 @@
import { Octokit, RequestError } from 'octokit'
import { ProxyAgent, fetch as undiciFetch } from 'undici'
import { Agent, type Dispatcher, ProxyAgent, fetch as undiciFetch } from 'undici'
import type { GitHubApiOptions } from './types'

export type GitHubOctokit = Octokit

export { RequestError }

export interface GitHubTransportOptions {
proxyUrl?: string
ca?: string | string[]
}

export function createOctokit(options: GitHubApiOptions): GitHubOctokit {
const fetch = createGitHubFetch(options)

return new Octokit({
auth: options.token,
baseUrl: options.baseUrl,
request: options.proxyUrl
? {
fetch: createProxyFetch(options.proxyUrl)
}
: undefined,
request: fetch ? { fetch } : undefined,
userAgent: options.userAgent ?? 'oh-my-github'
})
}

export function createProxyFetch(proxyUrl: string): typeof fetch {
const dispatcher = new ProxyAgent(proxyUrl)
/**
* A custom fetch bound to a proxy and/or a caller-supplied CA, or `undefined`
* when neither is configured so Octokit / callers fall back to the global
* fetch and Node's default TLS store.
*/
export function createGitHubFetch(options: GitHubTransportOptions): typeof fetch | undefined {
const dispatcher = createGitHubDispatcher(options)

return dispatcher ? createDispatcherFetch(dispatcher) : undefined
}

function createGitHubDispatcher(options: GitHubTransportOptions): Dispatcher | undefined {
const connect = options.ca ? { ca: options.ca } : undefined

if (options.proxyUrl) {
// Object form lets the origin-side TLS honour the extra CA through the tunnel.
return new ProxyAgent(connect ? { uri: options.proxyUrl, connect } : options.proxyUrl)
}

return connect ? new Agent({ connect }) : undefined
}

function createDispatcherFetch(dispatcher: Dispatcher): typeof fetch {
return ((input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) =>
undiciFetch(input as Parameters<typeof undiciFetch>[0], {
...(init as Parameters<typeof undiciFetch>[1]),
Expand Down
7 changes: 7 additions & 0 deletions packages/api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,13 @@ export interface GitHubApiOptions {
baseUrl?: string
proxyUrl?: string
userAgent?: string
/**
* Extra CA certificates (PEM) to trust for GitHub TLS connections, on top of
* Node's default store. Used to opt into a locally-installed root CA (e.g. a
* reverse-proxy tool that MITMs github.com); the main process supplies the
* system trust store here only when the user has explicitly enabled it.
*/
ca?: string | string[]
}

export interface StartDeviceAuthorizationOptions {
Expand Down
3 changes: 1 addition & 2 deletions packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oh-my-github/client",
"version": "0.0.9",
"version": "0.0.10",
"private": true,
"description": "Electron desktop client for Oh My GitHub.",
"author": "Oh My GitHub",
Expand Down Expand Up @@ -42,7 +42,6 @@
"lucide-vue-next": "^0.562.0",
"markstream-vue": "1.0.5-beta.0",
"mermaid": "^11.16.0",
"misans": "^4.1.0",
"monaco-editor": "^0.55.1",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/main/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createGitHubApi } from '@oh-my-github/api'
import { ipcMain } from 'electron'
import { getAuthenticatedAccessToken, getAuthenticatedAuthMetadata } from './auth'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

export function registerAccountsIpc(): void {
ipcMain.handle('accounts:get-profile', (_event, login: string) => getAccountProfile(login))
Expand Down Expand Up @@ -219,7 +219,7 @@ function normalizePositiveInteger(value: unknown, fallback: number): number {
async function createAuthenticatedGitHubApi() {
return createGitHubApi({
token: getAuthenticatedAccessToken(),
proxyUrl: await resolveGitHubProxyUrl()
...(await resolveGitHubTransport())
})
}

Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/main/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@oh-my-github/api'
import { ipcMain } from 'electron'
import { getAuthenticatedAccessToken } from './auth'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

export function registerActionsIpc(): void {
ipcMain.handle('actions:list-workflows', (_event, owner: string, repo: string) =>
Expand Down Expand Up @@ -193,6 +193,6 @@ function normalizePositiveInteger(value: number | undefined, fallback: number):
async function createAuthenticatedGitHubApi() {
return createGitHubApi({
token: getAuthenticatedAccessToken(),
proxyUrl: await resolveGitHubProxyUrl()
...(await resolveGitHubTransport())
})
}
4 changes: 2 additions & 2 deletions packages/client/src/main/activity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createGitHubApi, type PushCommitCountRef } from '@oh-my-github/api'
import { ipcMain } from 'electron'
import { getAuthenticatedAccessToken, getAuthenticatedViewerLogin } from './auth'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

interface ListReceivedEventsIpcOptions {
page?: number
Expand Down Expand Up @@ -40,6 +40,6 @@ async function listReceivedEvents(options?: ListReceivedEventsIpcOptions) {
async function createAuthenticatedGitHubApi() {
return createGitHubApi({
token: getAuthenticatedAccessToken(),
proxyUrl: await resolveGitHubProxyUrl(),
...(await resolveGitHubTransport()),
})
}
4 changes: 2 additions & 2 deletions packages/client/src/main/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
type StoredAccount,
type StoredAuthFile
} from './auth-store'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

export type { AccountSummary, AuthMethod, StoredAccount } from './auth-store'

Expand Down Expand Up @@ -355,7 +355,7 @@ async function pollForToken(options: {
}

async function createAuthApi(): Promise<AuthApi> {
return new AuthApi({ proxyUrl: await resolveGitHubProxyUrl() })
return new AuthApi({ ...(await resolveGitHubTransport()) })
}

function getGitHubClientId(): string {
Expand Down
35 changes: 33 additions & 2 deletions packages/client/src/main/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,21 @@ export interface LocalConfig {
activeAccountLogin: string | null
}
network: {
/**
* How GitHub connections choose a proxy:
* - `none` — direct, ignoring env vars and the OS proxy
* - `system` — the OS/env proxy (default; matches the app's original behaviour)
* - `custom` — the explicit `proxyUrl` below
*/
proxyMode: 'none' | 'system' | 'custom'
proxyUrl: string | null
/**
* Opt-in: trust the OS system CA store (in addition to Node's bundled
* roots) for GitHub connections. Off by default — follows Node's default
* behaviour. Enable only to work with a locally-installed root CA, e.g. a
* reverse-proxy tool that MITMs github.com; at the user's own risk.
*/
useSystemCa: boolean
}
ui: {
locale: 'en' | 'zh'
Expand Down Expand Up @@ -118,7 +132,9 @@ function normalizeConfig(config: Partial<LocalConfig>): LocalConfig {
activeAccountLogin: config.github?.activeAccountLogin ?? null
},
network: {
proxyUrl: normalizeProxyUrl(config.network?.proxyUrl)
proxyMode: normalizeProxyMode(config.network?.proxyMode, config.network?.proxyUrl),
proxyUrl: normalizeProxyUrl(config.network?.proxyUrl),
useSystemCa: config.network?.useSystemCa === true
},
ui: {
locale: normalizeLocale(config.ui?.locale),
Expand All @@ -141,7 +157,9 @@ function defaultConfig(): LocalConfig {
activeAccountLogin: null
},
network: {
proxyUrl: null
proxyMode: 'system',
proxyUrl: null,
useSystemCa: false
},
ui: {
locale: 'en',
Expand All @@ -157,6 +175,19 @@ function defaultConfig(): LocalConfig {
}
}

function normalizeProxyMode(
value: unknown,
proxyUrl: unknown
): LocalConfig['network']['proxyMode'] {
if (value === 'none' || value === 'system' || value === 'custom') {
return value
}

// Migrate configs written before proxyMode existed: an explicit proxyUrl meant
// "custom", otherwise the old null-cascade behaviour maps onto "system".
return typeof proxyUrl === 'string' && proxyUrl.trim() ? 'custom' : 'system'
}

function normalizeProxyUrl(value: unknown): string | null {
if (typeof value !== 'string') {
return null
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/main/deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@oh-my-github/api'
import { ipcMain } from 'electron'
import { getAuthenticatedAccessToken } from './auth'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

export function registerDeploymentsIpc(): void {
ipcMain.handle('deployments:list-environments', (_event, options: ListRepositoryEnvironmentsOptions) =>
Expand Down Expand Up @@ -146,6 +146,6 @@ function normalizePositiveInteger(value: number | undefined, fallback: number):
async function createAuthenticatedGitHubApi() {
return createGitHubApi({
token: getAuthenticatedAccessToken(),
proxyUrl: await resolveGitHubProxyUrl()
...(await resolveGitHubTransport())
})
}
4 changes: 2 additions & 2 deletions packages/client/src/main/inbox.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createGitHubApi, type GitHubNotification, type ListNotificationsOptions } from '@oh-my-github/api'
import { ipcMain } from 'electron'
import { getAuthenticatedAccessToken } from './auth'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

export function registerInboxIpc(): void {
ipcMain.handle('inbox:list-notifications', (_event, options?: ListNotificationsOptions) =>
Expand Down Expand Up @@ -41,7 +41,7 @@ async function unsubscribe(threadId: string) {
async function createAuthenticatedGitHubApi() {
return createGitHubApi({
token: getAuthenticatedAccessToken(),
proxyUrl: await resolveGitHubProxyUrl(),
...(await resolveGitHubTransport()),
})
}

Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/main/issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@oh-my-github/api'
import { ipcMain } from 'electron'
import { getAuthenticatedAccessToken } from './auth'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

export function registerIssuesIpc(): void {
ipcMain.handle('issues:list-category', (_event, category: GitHubIssueCategory) =>
Expand Down Expand Up @@ -373,6 +373,6 @@ function requireNonEmpty(value: string | undefined, message: string): string {
async function createAuthenticatedGitHubApi() {
return createGitHubApi({
token: getAuthenticatedAccessToken(),
proxyUrl: await resolveGitHubProxyUrl()
...(await resolveGitHubTransport())
})
}
4 changes: 2 additions & 2 deletions packages/client/src/main/organization-people.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createGitHubApi } from '@oh-my-github/api'
import { ipcMain } from 'electron'
import { getAuthenticatedAccessToken, getAuthenticatedAuthMetadata } from './auth'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

export function registerOrganizationPeopleIpc(): void {
ipcMain.handle('organization-people:get', (_event, org: string) => getOrganizationPeople(org))
Expand Down Expand Up @@ -138,7 +138,7 @@ function normalizeMemberRole(role: string | undefined): 'member' | 'admin' {
async function createAuthenticatedGitHubApi() {
return createGitHubApi({
token: getAuthenticatedAccessToken(),
proxyUrl: await resolveGitHubProxyUrl()
...(await resolveGitHubTransport())
})
}

Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/main/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@oh-my-github/api'
import { ipcMain } from 'electron'
import { getAuthenticatedAccessToken } from './auth'
import { resolveGitHubProxyUrl } from './proxy'
import { resolveGitHubTransport } from './proxy'

const PACKAGE_TYPES: readonly GitHubPackageType[] = ['npm', 'maven', 'rubygems', 'docker', 'nuget', 'container']

Expand Down Expand Up @@ -157,6 +157,6 @@ function normalizePositiveInteger(value: number | undefined, fallback: number):
async function createAuthenticatedGitHubApi() {
return createGitHubApi({
token: getAuthenticatedAccessToken(),
proxyUrl: await resolveGitHubProxyUrl()
...(await resolveGitHubTransport())
})
}
Loading
Loading