@shipfox/client-integrations 5.0.0 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +84 -0
- package/dist/application/complete-integration-callback.d.ts +13 -0
- package/dist/application/complete-integration-callback.d.ts.map +1 -0
- package/dist/application/complete-integration-callback.js +26 -0
- package/dist/application/complete-integration-callback.js.map +1 -0
- package/dist/components/connection-picker.d.ts +2 -2
- package/dist/components/connection-picker.d.ts.map +1 -1
- package/dist/components/connection-picker.js +4 -4
- package/dist/components/connection-picker.js.map +1 -1
- package/dist/components/installed-integrations-section.d.ts +8 -6
- package/dist/components/installed-integrations-section.d.ts.map +1 -1
- package/dist/components/installed-integrations-section.js +22 -15
- package/dist/components/installed-integrations-section.js.map +1 -1
- package/dist/components/integration-gallery-for-workspace.d.ts +2 -2
- package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -1
- package/dist/components/integration-gallery-for-workspace.js +14 -7
- package/dist/components/integration-gallery-for-workspace.js.map +1 -1
- package/dist/components/integration-gallery.d.ts +2 -2
- package/dist/components/integration-gallery.d.ts.map +1 -1
- package/dist/components/integration-gallery.js.map +1 -1
- package/dist/components/integration-usage-events.d.ts +1 -3
- package/dist/components/integration-usage-events.d.ts.map +1 -1
- package/dist/components/integration-usage-events.js +1 -125
- package/dist/components/integration-usage-events.js.map +1 -1
- package/dist/components/integration-usage-modal.d.ts +2 -2
- package/dist/components/integration-usage-modal.d.ts.map +1 -1
- package/dist/components/integration-usage-modal.js +2 -2
- package/dist/components/integration-usage-modal.js.map +1 -1
- package/dist/components/provider-grid.d.ts +7 -4
- package/dist/components/provider-grid.d.ts.map +1 -1
- package/dist/components/provider-grid.js +15 -10
- package/dist/components/provider-grid.js.map +1 -1
- package/dist/components/redirect-install-page.d.ts +2 -3
- package/dist/components/redirect-install-page.d.ts.map +1 -1
- package/dist/components/redirect-install-page.js +1 -1
- package/dist/components/redirect-install-page.js.map +1 -1
- package/dist/components/repository-picker.d.ts +2 -2
- package/dist/components/repository-picker.d.ts.map +1 -1
- package/dist/components/repository-picker.js +4 -4
- package/dist/components/repository-picker.js.map +1 -1
- package/dist/components/webhook/webhook-create-modal.d.ts +2 -2
- package/dist/components/webhook/webhook-create-modal.d.ts.map +1 -1
- package/dist/components/webhook/webhook-create-modal.js +8 -8
- package/dist/components/webhook/webhook-create-modal.js.map +1 -1
- package/dist/components/webhook/webhook-modals.stories.d.ts.map +1 -1
- package/dist/components/webhook/webhook-modals.stories.js +7 -7
- package/dist/components/webhook/webhook-modals.stories.js.map +1 -1
- package/dist/components/webhook/webhook-usage-details.d.ts.map +1 -1
- package/dist/components/webhook/webhook-usage-details.js +2 -2
- package/dist/components/webhook/webhook-usage-details.js.map +1 -1
- package/dist/connection-status-badge.d.ts +2 -2
- package/dist/connection-status-badge.d.ts.map +1 -1
- package/dist/connection-status-badge.js +11 -15
- package/dist/connection-status-badge.js.map +1 -1
- package/dist/core/models.d.ts +66 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +156 -0
- package/dist/core/models.js.map +1 -0
- package/dist/feature.d.ts +7 -0
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +10 -9
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/integration-mapper.d.ts +11 -0
- package/dist/hooks/api/integration-mapper.d.ts.map +1 -0
- package/dist/hooks/api/integration-mapper.js +54 -0
- package/dist/hooks/api/integration-mapper.js.map +1 -0
- package/dist/hooks/api/integrations.d.ts +45 -240
- package/dist/hooks/api/integrations.d.ts.map +1 -1
- package/dist/hooks/api/integrations.js +78 -31
- package/dist/hooks/api/integrations.js.map +1 -1
- package/dist/hooks/api/webhook-connections.d.ts +12 -64
- package/dist/hooks/api/webhook-connections.d.ts.map +1 -1
- package/dist/hooks/api/webhook-connections.js +17 -9
- package/dist/hooks/api/webhook-connections.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/linear-callback.d.ts +2 -1
- package/dist/linear-callback.d.ts.map +1 -1
- package/dist/linear-callback.js +17 -15
- package/dist/linear-callback.js.map +1 -1
- package/dist/pages/gitea-install-page.d.ts.map +1 -1
- package/dist/pages/gitea-install-page.js +1 -10
- package/dist/pages/gitea-install-page.js.map +1 -1
- package/dist/pages/linear-callback-page.d.ts.map +1 -1
- package/dist/pages/linear-callback-page.js +19 -32
- package/dist/pages/linear-callback-page.js.map +1 -1
- package/dist/pages/linear-install-page.d.ts.map +1 -1
- package/dist/pages/linear-install-page.js +2 -1
- package/dist/pages/linear-install-page.js.map +1 -1
- package/dist/pages/sentry-callback-page.d.ts.map +1 -1
- package/dist/pages/sentry-callback-page.js +22 -25
- package/dist/pages/sentry-callback-page.js.map +1 -1
- package/dist/pages/sentry-install-page.d.ts.map +1 -1
- package/dist/pages/sentry-install-page.js +2 -1
- package/dist/pages/sentry-install-page.js.map +1 -1
- package/dist/pages/slack-callback-page.d.ts.map +1 -1
- package/dist/pages/slack-callback-page.js +21 -28
- package/dist/pages/slack-callback-page.js.map +1 -1
- package/dist/pages/slack-install-page.d.ts.map +1 -1
- package/dist/pages/slack-install-page.js +2 -1
- package/dist/pages/slack-install-page.js.map +1 -1
- package/dist/pages/source-control-onboarding-page.d.ts.map +1 -1
- package/dist/pages/source-control-onboarding-page.js +5 -1
- package/dist/pages/source-control-onboarding-page.js.map +1 -1
- package/dist/provider-catalog.d.ts.map +1 -1
- package/dist/provider-catalog.js +7 -6
- package/dist/provider-catalog.js.map +1 -1
- package/dist/routes/github-callback.d.ts +16 -0
- package/dist/routes/github-callback.d.ts.map +1 -1
- package/dist/routes/github-callback.js +43 -21
- package/dist/routes/github-callback.js.map +1 -1
- package/dist/routes/linear-callback.d.ts +2 -0
- package/dist/routes/linear-callback.d.ts.map +1 -1
- package/dist/routes/linear-callback.js +2 -0
- package/dist/routes/linear-callback.js.map +1 -1
- package/dist/routes/sentry-callback.d.ts +2 -0
- package/dist/routes/sentry-callback.d.ts.map +1 -1
- package/dist/routes/sentry-callback.js +2 -0
- package/dist/routes/sentry-callback.js.map +1 -1
- package/dist/routes/slack-callback.d.ts +2 -0
- package/dist/routes/slack-callback.d.ts.map +1 -1
- package/dist/routes/slack-callback.js +2 -0
- package/dist/routes/slack-callback.js.map +1 -1
- package/dist/sentry-callback.d.ts +2 -1
- package/dist/sentry-callback.d.ts.map +1 -1
- package/dist/sentry-callback.js +18 -19
- package/dist/sentry-callback.js.map +1 -1
- package/dist/slack-callback.d.ts +2 -1
- package/dist/slack-callback.d.ts.map +1 -1
- package/dist/slack-callback.js +17 -15
- package/dist/slack-callback.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/application/complete-integration-callback.test.ts +41 -0
- package/src/application/complete-integration-callback.ts +39 -0
- package/src/components/connection-picker.tsx +7 -7
- package/src/components/installed-integrations-section.tsx +29 -24
- package/src/components/integration-gallery-for-workspace.tsx +23 -16
- package/src/components/integration-gallery.tsx +3 -3
- package/src/components/integration-usage-events.ts +1 -116
- package/src/components/integration-usage-modal.tsx +4 -4
- package/src/components/provider-grid.tsx +30 -18
- package/src/components/redirect-install-page.test.tsx +2 -6
- package/src/components/redirect-install-page.tsx +3 -2
- package/src/components/repository-picker.tsx +6 -6
- package/src/components/webhook/webhook-create-modal.tsx +12 -11
- package/src/components/webhook/webhook-modals.stories.tsx +10 -10
- package/src/components/webhook/webhook-usage-details.tsx +2 -4
- package/src/connection-status-badge.tsx +8 -14
- package/src/core/models.ts +200 -0
- package/src/feature.ts +12 -10
- package/src/hooks/api/integration-mapper.ts +64 -0
- package/src/hooks/api/integrations.test.ts +44 -22
- package/src/hooks/api/integrations.ts +229 -84
- package/src/hooks/api/webhook-connections.ts +58 -17
- package/src/index.ts +2 -1
- package/src/linear-callback.ts +24 -16
- package/src/pages/gitea-install-page.tsx +1 -10
- package/src/pages/linear-callback-page.tsx +18 -30
- package/src/pages/linear-install-page.tsx +2 -1
- package/src/pages/sentry-callback-page.test.tsx +5 -1
- package/src/pages/sentry-callback-page.tsx +28 -25
- package/src/pages/sentry-install-page.tsx +2 -1
- package/src/pages/slack-callback-page.tsx +20 -26
- package/src/pages/slack-install-page.tsx +2 -1
- package/src/pages/source-control-onboarding-page.tsx +5 -1
- package/src/provider-catalog.ts +7 -6
- package/src/routes/github-callback.test.ts +35 -1
- package/src/routes/github-callback.tsx +47 -21
- package/src/routes/linear-callback.tsx +5 -1
- package/src/routes/sentry-callback.tsx +5 -1
- package/src/routes/slack-callback.tsx +6 -1
- package/src/sentry-callback.test.ts +2 -1
- package/src/sentry-callback.ts +30 -20
- package/src/slack-callback.ts +24 -16
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vercel.json +5 -0
- package/dist/integration-icon.d.ts +0 -17
- package/dist/integration-icon.d.ts.map +0 -1
- package/dist/integration-icon.js +0 -22
- package/dist/integration-icon.js.map +0 -1
- package/src/integration-icon.react.test.tsx +0 -40
- package/src/integration-icon.test.ts +0 -19
- package/src/integration-icon.tsx +0 -26
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import {ApiError} from '@shipfox/client-api';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
githubCallbackErrorMessage,
|
|
4
|
+
githubCallbackParams,
|
|
5
|
+
missingCallbackParams,
|
|
6
|
+
validateGithubCallbackSearch,
|
|
7
|
+
} from './github-callback.js';
|
|
3
8
|
|
|
4
9
|
describe('githubCallbackErrorMessage', () => {
|
|
5
10
|
it('does not expose an API error message or request URL', () => {
|
|
@@ -15,3 +20,32 @@ describe('githubCallbackErrorMessage', () => {
|
|
|
15
20
|
expect(message).not.toContain('https://');
|
|
16
21
|
});
|
|
17
22
|
});
|
|
23
|
+
|
|
24
|
+
describe('github callback route inputs', () => {
|
|
25
|
+
it('normalizes valid values and reports missing or malformed inputs', () => {
|
|
26
|
+
const search = validateGithubCallbackSearch({
|
|
27
|
+
code: 'grant-code',
|
|
28
|
+
installation_id: '42',
|
|
29
|
+
state: 'signed-state',
|
|
30
|
+
setup_action: 'install',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(search).toEqual({
|
|
34
|
+
code: 'grant-code',
|
|
35
|
+
installationId: 42,
|
|
36
|
+
state: 'signed-state',
|
|
37
|
+
setupAction: 'install',
|
|
38
|
+
});
|
|
39
|
+
expect(githubCallbackParams(search)).toEqual({
|
|
40
|
+
code: 'grant-code',
|
|
41
|
+
installationId: 42,
|
|
42
|
+
state: 'signed-state',
|
|
43
|
+
setupAction: 'install',
|
|
44
|
+
});
|
|
45
|
+
expect(missingCallbackParams(validateGithubCallbackSearch({code: ['bad'], state: 7}))).toEqual([
|
|
46
|
+
'code',
|
|
47
|
+
'installation_id',
|
|
48
|
+
'state',
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -1,30 +1,43 @@
|
|
|
1
|
-
import {ApiError
|
|
1
|
+
import {ApiError} from '@shipfox/client-api';
|
|
2
2
|
import {useRefreshAuth} from '@shipfox/client-auth';
|
|
3
|
-
import {defineRoute} from '@shipfox/client-shell/runtime';
|
|
3
|
+
import {defineRoute, useRouteSearch} from '@shipfox/client-shell/runtime';
|
|
4
4
|
import {createSingleFlight} from '@shipfox/client-ui';
|
|
5
5
|
import {FullPageLoader} from '@shipfox/react-ui/loader';
|
|
6
6
|
import {toast} from '@shipfox/react-ui/toast';
|
|
7
|
-
import {useNavigate
|
|
7
|
+
import {useNavigate} from '@tanstack/react-router';
|
|
8
8
|
import {useEffect} from 'react';
|
|
9
|
+
import {useCompleteIntegrationCallback} from '#application/complete-integration-callback.js';
|
|
10
|
+
import {completeGithubCallback} from '#hooks/api/integrations.js';
|
|
9
11
|
|
|
10
|
-
interface GithubCallbackParams {
|
|
12
|
+
export interface GithubCallbackParams {
|
|
11
13
|
code: string;
|
|
12
14
|
installationId: number;
|
|
13
15
|
state: string;
|
|
14
16
|
setupAction?: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
export interface GithubCallbackSearch {
|
|
20
|
+
code?: string;
|
|
21
|
+
installationId?: number;
|
|
22
|
+
state?: string;
|
|
23
|
+
setupAction?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
// Retain only recent completions: this bounds long-lived callback pages while
|
|
18
27
|
// still covering StrictMode and immediate Back/Forward remounts.
|
|
19
28
|
const callbackRequests = createSingleFlight<string, void>({maxTerminalResults: 32});
|
|
20
29
|
const toastedCallbacks = new Set<string>();
|
|
21
30
|
|
|
22
|
-
export default defineRoute({
|
|
31
|
+
export default defineRoute({
|
|
32
|
+
validateSearch: validateGithubCallbackSearch,
|
|
33
|
+
component: GithubCallbackRoute,
|
|
34
|
+
});
|
|
23
35
|
|
|
24
36
|
function GithubCallbackRoute() {
|
|
25
|
-
const search =
|
|
37
|
+
const search = useRouteSearch(validateGithubCallbackSearch);
|
|
26
38
|
const navigate = useNavigate();
|
|
27
39
|
const refreshAuth = useRefreshAuth();
|
|
40
|
+
const completeIntegrationCallback = useCompleteIntegrationCallback();
|
|
28
41
|
|
|
29
42
|
useEffect(() => {
|
|
30
43
|
const missing = missingCallbackParams(search);
|
|
@@ -46,11 +59,11 @@ function GithubCallbackRoute() {
|
|
|
46
59
|
const request = callbackRequests.run(
|
|
47
60
|
key,
|
|
48
61
|
async () =>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})
|
|
53
|
-
}),
|
|
62
|
+
void (await completeIntegrationCallback({
|
|
63
|
+
input: params,
|
|
64
|
+
refreshAuth,
|
|
65
|
+
complete: async (input, token) => await completeGithubCallback({...input, token}),
|
|
66
|
+
})),
|
|
54
67
|
);
|
|
55
68
|
request
|
|
56
69
|
.then(() => {
|
|
@@ -72,24 +85,37 @@ function GithubCallbackRoute() {
|
|
|
72
85
|
return () => {
|
|
73
86
|
disposed = true;
|
|
74
87
|
};
|
|
75
|
-
}, [navigate, refreshAuth, search]);
|
|
88
|
+
}, [completeIntegrationCallback, navigate, refreshAuth, search]);
|
|
76
89
|
|
|
77
90
|
return <FullPageLoader />;
|
|
78
91
|
}
|
|
79
92
|
|
|
80
|
-
function
|
|
93
|
+
export function validateGithubCallbackSearch(input: Record<string, unknown>): GithubCallbackSearch {
|
|
94
|
+
const code = stringParam(input.code);
|
|
95
|
+
const installationId = numberParam(input.installation_id);
|
|
96
|
+
const state = stringParam(input.state);
|
|
97
|
+
const setupAction = stringParam(input.setup_action);
|
|
98
|
+
return {
|
|
99
|
+
...(code ? {code} : {}),
|
|
100
|
+
...(installationId === undefined ? {} : {installationId}),
|
|
101
|
+
...(state ? {state} : {}),
|
|
102
|
+
...(setupAction ? {setupAction} : {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function missingCallbackParams(search: GithubCallbackSearch): string[] {
|
|
81
107
|
const missing: string[] = [];
|
|
82
|
-
if (!
|
|
83
|
-
if (
|
|
84
|
-
if (!
|
|
108
|
+
if (!search.code) missing.push('code');
|
|
109
|
+
if (search.installationId === undefined) missing.push('installation_id');
|
|
110
|
+
if (!search.state) missing.push('state');
|
|
85
111
|
return missing;
|
|
86
112
|
}
|
|
87
|
-
function githubCallbackParams(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const state =
|
|
113
|
+
export function githubCallbackParams(
|
|
114
|
+
search: GithubCallbackSearch,
|
|
115
|
+
): GithubCallbackParams | undefined {
|
|
116
|
+
const {code, installationId, state} = search;
|
|
91
117
|
if (!code || installationId === undefined || !state) return undefined;
|
|
92
|
-
const setupAction =
|
|
118
|
+
const {setupAction} = search;
|
|
93
119
|
return setupAction ? {code, installationId, state, setupAction} : {code, installationId, state};
|
|
94
120
|
}
|
|
95
121
|
function encodeCallbackQuery(params: GithubCallbackParams): string {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import {defineRoute} from '@shipfox/client-shell/runtime';
|
|
2
2
|
import {LinearCallbackPage} from '#pages/linear-callback-page.js';
|
|
3
|
+
import {parseLinearCallbackQuery} from '../linear-callback.js';
|
|
3
4
|
|
|
4
|
-
export default defineRoute({
|
|
5
|
+
export default defineRoute({
|
|
6
|
+
validateSearch: parseLinearCallbackQuery,
|
|
7
|
+
component: LinearCallbackPage,
|
|
8
|
+
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import {defineRoute} from '@shipfox/client-shell/runtime';
|
|
2
2
|
import {SentryCallbackPage} from '#pages/sentry-callback-page.js';
|
|
3
|
+
import {parseSentryCallbackParams} from '../sentry-callback.js';
|
|
3
4
|
|
|
4
|
-
export default defineRoute({
|
|
5
|
+
export default defineRoute({
|
|
6
|
+
validateSearch: parseSentryCallbackParams,
|
|
7
|
+
component: SentryCallbackPage,
|
|
8
|
+
});
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import {defineRoute} from '@shipfox/client-shell/runtime';
|
|
2
2
|
import {SlackCallbackPage} from '#pages/slack-callback-page.js';
|
|
3
|
-
|
|
3
|
+
import {parseSlackCallbackQuery} from '../slack-callback.js';
|
|
4
|
+
|
|
5
|
+
export default defineRoute({
|
|
6
|
+
validateSearch: parseSlackCallbackQuery,
|
|
7
|
+
component: SlackCallbackPage,
|
|
8
|
+
});
|
|
@@ -181,7 +181,8 @@ describe('classifySentryConnectError', () => {
|
|
|
181
181
|
|
|
182
182
|
expect(result).toEqual({
|
|
183
183
|
kind: 'terminal',
|
|
184
|
-
message:
|
|
184
|
+
message:
|
|
185
|
+
'Sentry did not accept this install. Start the install again from workspace settings.',
|
|
185
186
|
startOver: true,
|
|
186
187
|
});
|
|
187
188
|
});
|
package/src/sentry-callback.ts
CHANGED
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
import {ApiError} from '@shipfox/client-api';
|
|
2
|
+
import {
|
|
3
|
+
type BrowserStorage,
|
|
4
|
+
type BrowserStorageKey,
|
|
5
|
+
createTypedBrowserStorage,
|
|
6
|
+
} from '@shipfox/client-ui';
|
|
2
7
|
|
|
3
8
|
export const SENTRY_INSTALL_WORKSPACE_KEY = 'shipfox.sentry-install.workspace-id';
|
|
4
9
|
|
|
5
|
-
type WorkspaceStorage =
|
|
10
|
+
type WorkspaceStorage = BrowserStorage | undefined;
|
|
11
|
+
|
|
12
|
+
const sentryInstallWorkspaceStorageKey = {
|
|
13
|
+
key: SENTRY_INSTALL_WORKSPACE_KEY,
|
|
14
|
+
lifetime: 'session',
|
|
15
|
+
// This is a one-shot navigation hint, not private workspace state. The
|
|
16
|
+
// callback validates it against the authenticated workspace list before use.
|
|
17
|
+
principalScope: 'global',
|
|
18
|
+
serialize: (workspaceId: string) => workspaceId,
|
|
19
|
+
parse: (value: string) => value || undefined,
|
|
20
|
+
} satisfies BrowserStorageKey<string>;
|
|
6
21
|
|
|
7
22
|
// Storage helpers swallow failures (quota, private mode, disabled storage):
|
|
8
23
|
// the handoff is an optimization, never a requirement — the callback always
|
|
9
24
|
// asks for explicit confirmation and only uses the stored id to pre-select.
|
|
10
25
|
export function saveSentryInstallWorkspace(storage: WorkspaceStorage, workspaceId: string): void {
|
|
11
|
-
|
|
12
|
-
storage.setItem(SENTRY_INSTALL_WORKSPACE_KEY, workspaceId);
|
|
13
|
-
} catch {
|
|
14
|
-
// The callback falls back to its sole-workspace pre-selection.
|
|
15
|
-
}
|
|
26
|
+
installWorkspaceStorage(storage).write(workspaceId);
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
export function readSentryInstallWorkspace(storage: WorkspaceStorage): string | undefined {
|
|
19
|
-
|
|
20
|
-
return storage.getItem(SENTRY_INSTALL_WORKSPACE_KEY) ?? undefined;
|
|
21
|
-
} catch {
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
30
|
+
return installWorkspaceStorage(storage).read();
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
export function clearSentryInstallWorkspace(storage: WorkspaceStorage): void {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
installWorkspaceStorage(storage).remove();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function installWorkspaceStorage(storage: WorkspaceStorage) {
|
|
38
|
+
return createTypedBrowserStorage(() => storage, sentryInstallWorkspaceStorageKey);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export interface SentryCallbackParams {
|
|
@@ -108,7 +115,7 @@ export function classifySentryConnectError(error: unknown): SentryConnectFailure
|
|
|
108
115
|
return {kind: 'retryable', message: 'Sentry is unreachable. Try again.'};
|
|
109
116
|
}
|
|
110
117
|
if (error.status === 0 || error.code === 'network-error') {
|
|
111
|
-
// A network failure (
|
|
118
|
+
// A network failure (the client transport reports status 0) never reached the
|
|
112
119
|
// server, so the grant code is untouched and a plain Retry can recover.
|
|
113
120
|
// Must precede the status < 500 branch, which would otherwise treat it as
|
|
114
121
|
// a spent code and force a full restart.
|
|
@@ -118,9 +125,12 @@ export function classifySentryConnectError(error: unknown): SentryConnectFailure
|
|
|
118
125
|
};
|
|
119
126
|
}
|
|
120
127
|
if (error.status < 500) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
return {
|
|
129
|
+
kind: 'terminal',
|
|
130
|
+
message:
|
|
131
|
+
'Sentry did not accept this install. Start the install again from workspace settings.',
|
|
132
|
+
startOver: true,
|
|
133
|
+
};
|
|
124
134
|
}
|
|
125
135
|
}
|
|
126
136
|
return {kind: 'retryable', message: 'Could not install Sentry. Try again.'};
|
package/src/slack-callback.ts
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import type {SlackCallbackQueryDto} from '@shipfox/api-integration-slack-dto';
|
|
2
2
|
import {ApiError} from '@shipfox/client-api';
|
|
3
|
+
import {
|
|
4
|
+
type BrowserStorage,
|
|
5
|
+
type BrowserStorageKey,
|
|
6
|
+
createTypedBrowserStorage,
|
|
7
|
+
} from '@shipfox/client-ui';
|
|
3
8
|
|
|
4
9
|
export const SLACK_INSTALL_WORKSPACE_KEY = 'shipfox.slack-install.workspace-id';
|
|
5
|
-
type WorkspaceStorage =
|
|
10
|
+
type WorkspaceStorage = BrowserStorage | undefined;
|
|
11
|
+
|
|
12
|
+
const slackInstallWorkspaceStorageKey = {
|
|
13
|
+
key: SLACK_INSTALL_WORKSPACE_KEY,
|
|
14
|
+
lifetime: 'session',
|
|
15
|
+
// This is a one-shot navigation hint, not private workspace state. It must
|
|
16
|
+
// remain readable on an OAuth callback before workspace hydration completes;
|
|
17
|
+
// the callback state and returned connection are the authoritative checks.
|
|
18
|
+
principalScope: 'global',
|
|
19
|
+
serialize: (workspaceId: string) => workspaceId,
|
|
20
|
+
parse: (value: string) => value || undefined,
|
|
21
|
+
} satisfies BrowserStorageKey<string>;
|
|
6
22
|
|
|
7
23
|
export function saveSlackInstallWorkspace(storage: WorkspaceStorage, workspaceId: string): void {
|
|
8
|
-
|
|
9
|
-
storage.setItem(SLACK_INSTALL_WORKSPACE_KEY, workspaceId);
|
|
10
|
-
} catch {
|
|
11
|
-
// Storage only supports recovery navigation.
|
|
12
|
-
}
|
|
24
|
+
installWorkspaceStorage(storage).write(workspaceId);
|
|
13
25
|
}
|
|
14
26
|
export function readSlackInstallWorkspace(storage: WorkspaceStorage): string | undefined {
|
|
15
|
-
|
|
16
|
-
return storage.getItem(SLACK_INSTALL_WORKSPACE_KEY) ?? undefined;
|
|
17
|
-
} catch {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
27
|
+
return installWorkspaceStorage(storage).read();
|
|
20
28
|
}
|
|
21
29
|
export function clearSlackInstallWorkspace(storage: WorkspaceStorage): void {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
installWorkspaceStorage(storage).remove();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function installWorkspaceStorage(storage: WorkspaceStorage) {
|
|
34
|
+
return createTypedBrowserStorage(() => storage, slackInstallWorkspaceStorageKey);
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
export function parseSlackCallbackQuery(
|