Skip to content

Commit d1fd7d9

Browse files
Fix gmail disconnect error (#9649)
Signed-off-by: Artem Savchenko <[email protected]> Signed-off-by: Artyom Savchenko <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 09fa21e commit d1fd7d9

File tree

6 files changed

+114
-74
lines changed

6 files changed

+114
-74
lines changed

packages/integration-client/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './client'
1717
export * from './types'
1818
export * from './utils'
1919
export * from './events'
20+
export * from './request'
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright © 2025 Hardcore Engineering Inc.
2+
//
3+
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License. You may
5+
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
6+
//
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
//
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
import { concatLink } from '@hcengineering/core'
15+
import platform, { PlatformError, Status, Severity } from '@hcengineering/platform'
16+
17+
/**
18+
* Options for making HTTP requests to integration service APIs.
19+
*/
20+
export interface RequestOptions {
21+
/** The base URL of the API endpoint */
22+
baseUrl: string
23+
/** HTTP method to use */
24+
method: 'GET' | 'POST' | 'DELETE'
25+
/** Optional path to append to the base URL */
26+
path?: string
27+
/** Optional Bearer token for authorization */
28+
token?: string
29+
/** Optional request body (will be JSON stringified) */
30+
body?: any
31+
}
32+
33+
/**
34+
* Makes HTTP requests to integration service APIs with proper error handling.
35+
*
36+
* @param options - Request configuration options
37+
* @returns Promise that resolves to the parsed JSON response, or undefined for empty responses
38+
* @throws {PlatformError} When network errors occur or HTTP status indicates failure
39+
*/
40+
export async function request (options: RequestOptions): Promise<any> {
41+
const { baseUrl, method, path, token, body } = options
42+
let response: Response
43+
try {
44+
response = await fetch(concatLink(baseUrl, path ?? ''), {
45+
method,
46+
headers: {
47+
...(token !== undefined ? { Authorization: 'Bearer ' + token } : {}),
48+
'Content-Type': 'application/json'
49+
},
50+
...(body !== undefined ? { body: JSON.stringify(body) } : {})
51+
})
52+
} catch (err) {
53+
throw new PlatformError(
54+
new Status(Severity.ERROR, platform.status.ConnectionClosed, {
55+
message: 'Network error occurred'
56+
})
57+
)
58+
}
59+
60+
if (response.status === 200) {
61+
const contentLength = response.headers.get('content-length')
62+
const contentType = response.headers.get('content-type') ?? ''
63+
64+
if (contentLength === '0' || (!contentType.includes('application/json') && !contentType.includes('text/json'))) {
65+
return undefined
66+
}
67+
68+
const text = await response.text()
69+
if (text.trim() === '') {
70+
return undefined
71+
}
72+
73+
try {
74+
return JSON.parse(text)
75+
} catch (error) {
76+
console.warn('Failed to parse JSON response:', text, error)
77+
return undefined
78+
}
79+
} else if (response.status === 202) {
80+
return undefined
81+
} else if (response.status === 401) {
82+
throw new PlatformError(new Status(Severity.ERROR, platform.status.Unauthorized, {}))
83+
} else if (response.status === 403) {
84+
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
85+
} else if (response.status === 404) {
86+
throw new PlatformError(
87+
new Status(Severity.ERROR, platform.status.ResourceNotFound, { resource: options.path ?? '' })
88+
)
89+
} else if (response.status >= 500) {
90+
throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
91+
} else {
92+
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, { status: response.status }))
93+
}
94+
}

plugins/calendar-resources/src/api.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
// limitations under the License.
1414
//
1515

