Skip to content

Commit 1ba13b8

Browse files
committed
feat: add support for github cloud data residency
Signed-off-by: Adam Setch <adam.setch@outlook.com>
1 parent cf98e9b commit 1ba13b8

11 files changed

Lines changed: 131 additions & 17 deletions

File tree

src/renderer/__mocks__/account-mocks.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ export const mockGitHubEnterpriseServerAccount: Account = {
5656
hasRequiredScopes: true,
5757
};
5858

59+
export const mockGitHubEnterpriseCloudAccount: Account = {
60+
platform: 'GitHub Enterprise Cloud with Data Residency',
61+
method: 'Personal Access Token',
62+
token: 'token-ghec-456' as Token,
63+
hostname: 'gitify.ghe.com' as Hostname,
64+
user: mockGitifyUser,
65+
version: 'latest',
66+
hasRequiredScopes: true,
67+
};
68+
5969
export function mockAccountWithError(error: GitifyError): AccountNotifications {
6070
return {
6171
account: mockGitHubCloudAccount,

src/renderer/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export const Constants = {
1616
clientSecret: process.env.OAUTH_CLIENT_SECRET as ClientSecret,
1717
} satisfies LoginOAuthAppOptions,
1818

19+
// GitHub Enterprise Cloud with Data Residency uses *.ghe.com domains
20+
GITHUB_ENTERPRISE_CLOUD_DATA_RESIDENCY_HOSTNAME: 'ghe.com',
21+
1922
GITHUB_API_BASE_URL: 'https://api.github.com',
2023
GITHUB_API_GRAPHQL_URL: 'https://api.github.com/graphql',
2124
GITHUB_API_MERGE_BATCH_SIZE: 100,

src/renderer/utils/__snapshots__/icons.test.ts.snap

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/renderer/utils/api/utils.test.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@ describe('renderer/utils/api/utils.ts', () => {
1010
expect(result.toString()).toBe('https://api.github.com/');
1111
});
1212

13-
it('should generate a GitHub REST API url - enterprise', () => {
13+
it('should generate a GitHub REST API url - enterprise server', () => {
1414
const result = getGitHubAPIBaseUrl(
1515
'github.gitify.io' as Hostname,
1616
'rest',
1717
);
1818

1919
expect(result.toString()).toBe('https://github.gitify.io/api/v3/');
2020
});
21+
22+
it('should generate a GitHub REST API url - enterprise cloud with data residency', () => {
23+
const result = getGitHubAPIBaseUrl('gitify.ghe.com' as Hostname, 'rest');
24+
25+
expect(result.toString()).toBe('https://api.gitify.ghe.com/');
26+
});
2127
});
2228

2329
it('should generate a GitHub GraphQL url - non enterprise', () => {
@@ -26,12 +32,18 @@ describe('renderer/utils/api/utils.ts', () => {
2632
expect(result.toString()).toBe('https://api.github.com/');
2733
});
2834

29-
it('should generate a GitHub GraphQL url - enterprise', () => {
35+
it('should generate a GitHub GraphQL url - enterprise server', () => {
3036
const result = getGitHubAPIBaseUrl(
3137
'github.gitify.io' as Hostname,
3238
'graphql',
3339
);
3440

3541
expect(result.toString()).toBe('https://github.gitify.io/api/');
3642
});
43+
44+
it('should generate a GitHub GraphQL url - enterprise cloud with data residency', () => {
45+
const result = getGitHubAPIBaseUrl('gitify.ghe.com' as Hostname, 'graphql');
46+
47+
expect(result.toString()).toBe('https://api.gitify.ghe.com/');
48+
});
3749
});

src/renderer/utils/api/utils.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,24 @@ import { Constants } from '../../constants';
33
import type { Hostname } from '../../types';
44
import type { APIClientType } from './types';
55

6-
import { isEnterpriseServerHost } from '../helpers';
6+
import { getPlatformFromHostname } from '../helpers';
77

88
export function getGitHubAPIBaseUrl(hostname: Hostname, type: APIClientType) {
9+
const platform = getPlatformFromHostname(hostname);
910
const url = new URL(Constants.GITHUB_API_BASE_URL);
1011

11-
if (isEnterpriseServerHost(hostname)) {
12-
url.hostname = hostname;
13-
url.pathname = type === 'rest' ? '/api/v3/' : '/api/';
12+
switch (platform) {
13+
case 'GitHub Enterprise Server':
14+
url.hostname = hostname;
15+
url.pathname = type === 'rest' ? '/api/v3/' : '/api/';
16+
break;
17+
case 'GitHub Enterprise Cloud with Data Residency':
18+
url.hostname = `api.${hostname}`;
19+
url.pathname = '/';
20+
break;
21+
default:
22+
url.pathname = '/';
23+
break;
1424
}
1525

1626
return url;

src/renderer/utils/auth/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import type {
88

99
export type AuthMethod = 'GitHub App' | 'Personal Access Token' | 'OAuth App';
1010

11-
export type PlatformType = 'GitHub Cloud' | 'GitHub Enterprise Server';
11+
export type PlatformType =
12+
| 'GitHub Cloud'
13+
| 'GitHub Enterprise Server'
14+
| 'GitHub Enterprise Cloud with Data Residency';
1215

1316
export interface LoginOAuthAppOptions {
1417
hostname: Hostname;

src/renderer/utils/auth/utils.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type { AuthMethod, AuthResponse, LoginOAuthAppOptions } from './types';
2525

2626
import { fetchAuthenticatedUserDetails } from '../api/client';
2727
import { encryptValue, openExternalLink } from '../comms';
28-
import { getPlatformFromHostname, isEnterpriseServerHost } from '../helpers';
28+
import { getPlatformFromHostname } from '../helpers';
2929
import { rendererLogError, rendererLogInfo, rendererLogWarn } from '../logger';
3030

3131
export function performGitHubOAuth(
@@ -206,12 +206,23 @@ export function extractHostVersion(version: string | null): string {
206206
}
207207

208208
export function getGitHubAuthBaseUrl(hostname: Hostname): URL {
209+
const platform = getPlatformFromHostname(hostname);
209210
const url = new URL(APPLICATION.GITHUB_BASE_URL);
210211

211-
if (isEnterpriseServerHost(hostname)) {
212-
url.hostname = hostname;
213-
url.pathname = '/api/v3/';
212+
switch (platform) {
213+
case 'GitHub Enterprise Server':
214+
url.hostname = hostname;
215+
url.pathname = '/api/v3/';
216+
break;
217+
case 'GitHub Enterprise Cloud with Data Residency':
218+
url.hostname = `api.${hostname}`;
219+
url.pathname = '/';
220+
break;
221+
default:
222+
url.pathname = '/';
223+
break;
214224
}
225+
215226
return url;
216227
}
217228

src/renderer/utils/helpers.test.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
generateNotificationReferrerId,
1616
getChevronDetails,
1717
getPlatformFromHostname,
18+
isCloudDataResidencyHost,
1819
isEnterpriseServerHost,
1920
parseInlineCode,
2021
} from './helpers';
@@ -38,10 +39,38 @@ describe('renderer/utils/helpers.ts', () => {
3839
'GitHub Enterprise Server',
3940
);
4041
});
42+
43+
it('should return GitHub Enterprise Cloud with Data Residency for ghe.com domains', () => {
44+
expect(getPlatformFromHostname('gitify.ghe.com' as Hostname)).toBe(
45+
'GitHub Enterprise Cloud with Data Residency',
46+
);
47+
expect(getPlatformFromHostname('acme-corp.ghe.com' as Hostname)).toBe(
48+
'GitHub Enterprise Cloud with Data Residency',
49+
);
50+
});
51+
});
52+
53+
describe('isCloudDataResidencyHost', () => {
54+
it('should return true for ghe.com hosts', () => {
55+
expect(isCloudDataResidencyHost('gitify.ghe.com' as Hostname)).toBe(true);
56+
expect(isCloudDataResidencyHost('acme-corp.ghe.com' as Hostname)).toBe(
57+
true,
58+
);
59+
});
60+
61+
it('should return false for non ghe.com hosts', () => {
62+
expect(isCloudDataResidencyHost('github.com' as Hostname)).toBe(false);
63+
expect(isCloudDataResidencyHost('api.github.com' as Hostname)).toBe(
64+
false,
65+
);
66+
expect(isCloudDataResidencyHost('github.gitify.app' as Hostname)).toBe(
67+
false,
68+
);
69+
});
4170
});
4271

4372
describe('isEnterpriseServerHost', () => {
44-
it('should return true for enterprise host', () => {
73+
it('should return true for enterprise server host', () => {
4574
expect(isEnterpriseServerHost('github.gitify.app' as Hostname)).toBe(
4675
true,
4776
);
@@ -50,10 +79,17 @@ describe('renderer/utils/helpers.ts', () => {
5079
);
5180
});
5281

53-
it('should return false for non-enterprise host', () => {
82+
it('should return false for github.com host', () => {
5483
expect(isEnterpriseServerHost('github.com' as Hostname)).toBe(false);
5584
expect(isEnterpriseServerHost('api.github.com' as Hostname)).toBe(false);
5685
});
86+
87+
it('should return false for ghe.com host', () => {
88+
expect(isEnterpriseServerHost('gitify.ghe.com' as Hostname)).toBe(false);
89+
expect(isEnterpriseServerHost('acme-corp.ghe.com' as Hostname)).toBe(
90+
false,
91+
);
92+
});
5793
});
5894

5995
describe('generateNotificationReferrerId', () => {

src/renderer/utils/helpers.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,28 @@ export interface ParsedCodePart {
2020
}
2121

2222
export function getPlatformFromHostname(hostname: string): PlatformType {
23-
return hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname)
24-
? 'GitHub Cloud'
25-
: 'GitHub Enterprise Server';
23+
if (hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname)) {
24+
return 'GitHub Cloud';
25+
}
26+
27+
if (
28+
hostname.endsWith(Constants.GITHUB_ENTERPRISE_CLOUD_DATA_RESIDENCY_HOSTNAME)
29+
) {
30+
return 'GitHub Enterprise Cloud with Data Residency';
31+
}
32+
33+
return 'GitHub Enterprise Server';
2634
}
2735

2836
export function isEnterpriseServerHost(hostname: Hostname): boolean {
29-
return !hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname);
37+
return getPlatformFromHostname(hostname) === 'GitHub Enterprise Server';
38+
}
39+
40+
export function isCloudDataResidencyHost(hostname: Hostname): boolean {
41+
return (
42+
getPlatformFromHostname(hostname) ===
43+
'GitHub Enterprise Cloud with Data Residency'
44+
);
3045
}
3146

3247
export function generateNotificationReferrerId(

src/renderer/utils/icons.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ describe('renderer/utils/icons.ts', () => {
122122
expect(getPlatformIcon('GitHub Cloud')).toMatchSnapshot();
123123

124124
expect(getPlatformIcon('GitHub Enterprise Server')).toMatchSnapshot();
125+
126+
expect(
127+
getPlatformIcon('GitHub Enterprise Cloud with Data Residency'),
128+
).toMatchSnapshot();
125129
});
126130

127131
describe('getDefaultUserIcon', () => {

0 commit comments

Comments
 (0)