@acorex-platform/extension-project-management-simpro-client 0.0.21
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/fesm2022/acorex-platform-extension-project-management-simpro-client-complete-simpro-oauth.command-BA01rFnU.mjs +52 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client-complete-simpro-oauth.command-BA01rFnU.mjs.map +1 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client-connect-simpro.command-BPDls4Yt.mjs +271 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client-connect-simpro.command-BPDls4Yt.mjs.map +1 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client-contracts.mjs +115 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client-simpro-command-error.util-BTtAPZsK.mjs +66 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client-simpro-command-error.util-BTtAPZsK.mjs.map +1 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client.mjs +579 -0
- package/fesm2022/acorex-platform-extension-project-management-simpro-client.mjs.map +1 -0
- package/package.json +28 -0
- package/types/acorex-platform-extension-project-management-simpro-client-contracts.d.ts +260 -0
- package/types/acorex-platform-extension-project-management-simpro-client.d.ts +34 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AXP_INTEGRATION_CONNECTION_ESTABLISHED } from '@acorex-platform/framework-client/common';
|
|
2
|
+
import { AXMIntegrationConnectionManager, toSafeIntegrationConnectionDto } from '@acorex-platform/module-integration-management-client';
|
|
3
|
+
import { AXTranslationService } from '@acorex/core/translation';
|
|
4
|
+
import { A as AXESimproOAuthHttpClient, r as resolveSimproCommandError } from './acorex-platform-extension-project-management-simpro-client-simpro-command-error.util-BTtAPZsK.mjs';
|
|
5
|
+
import * as i0 from '@angular/core';
|
|
6
|
+
import { inject, Injectable } from '@angular/core';
|
|
7
|
+
|
|
8
|
+
class CompleteSimproOAuthCommand {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.http = inject(AXESimproOAuthHttpClient);
|
|
11
|
+
this.connectionManager = inject(AXMIntegrationConnectionManager);
|
|
12
|
+
this.translationService = inject(AXTranslationService);
|
|
13
|
+
this.establishedHandlers = inject(AXP_INTEGRATION_CONNECTION_ESTABLISHED, { optional: true });
|
|
14
|
+
}
|
|
15
|
+
async execute(input) {
|
|
16
|
+
try {
|
|
17
|
+
const { connectionId } = await this.http.completeOAuth({
|
|
18
|
+
state: input.state,
|
|
19
|
+
code: input.code,
|
|
20
|
+
});
|
|
21
|
+
const connection = await this.connectionManager.getByIdForCurrentUser(connectionId);
|
|
22
|
+
if (!connection) {
|
|
23
|
+
return { success: false, message: { text: 'Connection not found after Simpro OAuth callback.' } };
|
|
24
|
+
}
|
|
25
|
+
const safe = toSafeIntegrationConnectionDto(connection);
|
|
26
|
+
await this.dispatchEstablished(safe);
|
|
27
|
+
return { success: true, kind: 'object', data: safe };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const resolved = await resolveSimproCommandError(this.translationService, error, '@extensions-simpro:errors.oauth-callback-failed');
|
|
31
|
+
return { success: false, message: { text: resolved.messageKeyOrText } };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async dispatchEstablished(connection) {
|
|
35
|
+
const list = this.establishedHandlers ?? [];
|
|
36
|
+
for (const handler of list) {
|
|
37
|
+
const resolved = handler instanceof Promise ? await handler : handler;
|
|
38
|
+
if (resolved.providerId === connection.providerId) {
|
|
39
|
+
await resolved.onEstablished(connection);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CompleteSimproOAuthCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
44
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CompleteSimproOAuthCommand, providedIn: 'root' }); }
|
|
45
|
+
}
|
|
46
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CompleteSimproOAuthCommand, decorators: [{
|
|
47
|
+
type: Injectable,
|
|
48
|
+
args: [{ providedIn: 'root' }]
|
|
49
|
+
}] });
|
|
50
|
+
|
|
51
|
+
export { CompleteSimproOAuthCommand };
|
|
52
|
+
//# sourceMappingURL=acorex-platform-extension-project-management-simpro-client-complete-simpro-oauth.command-BA01rFnU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acorex-platform-extension-project-management-simpro-client-complete-simpro-oauth.command-BA01rFnU.mjs","sources":["../../../../libs/extensions/project-management/simpro/client/src/lib/features/connect/complete-simpro-oauth.command.ts"],"sourcesContent":["//#region ---- Complete Simpro OAuth callback ----\n\nimport { AXPCommand, type AXPCommandExecuteResultPromise } from '@acorex-platform/framework-shared/runtime';\nimport type { AXPRuntimeCommandResult, AXPSafeIntegrationConnectionDto } from '@acorex-platform/framework-shared/core';\nimport { AXP_INTEGRATION_CONNECTION_ESTABLISHED } from '@acorex-platform/framework-client/common';\nimport {\n AXMIntegrationConnectionManager,\n toSafeIntegrationConnectionDto,\n} from '@acorex-platform/module-integration-management-client';\nimport { AXTranslationService } from '@acorex/core/translation';\nimport { AXESimproOAuthHttpClient } from '../../api/simpro-oauth-http.client';\nimport { resolveSimproCommandError } from '../../api/simpro-command-error.util';\nimport { Injectable, inject } from '@angular/core';\n\nexport interface CompleteSimproOAuthCommandInput {\n state: string;\n code: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class CompleteSimproOAuthCommand\n implements AXPCommand<CompleteSimproOAuthCommandInput, AXPSafeIntegrationConnectionDto>\n{\n private readonly http = inject(AXESimproOAuthHttpClient);\n private readonly connectionManager = inject(AXMIntegrationConnectionManager);\n private readonly translationService = inject(AXTranslationService);\n private readonly establishedHandlers = inject(AXP_INTEGRATION_CONNECTION_ESTABLISHED, { optional: true });\n\n async execute(\n input: CompleteSimproOAuthCommandInput,\n ): AXPCommandExecuteResultPromise<AXPSafeIntegrationConnectionDto> {\n try {\n const { connectionId } = await this.http.completeOAuth({\n state: input.state,\n code: input.code,\n });\n\n const connection = await this.connectionManager.getByIdForCurrentUser(connectionId);\n if (!connection) {\n return { success: false, message: { text: 'Connection not found after Simpro OAuth callback.' } };\n }\n\n const safe = toSafeIntegrationConnectionDto(connection);\n await this.dispatchEstablished(safe);\n return { success: true, kind: 'object', data: safe };\n } catch (error: unknown) {\n const resolved = await resolveSimproCommandError(\n this.translationService,\n error,\n '@extensions-simpro:errors.oauth-callback-failed',\n );\n return { success: false, message: { text: resolved.messageKeyOrText } };\n }\n }\n\n private async dispatchEstablished(connection: AXPSafeIntegrationConnectionDto): Promise<void> {\n const list = this.establishedHandlers ?? [];\n for (const handler of list) {\n const resolved = handler instanceof Promise ? await handler : handler;\n if (resolved.providerId === connection.providerId) {\n await resolved.onEstablished(connection);\n }\n }\n }\n}\n\n//#endregion\n"],"names":[],"mappings":";;;;;;;MAoBa,0BAA0B,CAAA;AADvC,IAAA,WAAA,GAAA;AAImB,QAAA,IAAA,CAAA,IAAI,GAAG,MAAM,CAAC,wBAAwB,CAAC;AACvC,QAAA,IAAA,CAAA,iBAAiB,GAAG,MAAM,CAAC,+BAA+B,CAAC;AAC3D,QAAA,IAAA,CAAA,kBAAkB,GAAG,MAAM,CAAC,oBAAoB,CAAC;QACjD,IAAA,CAAA,mBAAmB,GAAG,MAAM,CAAC,sCAAsC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAsC1G,IAAA;IApCC,MAAM,OAAO,CACX,KAAsC,EAAA;AAEtC,QAAA,IAAI;YACF,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC;gBACrD,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;AACjB,aAAA,CAAC;YAEF,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,YAAY,CAAC;YACnF,IAAI,CAAC,UAAU,EAAE;AACf,gBAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,mDAAmD,EAAE,EAAE;YACnG;AAEA,YAAA,MAAM,IAAI,GAAG,8BAA8B,CAAC,UAAU,CAAC;AACvD,YAAA,MAAM,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC;AACpC,YAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE;QACtD;QAAE,OAAO,KAAc,EAAE;AACvB,YAAA,MAAM,QAAQ,GAAG,MAAM,yBAAyB,CAC9C,IAAI,CAAC,kBAAkB,EACvB,KAAK,EACL,iDAAiD,CAClD;AACD,YAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,gBAAgB,EAAE,EAAE;QACzE;IACF;IAEQ,MAAM,mBAAmB,CAAC,UAA2C,EAAA;AAC3E,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,mBAAmB,IAAI,EAAE;AAC3C,QAAA,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE;AAC1B,YAAA,MAAM,QAAQ,GAAG,OAAO,YAAY,OAAO,GAAG,MAAM,OAAO,GAAG,OAAO;YACrE,IAAI,QAAQ,CAAC,UAAU,KAAK,UAAU,CAAC,UAAU,EAAE;AACjD,gBAAA,MAAM,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC;YAC1C;QACF;IACF;8GA3CW,0BAA0B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAA1B,IAAA,SAAA,IAAA,CAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,0BAA0B,cADb,MAAM,EAAA,CAAA,CAAA;;2FACnB,0BAA0B,EAAA,UAAA,EAAA,CAAA;kBADtC,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;;;"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { AXP_INTEGRATION_CONNECTION_STATUS, AXP_INTEGRATION_AUTH_METHOD_TYPE } from '@acorex-platform/framework-shared/core';
|
|
2
|
+
import { AXMIntegrationConnectionManager, AXP_INTEGRATION_CREDENTIAL_MASK, toSafeIntegrationConnectionDto, isMaskedIntegrationCredential, hasConfiguredIntegrationCredentials } from '@acorex-platform/module-integration-management-client';
|
|
3
|
+
import { AXPLayoutBuilderService, resolveLayoutBuilderDialogResult } from '@acorex-platform/framework-client/layout/builder';
|
|
4
|
+
import { AXTranslationService } from '@acorex/core/translation';
|
|
5
|
+
import { A as AXESimproOAuthHttpClient, r as resolveSimproCommandError } from './acorex-platform-extension-project-management-simpro-client-simpro-command-error.util-BTtAPZsK.mjs';
|
|
6
|
+
import * as i0 from '@angular/core';
|
|
7
|
+
import { inject, Injectable } from '@angular/core';
|
|
8
|
+
import { trim, defaultTo, isEmpty } from 'lodash-es';
|
|
9
|
+
import { buildIntegrationOAuthCallbackUrl } from '@acorex-platform/module-integration-management-client/contracts';
|
|
10
|
+
|
|
11
|
+
//#region ---- Simpro build URL validation ----
|
|
12
|
+
/** Rejects common misconfiguration (OAuth callback URL pasted as build URL). */
|
|
13
|
+
function validateSimproBuildUrl(raw) {
|
|
14
|
+
const buildUrl = raw.trim().replace(/\/+$/, '');
|
|
15
|
+
if (!buildUrl) {
|
|
16
|
+
return 'Simpro build URL is required.';
|
|
17
|
+
}
|
|
18
|
+
if (buildUrl.includes('/integrations/oauth/callback')) {
|
|
19
|
+
return 'Use your Simpro site URL (e.g. https://your-build.simprosuite.com), not the OAuth callback URL.';
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const parsed = new URL(buildUrl);
|
|
23
|
+
if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1') {
|
|
24
|
+
return 'Build URL must be your Simpro cloud site, not localhost.';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return 'Build URL must be a valid URL (e.g. https://enterprise-sandbox-au.simprosuite.com).';
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
|
|
34
|
+
class ConnectSimproCommand {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.connectionManager = inject(AXMIntegrationConnectionManager);
|
|
37
|
+
this.layoutBuilder = inject(AXPLayoutBuilderService);
|
|
38
|
+
this.translationService = inject(AXTranslationService);
|
|
39
|
+
this.simproHttp = inject(AXESimproOAuthHttpClient);
|
|
40
|
+
}
|
|
41
|
+
async execute(input = {}) {
|
|
42
|
+
try {
|
|
43
|
+
const providerId = input.providerId ?? 'simpro';
|
|
44
|
+
const connectionScope = input.connectionScope ?? 'tenant';
|
|
45
|
+
const existing = await this.resolveExistingConnection(providerId, connectionScope, input);
|
|
46
|
+
const isReconfigure = !!existing;
|
|
47
|
+
const existingConfig = (existing?.config ?? {});
|
|
48
|
+
const connectionKey = existing?.connectionKey
|
|
49
|
+
?? (trim(input.connectionKey) || crypto.randomUUID());
|
|
50
|
+
const title = await this.translationService.translateAsync(isReconfigure
|
|
51
|
+
? '@extensions-simpro:integration.connect.dialog.reconfigure-title'
|
|
52
|
+
: '@extensions-simpro:integration.connect.dialog.title');
|
|
53
|
+
const form = await this.openConnectDialog({
|
|
54
|
+
title: title ?? 'Connect Simpro',
|
|
55
|
+
context: {
|
|
56
|
+
displayName: existing?.displayName ?? 'Simpro',
|
|
57
|
+
buildUrl: existing?.instanceUrl ?? '',
|
|
58
|
+
companyIdText: String(existingConfig.companyId ?? 0),
|
|
59
|
+
clientId: existingConfig.clientId ?? '',
|
|
60
|
+
clientSecret: isReconfigure ? AXP_INTEGRATION_CREDENTIAL_MASK : '',
|
|
61
|
+
},
|
|
62
|
+
existing,
|
|
63
|
+
existingConfig,
|
|
64
|
+
isReconfigure,
|
|
65
|
+
});
|
|
66
|
+
if (!form) {
|
|
67
|
+
return { success: true, kind: 'object', data: { outcome: 'cancelled' } };
|
|
68
|
+
}
|
|
69
|
+
const credentialsChanged = this.credentialsChanged(existing, existingConfig, form);
|
|
70
|
+
const needsAuthorization = this.needsOAuthAuthorization(existingConfig, credentialsChanged);
|
|
71
|
+
const config = {
|
|
72
|
+
companyId: form.companyId,
|
|
73
|
+
clientId: form.clientId,
|
|
74
|
+
clientSecret: form.clientSecret,
|
|
75
|
+
...(credentialsChanged
|
|
76
|
+
? {}
|
|
77
|
+
: {
|
|
78
|
+
accessToken: existingConfig.accessToken,
|
|
79
|
+
refreshToken: existingConfig.refreshToken,
|
|
80
|
+
tokenExpiresAt: existingConfig.tokenExpiresAt,
|
|
81
|
+
oauthTokenJson: existingConfig.oauthTokenJson,
|
|
82
|
+
authorizationUrl: existingConfig.authorizationUrl,
|
|
83
|
+
}),
|
|
84
|
+
};
|
|
85
|
+
const connection = await this.connectionManager.upsertConnection({
|
|
86
|
+
providerId,
|
|
87
|
+
authMethodType: AXP_INTEGRATION_AUTH_METHOD_TYPE.OAuth2,
|
|
88
|
+
connectionScope,
|
|
89
|
+
connectionKey,
|
|
90
|
+
displayName: defaultTo(trim(form.displayName), 'Simpro'),
|
|
91
|
+
instanceUrl: form.buildUrl,
|
|
92
|
+
externalAccountName: AXP_INTEGRATION_CREDENTIAL_MASK,
|
|
93
|
+
config: config,
|
|
94
|
+
status: needsAuthorization
|
|
95
|
+
? AXP_INTEGRATION_CONNECTION_STATUS.PendingAuthorization
|
|
96
|
+
: AXP_INTEGRATION_CONNECTION_STATUS.Connected,
|
|
97
|
+
isRevoked: false,
|
|
98
|
+
isDisabled: false,
|
|
99
|
+
});
|
|
100
|
+
if (needsAuthorization) {
|
|
101
|
+
// App-agnostic: Simpro uses a fixed registered redirect URI (ignores authorize redirect_uri).
|
|
102
|
+
const redirectUri = buildIntegrationOAuthCallbackUrl(window.location.origin, providerId);
|
|
103
|
+
const oauth = await this.simproHttp.startOAuth({
|
|
104
|
+
redirectUri,
|
|
105
|
+
connectionId: connection.id,
|
|
106
|
+
});
|
|
107
|
+
return { success: true, kind: 'object',
|
|
108
|
+
data: { outcome: 'redirect', redirectUrl: oauth.redirectUrl },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const validation = await this.simproHttp.validateOAuth({ connectionId: connection.id });
|
|
112
|
+
if (!validation.valid) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
message: { text: validation.message ?? 'Simpro connection validation failed. Re-authorize Simpro.' },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const refreshed = await this.connectionManager.getByIdForCurrentUser(connection.id);
|
|
119
|
+
return { success: true, kind: 'object',
|
|
120
|
+
data: {
|
|
121
|
+
outcome: 'connected',
|
|
122
|
+
connection: toSafeIntegrationConnectionDto(refreshed ?? connection),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const resolved = await resolveSimproCommandError(this.translationService, error, '@extensions-simpro:errors.connect-failed');
|
|
128
|
+
return { success: false, message: { text: resolved.messageKeyOrText } };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async resolveExistingConnection(providerId, connectionScope, input) {
|
|
132
|
+
if (input.connectionId) {
|
|
133
|
+
const byId = await this.connectionManager.getByIdForCurrentUser(input.connectionId);
|
|
134
|
+
if (!byId) {
|
|
135
|
+
throw new Error('Connection not found or access denied.');
|
|
136
|
+
}
|
|
137
|
+
return byId;
|
|
138
|
+
}
|
|
139
|
+
const connectionKey = trim(input.connectionKey);
|
|
140
|
+
if (!connectionKey) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return this.connectionManager.findByUniqueKey({
|
|
144
|
+
providerId,
|
|
145
|
+
connectionScope,
|
|
146
|
+
connectionKey,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
credentialsChanged(existing, existingConfig, form) {
|
|
150
|
+
if (!existing) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (trim(form.buildUrl) !== trim(existing.instanceUrl ?? '')) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (trim(form.clientId) !== trim(existingConfig.clientId ?? '')) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (!isMaskedIntegrationCredential(form.clientSecret)) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
needsOAuthAuthorization(existingConfig, credentialsChanged) {
|
|
165
|
+
if (credentialsChanged) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return isEmpty(trim(existingConfig.refreshToken ?? ''));
|
|
169
|
+
}
|
|
170
|
+
async openConnectDialog(input) {
|
|
171
|
+
const result = await this.layoutBuilder
|
|
172
|
+
.create()
|
|
173
|
+
.dialog((dialog) => {
|
|
174
|
+
dialog
|
|
175
|
+
.setTitle(input.title)
|
|
176
|
+
.setSize('md')
|
|
177
|
+
.setCloseButton(true)
|
|
178
|
+
.setContext({ ...input.context })
|
|
179
|
+
.content((flex) => {
|
|
180
|
+
flex
|
|
181
|
+
.setDirection('column')
|
|
182
|
+
.setGap('12px')
|
|
183
|
+
.customWidget('text-block-layout', {
|
|
184
|
+
content: '@extensions-simpro:integration.connect.instructions',
|
|
185
|
+
})
|
|
186
|
+
.formField('@extensions-simpro:integration.connect.fields.display-name', (field) => {
|
|
187
|
+
field.path('displayName');
|
|
188
|
+
field.textBox({
|
|
189
|
+
placeholder: '@extensions-simpro:integration.connect.placeholders.display-name',
|
|
190
|
+
});
|
|
191
|
+
})
|
|
192
|
+
.formField('@extensions-simpro:integration.auth.oauth.build-url.label', (field) => {
|
|
193
|
+
field.path('buildUrl');
|
|
194
|
+
field.textBox({
|
|
195
|
+
placeholder: '@extensions-simpro:integration.auth.oauth.build-url.placeholder',
|
|
196
|
+
validations: [{ rule: 'required' }],
|
|
197
|
+
});
|
|
198
|
+
})
|
|
199
|
+
.formField('@extensions-simpro:integration.auth.oauth.company-id.label', (field) => {
|
|
200
|
+
field.path('companyIdText');
|
|
201
|
+
field.textBox({
|
|
202
|
+
placeholder: '@extensions-simpro:integration.auth.oauth.company-id.placeholder',
|
|
203
|
+
});
|
|
204
|
+
})
|
|
205
|
+
.formField('@extensions-simpro:integration.auth.oauth.client-id.label', (field) => {
|
|
206
|
+
field.path('clientId');
|
|
207
|
+
field.textBox({
|
|
208
|
+
placeholder: '@extensions-simpro:integration.auth.oauth.client-id.placeholder',
|
|
209
|
+
validations: [{ rule: 'required' }],
|
|
210
|
+
});
|
|
211
|
+
})
|
|
212
|
+
.formField('@extensions-simpro:integration.auth.oauth.client-secret.label', (field) => {
|
|
213
|
+
field.path('clientSecret');
|
|
214
|
+
field.passwordBox({
|
|
215
|
+
placeholder: '@extensions-simpro:integration.auth.oauth.client-secret.placeholder',
|
|
216
|
+
revealToggle: true,
|
|
217
|
+
validations: input.isReconfigure ? [] : [{ rule: 'required' }],
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
})
|
|
221
|
+
.setActions((actions) => {
|
|
222
|
+
actions
|
|
223
|
+
.cancel('@general:actions.cancel.title')
|
|
224
|
+
.submit(input.isReconfigure && hasConfiguredIntegrationCredentials(input.existing)
|
|
225
|
+
? '@extensions-simpro:integration.connect.actions.save'
|
|
226
|
+
: '@extensions-simpro:integration.connect.actions.save-and-authorize');
|
|
227
|
+
})
|
|
228
|
+
.onAction(async (ref) => {
|
|
229
|
+
if (ref.action() === 'cancel') {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
const ctx = ref.context();
|
|
233
|
+
const buildUrl = trim(ctx.buildUrl).replace(/\/+$/, '');
|
|
234
|
+
const buildUrlError = validateSimproBuildUrl(buildUrl);
|
|
235
|
+
if (buildUrlError) {
|
|
236
|
+
throw new Error(buildUrlError);
|
|
237
|
+
}
|
|
238
|
+
const clientId = trim(ctx.clientId);
|
|
239
|
+
if (!clientId) {
|
|
240
|
+
throw new Error('Simpro client ID is required.');
|
|
241
|
+
}
|
|
242
|
+
let clientSecret = trim(ctx.clientSecret);
|
|
243
|
+
if (isMaskedIntegrationCredential(clientSecret) && input.existing) {
|
|
244
|
+
clientSecret = trim(input.existingConfig.clientSecret ?? '');
|
|
245
|
+
}
|
|
246
|
+
if (!clientSecret) {
|
|
247
|
+
throw new Error('Simpro client secret is required.');
|
|
248
|
+
}
|
|
249
|
+
const companyId = Number(trim(ctx.companyIdText));
|
|
250
|
+
return {
|
|
251
|
+
displayName: trim(ctx.displayName) || 'Simpro',
|
|
252
|
+
buildUrl,
|
|
253
|
+
companyId: Number.isFinite(companyId) ? companyId : 0,
|
|
254
|
+
clientId,
|
|
255
|
+
clientSecret,
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
})
|
|
259
|
+
.show();
|
|
260
|
+
return resolveLayoutBuilderDialogResult(result);
|
|
261
|
+
}
|
|
262
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ConnectSimproCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
263
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ConnectSimproCommand, providedIn: 'root' }); }
|
|
264
|
+
}
|
|
265
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ConnectSimproCommand, decorators: [{
|
|
266
|
+
type: Injectable,
|
|
267
|
+
args: [{ providedIn: 'root' }]
|
|
268
|
+
}] });
|
|
269
|
+
|
|
270
|
+
export { ConnectSimproCommand };
|
|
271
|
+
//# sourceMappingURL=acorex-platform-extension-project-management-simpro-client-connect-simpro.command-BPDls4Yt.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acorex-platform-extension-project-management-simpro-client-connect-simpro.command-BPDls4Yt.mjs","sources":["../../../../libs/extensions/project-management/simpro/client/src/lib/features/connect/simpro-build-url.util.ts","../../../../libs/extensions/project-management/simpro/client/src/lib/features/connect/connect-simpro.command.ts"],"sourcesContent":["//#region ---- Simpro build URL validation ----\n\n/** Rejects common misconfiguration (OAuth callback URL pasted as build URL). */\nexport function validateSimproBuildUrl(raw: string): string | null {\n const buildUrl = raw.trim().replace(/\\/+$/, '');\n if (!buildUrl) {\n return 'Simpro build URL is required.';\n }\n\n if (buildUrl.includes('/integrations/oauth/callback')) {\n return 'Use your Simpro site URL (e.g. https://your-build.simprosuite.com), not the OAuth callback URL.';\n }\n\n try {\n const parsed = new URL(buildUrl);\n if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1') {\n return 'Build URL must be your Simpro cloud site, not localhost.';\n }\n } catch {\n return 'Build URL must be a valid URL (e.g. https://enterprise-sandbox-au.simprosuite.com).';\n }\n\n return null;\n}\n\n//#endregion\n","//#region ---- Connect Simpro (OAuth authorization code) ----\n\nimport { AXPCommand, type AXPCommandExecuteResultPromise } from '@acorex-platform/framework-shared/runtime';\nimport {\n AXP_INTEGRATION_AUTH_METHOD_TYPE,\n AXP_INTEGRATION_CONNECTION_STATUS,\n type AXPRuntimeCommandResult,\n type AXPIntegrationConnectionScope,\n} from '@acorex-platform/framework-shared/core';\nimport {\n AXMIntegrationConnectionManager,\n AXP_INTEGRATION_CREDENTIAL_MASK,\n hasConfiguredIntegrationCredentials,\n isMaskedIntegrationCredential,\n toSafeIntegrationConnectionDto,\n type ConnectCommandResult,\n} from '@acorex-platform/module-integration-management-client';\nimport {\n AXPLayoutBuilderService,\n resolveLayoutBuilderDialogResult,\n type AXPDialogRef,\n type IActionBuilder,\n type IDialogBuilder,\n type IFlexContainerBuilder,\n type IFormFieldBuilder,\n} from '@acorex-platform/framework-client/layout/builder';\nimport { AXTranslationService } from '@acorex/core/translation';\nimport { AXESimproOAuthHttpClient } from '../../api/simpro-oauth-http.client';\nimport { resolveSimproCommandError } from '../../api/simpro-command-error.util';\nimport { Injectable, inject } from '@angular/core';\nimport { defaultTo, isEmpty, trim } from 'lodash-es';\nimport type { AXESimproConnectionConfig } from '@acorex-platform/extension-project-management-simpro-client/contracts';\nimport type { AXMIntegrationManagementIntegrationConnectionEntityModel } from '@acorex-platform/module-integration-management-client/contracts';\nimport { buildIntegrationOAuthCallbackUrl } from '@acorex-platform/module-integration-management-client/contracts';\nimport { validateSimproBuildUrl } from './simpro-build-url.util';\n\nexport interface ConnectSimproCommandInput {\n providerId?: string;\n connectionScope?: AXPIntegrationConnectionScope;\n /** When set, reconfigure / reconnect this existing connection. */\n connectionId?: string;\n /** Optional explicit key; new connections get a generated key when omitted. */\n connectionKey?: string;\n}\n\ninterface AXESimproConnectFormContext {\n displayName: string;\n buildUrl: string;\n companyIdText: string;\n clientId: string;\n clientSecret: string;\n}\n\ninterface AXESimproConnectFormResult {\n displayName: string;\n buildUrl: string;\n companyId: number;\n clientId: string;\n clientSecret: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ConnectSimproCommand implements AXPCommand<ConnectSimproCommandInput, ConnectCommandResult> {\n private readonly connectionManager = inject(AXMIntegrationConnectionManager);\n private readonly layoutBuilder = inject(AXPLayoutBuilderService);\n private readonly translationService = inject(AXTranslationService);\n private readonly simproHttp = inject(AXESimproOAuthHttpClient);\n\n async execute(input: ConnectSimproCommandInput = {}): AXPCommandExecuteResultPromise<ConnectCommandResult> {\n try {\n const providerId = input.providerId ?? 'simpro';\n const connectionScope = input.connectionScope ?? 'tenant';\n const existing = await this.resolveExistingConnection(providerId, connectionScope, input);\n const isReconfigure = !!existing;\n const existingConfig = (existing?.config ?? {}) as AXESimproConnectionConfig;\n const connectionKey =\n existing?.connectionKey\n ?? (trim(input.connectionKey) || crypto.randomUUID());\n\n const title = await this.translationService.translateAsync(\n isReconfigure\n ? '@extensions-simpro:integration.connect.dialog.reconfigure-title'\n : '@extensions-simpro:integration.connect.dialog.title',\n );\n\n const form = await this.openConnectDialog({\n title: title ?? 'Connect Simpro',\n context: {\n displayName: existing?.displayName ?? 'Simpro',\n buildUrl: existing?.instanceUrl ?? '',\n companyIdText: String(existingConfig.companyId ?? 0),\n clientId: existingConfig.clientId ?? '',\n clientSecret: isReconfigure ? AXP_INTEGRATION_CREDENTIAL_MASK : '',\n },\n existing,\n existingConfig,\n isReconfigure,\n });\n\n if (!form) {\n return { success: true, kind: 'object', data: { outcome: 'cancelled' } };\n }\n\n const credentialsChanged = this.credentialsChanged(existing, existingConfig, form);\n const needsAuthorization = this.needsOAuthAuthorization(existingConfig, credentialsChanged);\n\n const config: AXESimproConnectionConfig = {\n companyId: form.companyId,\n clientId: form.clientId,\n clientSecret: form.clientSecret,\n ...(credentialsChanged\n ? {}\n : {\n accessToken: existingConfig.accessToken,\n refreshToken: existingConfig.refreshToken,\n tokenExpiresAt: existingConfig.tokenExpiresAt,\n oauthTokenJson: existingConfig.oauthTokenJson,\n authorizationUrl: existingConfig.authorizationUrl,\n }),\n };\n\n const connection = await this.connectionManager.upsertConnection({\n providerId,\n authMethodType: AXP_INTEGRATION_AUTH_METHOD_TYPE.OAuth2,\n connectionScope,\n connectionKey,\n displayName: defaultTo(trim(form.displayName), 'Simpro'),\n instanceUrl: form.buildUrl,\n externalAccountName: AXP_INTEGRATION_CREDENTIAL_MASK,\n config: config as Record<string, unknown>,\n status: needsAuthorization\n ? AXP_INTEGRATION_CONNECTION_STATUS.PendingAuthorization\n : AXP_INTEGRATION_CONNECTION_STATUS.Connected,\n isRevoked: false,\n isDisabled: false,\n });\n\n if (needsAuthorization) {\n // App-agnostic: Simpro uses a fixed registered redirect URI (ignores authorize redirect_uri).\n const redirectUri = buildIntegrationOAuthCallbackUrl(window.location.origin, providerId);\n const oauth = await this.simproHttp.startOAuth({\n redirectUri,\n connectionId: connection.id,\n });\n return { success: true, kind: 'object',\n data: { outcome: 'redirect', redirectUrl: oauth.redirectUrl },\n };\n }\n\n const validation = await this.simproHttp.validateOAuth({ connectionId: connection.id });\n if (!validation.valid) {\n return {\n success: false,\n message: { text: validation.message ?? 'Simpro connection validation failed. Re-authorize Simpro.' },\n };\n }\n\n const refreshed = await this.connectionManager.getByIdForCurrentUser(connection.id);\n return { success: true, kind: 'object',\n data: {\n outcome: 'connected',\n connection: toSafeIntegrationConnectionDto(refreshed ?? connection),\n },\n };\n } catch (error: unknown) {\n const resolved = await resolveSimproCommandError(\n this.translationService,\n error,\n '@extensions-simpro:errors.connect-failed',\n );\n return { success: false, message: { text: resolved.messageKeyOrText } };\n }\n }\n\n private async resolveExistingConnection(\n providerId: string,\n connectionScope: AXPIntegrationConnectionScope,\n input: ConnectSimproCommandInput,\n ): Promise<AXMIntegrationManagementIntegrationConnectionEntityModel | null> {\n if (input.connectionId) {\n const byId = await this.connectionManager.getByIdForCurrentUser(input.connectionId);\n if (!byId) {\n throw new Error('Connection not found or access denied.');\n }\n return byId;\n }\n\n const connectionKey = trim(input.connectionKey);\n if (!connectionKey) {\n return null;\n }\n\n return this.connectionManager.findByUniqueKey({\n providerId,\n connectionScope,\n connectionKey,\n });\n }\n\n private credentialsChanged(\n existing: AXMIntegrationManagementIntegrationConnectionEntityModel | null,\n existingConfig: AXESimproConnectionConfig,\n form: AXESimproConnectFormResult,\n ): boolean {\n if (!existing) {\n return true;\n }\n if (trim(form.buildUrl) !== trim(existing.instanceUrl ?? '')) {\n return true;\n }\n if (trim(form.clientId) !== trim(existingConfig.clientId ?? '')) {\n return true;\n }\n if (!isMaskedIntegrationCredential(form.clientSecret)) {\n return true;\n }\n return false;\n }\n\n private needsOAuthAuthorization(\n existingConfig: AXESimproConnectionConfig,\n credentialsChanged: boolean,\n ): boolean {\n if (credentialsChanged) {\n return true;\n }\n return isEmpty(trim(existingConfig.refreshToken ?? ''));\n }\n\n private async openConnectDialog(input: {\n title: string;\n context: AXESimproConnectFormContext;\n existing: AXMIntegrationManagementIntegrationConnectionEntityModel | null;\n existingConfig: AXESimproConnectionConfig;\n isReconfigure: boolean;\n }): Promise<AXESimproConnectFormResult | null> {\n const result: unknown = await this.layoutBuilder\n .create()\n .dialog<AXESimproConnectFormResult | null>((dialog: IDialogBuilder<AXESimproConnectFormResult | null>) => {\n dialog\n .setTitle(input.title)\n .setSize('md')\n .setCloseButton(true)\n .setContext({ ...input.context })\n .content((flex: IFlexContainerBuilder) => {\n flex\n .setDirection('column')\n .setGap('12px')\n .customWidget('text-block-layout', {\n content: '@extensions-simpro:integration.connect.instructions',\n })\n .formField('@extensions-simpro:integration.connect.fields.display-name', (field: IFormFieldBuilder) => {\n field.path('displayName');\n field.textBox({\n placeholder: '@extensions-simpro:integration.connect.placeholders.display-name',\n });\n })\n .formField('@extensions-simpro:integration.auth.oauth.build-url.label', (field) => {\n field.path('buildUrl');\n field.textBox({\n placeholder: '@extensions-simpro:integration.auth.oauth.build-url.placeholder',\n validations: [{ rule: 'required' }],\n });\n })\n .formField('@extensions-simpro:integration.auth.oauth.company-id.label', (field) => {\n field.path('companyIdText');\n field.textBox({\n placeholder: '@extensions-simpro:integration.auth.oauth.company-id.placeholder',\n });\n })\n .formField('@extensions-simpro:integration.auth.oauth.client-id.label', (field) => {\n field.path('clientId');\n field.textBox({\n placeholder: '@extensions-simpro:integration.auth.oauth.client-id.placeholder',\n validations: [{ rule: 'required' }],\n });\n })\n .formField('@extensions-simpro:integration.auth.oauth.client-secret.label', (field) => {\n field.path('clientSecret');\n field.passwordBox({\n placeholder: '@extensions-simpro:integration.auth.oauth.client-secret.placeholder',\n revealToggle: true,\n validations: input.isReconfigure ? [] : [{ rule: 'required' }],\n });\n });\n })\n .setActions((actions: IActionBuilder) => {\n actions\n .cancel('@general:actions.cancel.title')\n .submit(\n input.isReconfigure && hasConfiguredIntegrationCredentials(input.existing)\n ? '@extensions-simpro:integration.connect.actions.save'\n : '@extensions-simpro:integration.connect.actions.save-and-authorize',\n );\n })\n .onAction(async (ref: AXPDialogRef) => {\n if (ref.action() === 'cancel') {\n return null;\n }\n\n const ctx = ref.context() as AXESimproConnectFormContext;\n const buildUrl = trim(ctx.buildUrl).replace(/\\/+$/, '');\n const buildUrlError = validateSimproBuildUrl(buildUrl);\n if (buildUrlError) {\n throw new Error(buildUrlError);\n }\n\n const clientId = trim(ctx.clientId);\n if (!clientId) {\n throw new Error('Simpro client ID is required.');\n }\n\n let clientSecret = trim(ctx.clientSecret);\n if (isMaskedIntegrationCredential(clientSecret) && input.existing) {\n clientSecret = trim(input.existingConfig.clientSecret ?? '');\n }\n if (!clientSecret) {\n throw new Error('Simpro client secret is required.');\n }\n\n const companyId = Number(trim(ctx.companyIdText));\n return {\n displayName: trim(ctx.displayName) || 'Simpro',\n buildUrl,\n companyId: Number.isFinite(companyId) ? companyId : 0,\n clientId,\n clientSecret,\n };\n });\n })\n .show();\n\n return resolveLayoutBuilderDialogResult<AXESimproConnectFormResult>(result);\n }\n}\n\n//#endregion\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export { AXE_PM_PROJECT_SYNC_STRATEGIES as AXE_SIMPRO_PROJECT_SYNC_STRATEGIES } from '@acorex-platform/extension-project-management-core-contracts';
|
|
2
|
+
|
|
3
|
+
//#region ---- Module Config ----
|
|
4
|
+
const config = {
|
|
5
|
+
i18n: 'extensions-simpro',
|
|
6
|
+
};
|
|
7
|
+
const MODULE_NAME = 'SimproExtension';
|
|
8
|
+
const RootConfig = {
|
|
9
|
+
config,
|
|
10
|
+
module: {
|
|
11
|
+
name: MODULE_NAME,
|
|
12
|
+
title: `@${config.i18n}:integration.display-name`,
|
|
13
|
+
icon: 'fa-light fa-briefcase',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
|
|
18
|
+
//#region ---- Command Keys ----
|
|
19
|
+
const AXESimproCommandKeys = {
|
|
20
|
+
ConnectionConnect: 'simpro-extension.Connection:Connect',
|
|
21
|
+
ConnectionCompleteOAuth: 'simpro-extension.Connection:CompleteOAuth',
|
|
22
|
+
ConnectionLifecycle: 'simpro-extension.Connection:Lifecycle',
|
|
23
|
+
OAuthStart: 'simpro-extension.OAuth:Start',
|
|
24
|
+
OAuthComplete: 'simpro-extension.OAuth:Complete',
|
|
25
|
+
OAuthValidate: 'simpro-extension.OAuth:Validate',
|
|
26
|
+
ProjectImportFromSimpro: 'simpro-extension.Project:ImportFromSimpro',
|
|
27
|
+
CustomerImportFromSimpro: 'simpro-extension.Customer:ImportFromSimpro',
|
|
28
|
+
ProjectSync: 'simpro-extension.Project:Sync',
|
|
29
|
+
JobsPull: 'simpro-extension.Jobs:Pull',
|
|
30
|
+
JobsRefresh: 'simpro-extension.Jobs:Refresh',
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
|
|
34
|
+
//#region ---- Menu Keys ----
|
|
35
|
+
const AXESimproMenuKeys = {
|
|
36
|
+
SimproExtensionConfig: 'simpro-extension:Menu:Configuration',
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
|
|
40
|
+
//#region ---- Permission Keys ----
|
|
41
|
+
/**
|
|
42
|
+
* Administration capabilities used by this extension.
|
|
43
|
+
* Domain capability keys for this module. Root groups live in `AXPCommonPermissionGroupKeys` (common contracts).
|
|
44
|
+
*/
|
|
45
|
+
const AXPAdministrationPermissionKeys = {
|
|
46
|
+
Access: 'Administration:Permission:Access',
|
|
47
|
+
MaintainUsersAndRoles: 'Administration:Permission:MaintainUsersAndRoles',
|
|
48
|
+
ManageSessions: 'Administration:Permission:ManageSessions',
|
|
49
|
+
MaintainPolicies: 'Administration:Permission:MaintainPolicies',
|
|
50
|
+
ViewIntegrations: 'Administration:Permission:ViewIntegrations',
|
|
51
|
+
MaintainConnections: 'Administration:Permission:MaintainConnections',
|
|
52
|
+
ManageIntegrationActions: 'Administration:Permission:ManageIntegrationActions',
|
|
53
|
+
MaintainWorkflows: 'Administration:Permission:MaintainWorkflows',
|
|
54
|
+
ManageWorkflowActions: 'Administration:Permission:ManageWorkflowActions',
|
|
55
|
+
ViewRules: 'Administration:Permission:ViewRules',
|
|
56
|
+
MaintainRules: 'Administration:Permission:MaintainRules',
|
|
57
|
+
ManageRuleActions: 'Administration:Permission:ManageRuleActions',
|
|
58
|
+
MaintainAi: 'Administration:Permission:MaintainAi',
|
|
59
|
+
UseAiAssist: 'Administration:Permission:UseAiAssist',
|
|
60
|
+
MaintainDashboards: 'Administration:Permission:MaintainDashboards',
|
|
61
|
+
ViewJobs: 'Administration:Permission:ViewJobs',
|
|
62
|
+
ManageJobActions: 'Administration:Permission:ManageJobActions',
|
|
63
|
+
ViewObservability: 'Administration:Permission:ViewObservability',
|
|
64
|
+
MaintainObservability: 'Administration:Permission:MaintainObservability',
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* @deprecated Prefer {@link AXPAdministrationPermissionKeys}.
|
|
68
|
+
* Legacy nested shape kept so existing imports compile during migration.
|
|
69
|
+
*/
|
|
70
|
+
const AXESimproPermissions = {
|
|
71
|
+
SimproExtension: {
|
|
72
|
+
Management: AXPAdministrationPermissionKeys.MaintainConnections,
|
|
73
|
+
Catalog: {
|
|
74
|
+
View: AXPAdministrationPermissionKeys.ViewIntegrations,
|
|
75
|
+
},
|
|
76
|
+
ProjectImport: {
|
|
77
|
+
Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,
|
|
78
|
+
},
|
|
79
|
+
CustomerImport: {
|
|
80
|
+
Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,
|
|
81
|
+
},
|
|
82
|
+
ProjectSync: {
|
|
83
|
+
Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
|
|
89
|
+
//#region ---- Simpro tenant connection config ----
|
|
90
|
+
//#endregion
|
|
91
|
+
|
|
92
|
+
//#region ---- Simpro OAuth API (client ↔ server) ----
|
|
93
|
+
//#endregion
|
|
94
|
+
|
|
95
|
+
//#region ---- Remote Simpro DTOs ----
|
|
96
|
+
//#endregion
|
|
97
|
+
|
|
98
|
+
//#region ----- Simpro error codes -----
|
|
99
|
+
const AXE_SIMPRO_ERROR_CODES = {
|
|
100
|
+
COMPANY_ID_ZERO_MULTI: 'SIMPRO_COMPANY_ID_ZERO_MULTI',
|
|
101
|
+
COMPANY_ID_INVALID: 'SIMPRO_COMPANY_ID_INVALID',
|
|
102
|
+
};
|
|
103
|
+
/** Client i18n keys for known Simpro error codes. */
|
|
104
|
+
const AXE_SIMPRO_ERROR_I18N_KEYS = {
|
|
105
|
+
[AXE_SIMPRO_ERROR_CODES.COMPANY_ID_ZERO_MULTI]: '@extensions-simpro:errors.company-id-zero-multi',
|
|
106
|
+
[AXE_SIMPRO_ERROR_CODES.COMPANY_ID_INVALID]: '@extensions-simpro:errors.company-id-invalid',
|
|
107
|
+
};
|
|
108
|
+
//#endregion
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generated bundle index. Do not edit.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
export { AXESimproCommandKeys, AXESimproMenuKeys, AXESimproPermissions, AXE_SIMPRO_ERROR_CODES, AXE_SIMPRO_ERROR_I18N_KEYS, AXPAdministrationPermissionKeys, RootConfig };
|
|
115
|
+
//# sourceMappingURL=acorex-platform-extension-project-management-simpro-client-contracts.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acorex-platform-extension-project-management-simpro-client-contracts.mjs","sources":["../../../../libs/extensions/project-management/simpro/contracts/src/root-config.ts","../../../../libs/extensions/project-management/simpro/contracts/src/commands.keys.ts","../../../../libs/extensions/project-management/simpro/contracts/src/menu.keys.ts","../../../../libs/extensions/project-management/simpro/contracts/src/permission.keys.ts","../../../../libs/extensions/project-management/simpro/contracts/src/connection.types.ts","../../../../libs/extensions/project-management/simpro/contracts/src/oauth.api.types.ts","../../../../libs/extensions/project-management/simpro/contracts/src/project-sync.api.types.ts","../../../../libs/extensions/project-management/simpro/contracts/src/simpro.errors.ts","../../../../libs/extensions/project-management/simpro/contracts/src/acorex-platform-extension-project-management-simpro-client-contracts.ts"],"sourcesContent":["//#region ---- Module Config ----\n\nconst config = {\n i18n: 'extensions-simpro',\n};\n\nconst MODULE_NAME = 'SimproExtension';\n\nexport const RootConfig = {\n config,\n module: {\n name: MODULE_NAME,\n title: `@${config.i18n}:integration.display-name`,\n icon: 'fa-light fa-briefcase',\n },\n};\n\n//#endregion\n","//#region ---- Command Keys ----\n\nexport const AXESimproCommandKeys = {\n ConnectionConnect: 'simpro-extension.Connection:Connect',\n ConnectionCompleteOAuth: 'simpro-extension.Connection:CompleteOAuth',\n ConnectionLifecycle: 'simpro-extension.Connection:Lifecycle',\n OAuthStart: 'simpro-extension.OAuth:Start',\n OAuthComplete: 'simpro-extension.OAuth:Complete',\n OAuthValidate: 'simpro-extension.OAuth:Validate',\n ProjectImportFromSimpro: 'simpro-extension.Project:ImportFromSimpro',\n CustomerImportFromSimpro: 'simpro-extension.Customer:ImportFromSimpro',\n ProjectSync: 'simpro-extension.Project:Sync',\n JobsPull: 'simpro-extension.Jobs:Pull',\n JobsRefresh: 'simpro-extension.Jobs:Refresh',\n} as const;\n\n//#endregion\n","//#region ---- Menu Keys ----\n\nexport const AXESimproMenuKeys = {\n SimproExtensionConfig: 'simpro-extension:Menu:Configuration',\n} as const;\n\n//#endregion\n","//#region ---- Permission Keys ----\n\n/**\n * Administration capabilities used by this extension.\n * Domain capability keys for this module. Root groups live in `AXPCommonPermissionGroupKeys` (common contracts).\n */\nexport const AXPAdministrationPermissionKeys = {\n Access: 'Administration:Permission:Access',\n MaintainUsersAndRoles: 'Administration:Permission:MaintainUsersAndRoles',\n ManageSessions: 'Administration:Permission:ManageSessions',\n MaintainPolicies: 'Administration:Permission:MaintainPolicies',\n ViewIntegrations: 'Administration:Permission:ViewIntegrations',\n MaintainConnections: 'Administration:Permission:MaintainConnections',\n ManageIntegrationActions: 'Administration:Permission:ManageIntegrationActions',\n MaintainWorkflows: 'Administration:Permission:MaintainWorkflows',\n ManageWorkflowActions: 'Administration:Permission:ManageWorkflowActions',\n ViewRules: 'Administration:Permission:ViewRules',\n MaintainRules: 'Administration:Permission:MaintainRules',\n ManageRuleActions: 'Administration:Permission:ManageRuleActions',\n MaintainAi: 'Administration:Permission:MaintainAi',\n UseAiAssist: 'Administration:Permission:UseAiAssist',\n MaintainDashboards: 'Administration:Permission:MaintainDashboards',\n ViewJobs: 'Administration:Permission:ViewJobs',\n ManageJobActions: 'Administration:Permission:ManageJobActions',\n ViewObservability: 'Administration:Permission:ViewObservability',\n MaintainObservability: 'Administration:Permission:MaintainObservability',\n} as const;\n\n/**\n * @deprecated Prefer {@link AXPAdministrationPermissionKeys}.\n * Legacy nested shape kept so existing imports compile during migration.\n */\nexport const AXESimproPermissions = {\n SimproExtension: {\n Management: AXPAdministrationPermissionKeys.MaintainConnections,\n Catalog: {\n View: AXPAdministrationPermissionKeys.ViewIntegrations,\n },\n ProjectImport: {\n Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,\n },\n CustomerImport: {\n Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,\n },\n ProjectSync: {\n Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,\n },\n },\n} as const;\n\n//#endregion\n","//#region ---- Simpro tenant connection config ----\n\n/** Stored on IntegrationManagement connection `config` for tenant-scoped Simpro (cloud-only). */\nexport interface AXESimproConnectionConfig {\n /** Simpro multi-company ID (0 for single-company builds). */\n companyId?: number;\n /** OAuth2 client ID from the Simpro application key file. */\n clientId?: string;\n /** OAuth2 client secret from the Simpro application key file. */\n clientSecret?: string;\n /** Optional override when it differs from `{buildUrl}/oauth2/login`. */\n authorizationUrl?: string;\n /** Current bearer token (authorization-code flow). */\n accessToken?: string;\n /** Refresh token from Simpro token response. */\n refreshToken?: string;\n /** ISO timestamp when `accessToken` expires. */\n tokenExpiresAt?: string;\n /** Full Simpro token JSON response (mirrors legacy SimproJson). */\n oauthTokenJson?: string;\n /** Background job pull sync state (server-managed). */\n jobsSync?: AXESimproJobsSyncState;\n}\n\n/** Simpro job pull sync state stored on the tenant connection `config.jobsSync`. */\nexport interface AXESimproJobsSyncState {\n /** ISO timestamp — last successful job pull (incremental sync watermark). */\n lastSyncAt?: string;\n /** ISO timestamp — last full snapshot reconcile (deactivation pass). */\n lastFullReconcileAt?: string;\n}\n\n//#endregion\n","//#region ---- Simpro OAuth API (client ↔ server) ----\n\nexport interface AXESimproOAuthStartRequest {\n redirectUri: string;\n connectionId?: string;\n}\n\nexport interface AXESimproOAuthStartResponse {\n redirectUrl: string;\n state: string;\n}\n\nexport interface AXESimproOAuthCompleteRequest {\n state: string;\n code: string;\n}\n\nexport interface AXESimproOAuthCompleteResponse {\n connectionId: string;\n}\n\nexport interface AXESimproOAuthValidateRequest {\n connectionId?: string;\n}\n\nexport interface AXESimproOAuthValidateResponse {\n valid: boolean;\n message?: string;\n}\n\n//#endregion\n","//#region ---- Remote Simpro DTOs ----\n\nimport type { AXESimproProjectSyncStrategy } from './sync.types';\n\nexport interface AXESimproRemotePartyRef {\n ID?: number;\n Name?: string | null;\n GivenName?: string | null;\n CompanyName?: string | null;\n}\n\nexport interface AXESimproRemoteAddress {\n Address?: string | null;\n City?: string | null;\n State?: string | null;\n PostalCode?: string | null;\n}\n\nexport interface AXESimproRemoteJob {\n ID: number;\n Name?: string | null;\n Description?: string | null;\n OrderNo?: string | null;\n RequestNo?: string | null;\n Type?: string | null;\n Stage?: string | { Name?: string } | null;\n DateIssued?: string | null;\n DueDate?: string | null;\n DueTime?: string | null;\n DateModified?: string | null;\n Status?: { ID?: number; Name?: string } | string | null;\n Customer?: AXESimproRemotePartyRef | string | null;\n Site?: AXESimproRemotePartyRef | string | null;\n}\n\nexport interface AXESimproRemoteCustomer {\n ID: number;\n GivenName?: string | null;\n FamilyName?: string | null;\n Title?: string | null;\n Phone?: string | null;\n Email?: string | null;\n EIN?: string | null;\n CompanyName?: string | null;\n Address?: AXESimproRemoteAddress | null;\n BillingAddress?: AXESimproRemoteAddress | null;\n}\n\nexport interface AXESimproRemoteSitePrimaryContact {\n CellPhone?: string | null;\n Email?: string | null;\n Contact?: { ID?: number } | null;\n}\n\nexport interface AXESimproRemoteSite {\n ID: number;\n Name?: string | null;\n Address?: AXESimproRemoteAddress | null;\n PrimaryContact?: AXESimproRemoteSitePrimaryContact | null;\n}\n\n//#endregion\n\n//#region ---- API request / response ----\n\nexport interface AXESimproPullJobsRequest {\n /** When set, pulls for this Simpro connection (required for multi-connection schedules). */\n connectionId?: string;\n /** Maximum Simpro jobs to pull into the cache (default 250, max 1000). */\n maxJobs?: number;\n /**\n * When true, runs a full snapshot (no incremental filter) and reconciles deactivations.\n * When false (default for schedules), pulls jobs modified since the last successful sync.\n */\n force?: boolean;\n}\n\nexport interface AXESimproPullJobsResponse {\n upserted: number;\n deactivated: number;\n /** True when incremental pull found no modified jobs in Simpro. */\n skipped?: boolean;\n /** Whether this run used incremental or full snapshot mode. */\n syncMode?: 'incremental' | 'full';\n /** Per data-type upsert counts (jobs / customers / sites). */\n byResourceType?: {\n job?: { upserted: number; deactivated: number };\n customer?: { upserted: number; deactivated: number };\n site?: { upserted: number; deactivated: number };\n };\n}\n\nexport interface AXESimproImportJobRequest {\n remoteRecordId: string;\n projectTypeId: string;\n syncStrategy: AXESimproProjectSyncStrategy;\n}\n\nexport interface AXESimproImportJobResponse {\n platformProjectId: string;\n created: boolean;\n}\n\nexport interface AXESimproImportCustomerRequest {\n remoteRecordId: string;\n}\n\nexport interface AXESimproImportCustomerResponse {\n platformCustomerId: string;\n created: boolean;\n}\n\nexport interface AXESimproSyncPlatformProjectRequest {\n platformProjectId: string;\n}\n\nexport interface AXESimproConnectionLifecycleRequest {\n connectionId: string;\n action: 'connected' | 'disconnected' | 'deleted' | 'enabled' | 'disabled';\n}\n\nexport interface AXESimproConnectionLifecycleResponse {\n ok: true;\n}\n\n//#endregion\n","//#region ----- Simpro error codes -----\n\nexport const AXE_SIMPRO_ERROR_CODES = {\n COMPANY_ID_ZERO_MULTI: 'SIMPRO_COMPANY_ID_ZERO_MULTI',\n COMPANY_ID_INVALID: 'SIMPRO_COMPANY_ID_INVALID',\n} as const;\n\nexport type AXESimproErrorCode = (typeof AXE_SIMPRO_ERROR_CODES)[keyof typeof AXE_SIMPRO_ERROR_CODES];\n\n/** Client i18n keys for known Simpro error codes. */\nexport const AXE_SIMPRO_ERROR_I18N_KEYS: Record<AXESimproErrorCode, string> = {\n [AXE_SIMPRO_ERROR_CODES.COMPANY_ID_ZERO_MULTI]: '@extensions-simpro:errors.company-id-zero-multi',\n [AXE_SIMPRO_ERROR_CODES.COMPANY_ID_INVALID]: '@extensions-simpro:errors.company-id-invalid',\n};\n\n//#endregion\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAAA;AAEA,MAAM,MAAM,GAAG;AACb,IAAA,IAAI,EAAE,mBAAmB;CAC1B;AAED,MAAM,WAAW,GAAG,iBAAiB;AAE9B,MAAM,UAAU,GAAG;IACxB,MAAM;AACN,IAAA,MAAM,EAAE;AACN,QAAA,IAAI,EAAE,WAAW;AACjB,QAAA,KAAK,EAAE,CAAA,CAAA,EAAI,MAAM,CAAC,IAAI,CAAA,yBAAA,CAA2B;AACjD,QAAA,IAAI,EAAE,uBAAuB;AAC9B,KAAA;;AAGH;;ACjBA;AAEO,MAAM,oBAAoB,GAAG;AAClC,IAAA,iBAAiB,EAAE,qCAAqC;AACxD,IAAA,uBAAuB,EAAE,2CAA2C;AACpE,IAAA,mBAAmB,EAAE,uCAAuC;AAC5D,IAAA,UAAU,EAAE,8BAA8B;AAC1C,IAAA,aAAa,EAAE,iCAAiC;AAChD,IAAA,aAAa,EAAE,iCAAiC;AAChD,IAAA,uBAAuB,EAAE,2CAA2C;AACpE,IAAA,wBAAwB,EAAE,4CAA4C;AACtE,IAAA,WAAW,EAAE,+BAA+B;AAC5C,IAAA,QAAQ,EAAE,4BAA4B;AACtC,IAAA,WAAW,EAAE,+BAA+B;;AAG9C;;AChBA;AAEO,MAAM,iBAAiB,GAAG;AAC/B,IAAA,qBAAqB,EAAE,qCAAqC;;AAG9D;;ACNA;AAEA;;;AAGG;AACI,MAAM,+BAA+B,GAAG;AAC7C,IAAA,MAAM,EAAE,kCAAkC;AAC1C,IAAA,qBAAqB,EAAE,iDAAiD;AACxE,IAAA,cAAc,EAAE,0CAA0C;AAC1D,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,mBAAmB,EAAE,+CAA+C;AACpE,IAAA,wBAAwB,EAAE,oDAAoD;AAC9E,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,qBAAqB,EAAE,iDAAiD;AACxE,IAAA,SAAS,EAAE,qCAAqC;AAChD,IAAA,aAAa,EAAE,yCAAyC;AACxD,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,UAAU,EAAE,sCAAsC;AAClD,IAAA,WAAW,EAAE,uCAAuC;AACpD,IAAA,kBAAkB,EAAE,8CAA8C;AAClE,IAAA,QAAQ,EAAE,oCAAoC;AAC9C,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,qBAAqB,EAAE,iDAAiD;;AAG1E;;;AAGG;AACI,MAAM,oBAAoB,GAAG;AAClC,IAAA,eAAe,EAAE;QACf,UAAU,EAAE,+BAA+B,CAAC,mBAAmB;AAC/D,QAAA,OAAO,EAAE;YACP,IAAI,EAAE,+BAA+B,CAAC,gBAAgB;AACvD,SAAA;AACD,QAAA,aAAa,EAAE;YACb,OAAO,EAAE,+BAA+B,CAAC,wBAAwB;AAClE,SAAA;AACD,QAAA,cAAc,EAAE;YACd,OAAO,EAAE,+BAA+B,CAAC,wBAAwB;AAClE,SAAA;AACD,QAAA,WAAW,EAAE;YACX,OAAO,EAAE,+BAA+B,CAAC,wBAAwB;AAClE,SAAA;AACF,KAAA;;AAGH;;AClDA;AAgCA;;AChCA;AA8BA;;AC9BA;AA6HA;;AC7HA;AAEO,MAAM,sBAAsB,GAAG;AACpC,IAAA,qBAAqB,EAAE,8BAA8B;AACrD,IAAA,kBAAkB,EAAE,2BAA2B;;AAKjD;AACO,MAAM,0BAA0B,GAAuC;AAC5E,IAAA,CAAC,sBAAsB,CAAC,qBAAqB,GAAG,iDAAiD;AACjG,IAAA,CAAC,sBAAsB,CAAC,kBAAkB,GAAG,8CAA8C;;AAG7F;;ACfA;;AAEG;;;;"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { runtimeCommandPayloadData } from '@acorex-platform/framework-shared/core';
|
|
2
|
+
import { throwIfCommandFailed, extractRequestErrorInfo } from '@acorex-platform/framework-client/common';
|
|
3
|
+
import { AXPCommandService } from '@acorex-platform/framework-client/runtime';
|
|
4
|
+
import { AXESimproCommandKeys, AXE_SIMPRO_ERROR_I18N_KEYS } from '@acorex-platform/extension-project-management-simpro-client/contracts';
|
|
5
|
+
import * as i0 from '@angular/core';
|
|
6
|
+
import { inject, Injectable } from '@angular/core';
|
|
7
|
+
|
|
8
|
+
class AXESimproOAuthHttpClient {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.commandService = inject(AXPCommandService);
|
|
11
|
+
}
|
|
12
|
+
startOAuth(body) {
|
|
13
|
+
return this.runCommand(AXESimproCommandKeys.OAuthStart, body);
|
|
14
|
+
}
|
|
15
|
+
completeOAuth(body) {
|
|
16
|
+
return this.runCommand(AXESimproCommandKeys.OAuthComplete, body);
|
|
17
|
+
}
|
|
18
|
+
validateOAuth(body = {}) {
|
|
19
|
+
return this.runCommand(AXESimproCommandKeys.OAuthValidate, body);
|
|
20
|
+
}
|
|
21
|
+
async runCommand(key, input) {
|
|
22
|
+
const result = await this.commandService.execute(key, input);
|
|
23
|
+
throwIfCommandFailed(result);
|
|
24
|
+
const data = runtimeCommandPayloadData(result);
|
|
25
|
+
if (data == null) {
|
|
26
|
+
throw new Error(`Simpro OAuth command failed: ${key}`);
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESimproOAuthHttpClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
31
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESimproOAuthHttpClient, providedIn: 'root' }); }
|
|
32
|
+
}
|
|
33
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESimproOAuthHttpClient, decorators: [{
|
|
34
|
+
type: Injectable,
|
|
35
|
+
args: [{ providedIn: 'root' }]
|
|
36
|
+
}] });
|
|
37
|
+
|
|
38
|
+
//#region ----- Simpro command error resolution -----
|
|
39
|
+
async function resolveSimproCommandError(translationService, error, fallbackKeyOrText) {
|
|
40
|
+
const info = extractRequestErrorInfo(error);
|
|
41
|
+
if (info?.code) {
|
|
42
|
+
const messageKey = AXE_SIMPRO_ERROR_I18N_KEYS[info.code];
|
|
43
|
+
if (messageKey) {
|
|
44
|
+
const displayText = await translationService.translateAsync(messageKey);
|
|
45
|
+
return { messageKeyOrText: messageKey, displayText };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const raw = (typeof info?.message === 'string' && info.message.trim() ? info.message : undefined) ??
|
|
49
|
+
(error instanceof Error && error.message.trim() ? error.message : undefined);
|
|
50
|
+
if (raw?.startsWith('@')) {
|
|
51
|
+
const displayText = await translationService.translateAsync(raw);
|
|
52
|
+
return { messageKeyOrText: raw, displayText };
|
|
53
|
+
}
|
|
54
|
+
if (raw) {
|
|
55
|
+
return { messageKeyOrText: raw, displayText: raw };
|
|
56
|
+
}
|
|
57
|
+
if (fallbackKeyOrText.startsWith('@')) {
|
|
58
|
+
const displayText = await translationService.translateAsync(fallbackKeyOrText);
|
|
59
|
+
return { messageKeyOrText: fallbackKeyOrText, displayText };
|
|
60
|
+
}
|
|
61
|
+
return { messageKeyOrText: fallbackKeyOrText, displayText: fallbackKeyOrText };
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
|
|
65
|
+
export { AXESimproOAuthHttpClient as A, resolveSimproCommandError as r };
|
|
66
|
+
//# sourceMappingURL=acorex-platform-extension-project-management-simpro-client-simpro-command-error.util-BTtAPZsK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acorex-platform-extension-project-management-simpro-client-simpro-command-error.util-BTtAPZsK.mjs","sources":["../../../../libs/extensions/project-management/simpro/client/src/lib/api/simpro-oauth-http.client.ts","../../../../libs/extensions/project-management/simpro/client/src/lib/api/simpro-command-error.util.ts"],"sourcesContent":["import { runtimeCommandPayloadData } from '@acorex-platform/framework-shared/core';\nimport { throwIfCommandFailed } from '@acorex-platform/framework-client/common';\nimport { AXPCommandService } from '@acorex-platform/framework-client/runtime';\nimport {\n AXESimproCommandKeys,\n type AXESimproOAuthCompleteRequest,\n type AXESimproOAuthCompleteResponse,\n type AXESimproOAuthStartRequest,\n type AXESimproOAuthStartResponse,\n type AXESimproOAuthValidateRequest,\n type AXESimproOAuthValidateResponse,\n} from '@acorex-platform/extension-project-management-simpro-client/contracts';\nimport { inject, Injectable } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nexport class AXESimproOAuthHttpClient {\n private readonly commandService = inject(AXPCommandService);\n\n startOAuth(body: AXESimproOAuthStartRequest): Promise<AXESimproOAuthStartResponse> {\n return this.runCommand(AXESimproCommandKeys.OAuthStart, body);\n }\n\n completeOAuth(body: AXESimproOAuthCompleteRequest): Promise<AXESimproOAuthCompleteResponse> {\n return this.runCommand(AXESimproCommandKeys.OAuthComplete, body);\n }\n\n validateOAuth(body: AXESimproOAuthValidateRequest = {}): Promise<AXESimproOAuthValidateResponse> {\n return this.runCommand(AXESimproCommandKeys.OAuthValidate, body);\n }\n\n private async runCommand<TInput, TResult>(key: string, input: TInput): Promise<TResult> {\n const result = await this.commandService.execute<TInput, TResult>(key, input);\n throwIfCommandFailed(result);\n const data = runtimeCommandPayloadData(result);\n if (data == null) {\n throw new Error(`Simpro OAuth command failed: ${key}`);\n }\n return data;\n }\n}\n","//#region ----- Simpro command error resolution -----\n\nimport { extractRequestErrorInfo } from '@acorex-platform/framework-client/common';\nimport { AXE_SIMPRO_ERROR_I18N_KEYS } from '@acorex-platform/extension-project-management-simpro-client/contracts';\nimport { AXTranslationService } from '@acorex/core/translation';\n\nexport async function resolveSimproCommandError(\n translationService: AXTranslationService,\n error: unknown,\n fallbackKeyOrText: string,\n): Promise<{ messageKeyOrText: string; displayText: string }> {\n const info = extractRequestErrorInfo(error);\n if (info?.code) {\n const messageKey = AXE_SIMPRO_ERROR_I18N_KEYS[info.code as keyof typeof AXE_SIMPRO_ERROR_I18N_KEYS];\n if (messageKey) {\n const displayText = await translationService.translateAsync(messageKey);\n return { messageKeyOrText: messageKey, displayText };\n }\n }\n\n const raw =\n (typeof info?.message === 'string' && info.message.trim() ? info.message : undefined) ??\n (error instanceof Error && error.message.trim() ? error.message : undefined);\n\n if (raw?.startsWith('@')) {\n const displayText = await translationService.translateAsync(raw);\n return { messageKeyOrText: raw, displayText };\n }\n\n if (raw) {\n return { messageKeyOrText: raw, displayText: raw };\n }\n\n if (fallbackKeyOrText.startsWith('@')) {\n const displayText = await translationService.translateAsync(fallbackKeyOrText);\n return { messageKeyOrText: fallbackKeyOrText, displayText };\n }\n\n return { messageKeyOrText: fallbackKeyOrText, displayText: fallbackKeyOrText };\n}\n\n//#endregion\n"],"names":[],"mappings":"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"}
|