16-
import { concatLink } from '@hcengineering/core'
1716
import { getMetadata } from '@hcengineering/platform'
1817
import presentation from '@hcengineering/presentation'
1918
import login from '@hcengineering/login'
2019
import { type Integration } from '@hcengineering/account-client'
2120
import {
2221
type IntegrationClient,
23-
getIntegrationClient as getIntegrationClientRaw
22+
getIntegrationClient as getIntegrationClientRaw,
23+
request
2424
} from '@hcengineering/integration-client'
2525
import calendar from './plugin'
2626
import { calendarIntegrationKind } from '@hcengineering/calendar'
@@ -36,12 +36,11 @@ export async function signout (integration: Integration, client: IntegrationClie
3636
if (email === undefined) {
3737
throw new Error('Email is not defined in integration')
3838
}
39-
await fetch(concatLink(url, `/signout?value=${email}`), {
39+
await request({
40+
baseUrl: url,
41+
path: `/signout?value=${email}`,
4042
method: 'GET',
41-
headers: {
42-
Authorization: 'Bearer ' + token,
43-
'Content-Type': 'application/json'
44-
}
43+
token
4544
})
4645
}
4746

plugins/gmail-resources/src/api.ts

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import login from '@hcengineering/login'
1818
import { type GmailSyncState, gmailIntegrationKind } from '@hcengineering/gmail'
1919
import {
2020
getIntegrationClient as getIntegrationClientRaw,
21-
type IntegrationClient
21+
type IntegrationClient,
22+
request as httpRequest
2223
} from '@hcengineering/integration-client'
2324

2425
import gmail from './plugin'
25-
import { concatLink } from '@hcengineering/core'
2626

2727
export async function getIntegrationClient (): Promise<IntegrationClient> {
2828
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
@@ -36,22 +36,13 @@ export async function getIntegrationClient (): Promise<IntegrationClient> {
3636
const url = getMetadata(gmail.metadata.GmailURL) ?? ''
3737

3838
async function request (method: 'GET' | 'POST' | 'DELETE', path?: string, body?: any): Promise<any> {
39-
const response = await fetch(concatLink(url, path ?? ''), {
39+
return await httpRequest({
40+
baseUrl: url,
4041
method,
41-
headers: {
42-
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
43-
'Content-Type': 'application/json'
44-
},
45-
...(body !== undefined ? { body: JSON.stringify(body) } : {})
42+
path,
43+
token: getMetadata(presentation.metadata.Token),
44+
body
4645
})
47-
48-
if (response.status === 200) {
49-
return await response.json()
50-
} else if (response.status === 202) {
51-
return undefined
52-
} else {
53-
throw new Error(`Unexpected response: ${response.status}`)
54-
}
5546
}
5647

5748
export async function getState (socialId: string): Promise<GmailSyncState | null> {

plugins/setting-resources/src/components/integrations/Integrations.svelte

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@
117117
}
118118
119119
function onRefreshIntegrations (data: any): void {
120-
console.log('Refreshing integrations due to:', data.integrationKind, data.operation)
121120
lastEventTime = Date.now()
122121
void refreshIntegrations()
123122
}
@@ -229,7 +228,6 @@
229228
}
230229
return integrationInfo
231230
})
232-
console.log('Filtered Integrations:', filteredIntegrations)
233231
return filteredIntegrations
234232
}
235233

plugins/telegram-resources/src/api.ts

Lines changed: 6 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313
//
1414

1515
import { concatLink, type PersonId } from '@hcengineering/core'
16-
import platform, { getMetadata, PlatformError, Status, Severity } from '@hcengineering/platform'
16+
import { getMetadata } from '@hcengineering/platform'
1717
import telegram from './plugin'
1818
import presentation, { getCurrentWorkspaceUuid } from '@hcengineering/presentation'
1919
import login from '@hcengineering/login'
2020
import { telegramIntegrationKind } from '@hcengineering/telegram'
2121
import {
2222
getIntegrationClient as getIntegrationClientRaw,
23-
type IntegrationClient
23+
type IntegrationClient,
24+
request as httpRequest
2425
} from '@hcengineering/integration-client'
2526
import { withRetry } from '@hcengineering/retry'
2627
import type { Integration } from '@hcengineering/account-client'
@@ -50,55 +51,11 @@ export interface TelegramChannelData {
5051
}
5152

5253
const url = getMetadata(telegram.metadata.TelegramURL) ?? ''
53-
54-
async function _request (method: 'GET' | 'POST' | 'DELETE', path?: string, body?: any): Promise<any> {
55-
const base = concatLink(url, 'api/integrations')
56-
57-
let response: Response
58-
try {
59-
response = await fetch(concatLink(base, path ?? ''), {
60-
method,
61-
headers: {
62-
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
63-
'Content-Type': 'application/json'
64-
},
65-
...(body !== undefined ? { body: JSON.stringify(body) } : {})
66-
})
67-
} catch (err) {
68-
throw new PlatformError(
69-
new Status(Severity.ERROR, platform.status.ConnectionClosed, {
70-
message: 'Network error occurred'
71-
})
72-
)
73-
}
74-
75-
if (response.status === 200) {
76-
try {
77-
return await response.json()
78-
} catch (err) {
79-
throw new PlatformError(
80-
new Status(Severity.ERROR, platform.status.BadRequest, {
81-
message: 'Failed to parse response JSON'
82-
})
83-
)
84-
}
85-
} else if (response.status === 202) {
86-
return undefined
87-
} else if (response.status === 401) {
88-
throw new PlatformError(new Status(Severity.ERROR, platform.status.Unauthorized, {}))
89-
} else if (response.status === 403) {
90-
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
91-
} else if (response.status === 404) {
92-
throw new PlatformError(new Status(Severity.ERROR, platform.status.ResourceNotFound, { resource: path ?? '' }))
93-
} else if (response.status >= 500) {
94-
throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
95-
} else {
96-
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, { status: response.status }))
97-
}
98-
}
54+
const baseUrl = concatLink(url, 'api/integrations')
9955

10056
async function request (method: 'GET' | 'POST' | 'DELETE', path?: string, body?: any): Promise<any> {
101-
return await withRetry(async () => await _request(method, path, body))
57+
const token = getMetadata(presentation.metadata.Token)
58+
return await withRetry(async () => await httpRequest({ baseUrl, method, path, token, body }))
10259
}
10360

10461
export async function getState (phone: string): Promise<IntegrationState> {

0 commit comments

Comments
 (0)