@vida-global/apps-tools 1.0.1
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/index.js +23 -0
- package/lib/actionHandlers/appFunctionHandler.js +7 -0
- package/lib/actionHandlers/appHooksHandler.js +7 -0
- package/lib/actionHandlers/index.js +4 -0
- package/lib/appResponses/appFunctionResponse.js +23 -0
- package/lib/appResponses/appHookResponse.js +21 -0
- package/lib/appResponses/calendarAppResponse.js +6 -0
- package/lib/appResponses/index.js +9 -0
- package/lib/apps/appManager/abstractAppManager.js +70 -0
- package/lib/apps/appManager/appServerAppManager.js +237 -0
- package/lib/apps/appManager/vaderApiClient.js +81 -0
- package/lib/apps/appManager/vidaLiveAppManager.js +187 -0
- package/lib/apps/vidaApp.js +345 -0
- package/lib/errors/illegalAppInvocationError.js +8 -0
- package/lib/openApi/baseApiSpec.js +10 -0
- package/lib/openApi/index.js +150 -0
- package/lib/providers/provider.js +141 -0
- package/lib/providers/providerManager.js +73 -0
- package/lib/providers/providers/auth/generic/index.js +60 -0
- package/lib/providers/providers/auth/google/index.js +251 -0
- package/lib/providers/providers/auth/hubspot/index.js +313 -0
- package/lib/providers/providers/auth/mcp/index.js +99 -0
- package/lib/providers/providers/auth/outlook/index.js +257 -0
- package/lib/providers/providers/auth/squareUp/index.js +111 -0
- package/lib/providers/providers/mock.js +51 -0
- package/lib/server/appController.js +112 -0
- package/lib/server/server.js +73 -0
- package/lib/userContext.js +218 -0
- package/package.json +33 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const {BaseProvider, BaseProviderConfigurator } = require('../../../provider');
|
|
2
|
+
const {logger} = require('@vida-global/core');
|
|
3
|
+
|
|
4
|
+
class MCPAuthConfigurator extends BaseProviderConfigurator {
|
|
5
|
+
async initialize() {
|
|
6
|
+
logger.info('MCP Auth Configurator initialized');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class MCPAuthProvider extends BaseProvider {
|
|
11
|
+
type = 'auth'
|
|
12
|
+
name = 'mcp'
|
|
13
|
+
configurator = MCPAuthConfigurator
|
|
14
|
+
|
|
15
|
+
async initialize() {
|
|
16
|
+
logger.info('MCP Auth Provider initialized');
|
|
17
|
+
this._client = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async uninstall() {
|
|
21
|
+
logger.info('MCP Auth Provider Uninstalled');
|
|
22
|
+
await this.userContext.deleteProviderConfig(
|
|
23
|
+
this.type, this.name, this.appId, this.appVersion
|
|
24
|
+
)
|
|
25
|
+
this._client = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async update(config) {
|
|
29
|
+
logger.info('MCP Auth Provider Updated')
|
|
30
|
+
await this.userContext.setProviderConfig(
|
|
31
|
+
this.type, this.name, config,
|
|
32
|
+
this.appId, this.appVersion
|
|
33
|
+
)
|
|
34
|
+
this.config = config;
|
|
35
|
+
this._client = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getManifest() {
|
|
39
|
+
return this.config
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async isConfigured() {
|
|
43
|
+
if (this.config && this.config.parameters) {
|
|
44
|
+
const { properties, required } = this.config.parameters;
|
|
45
|
+
if (required && Array.isArray(required)) {
|
|
46
|
+
for (const field of required) {
|
|
47
|
+
const property = properties[field];
|
|
48
|
+
if (!property || !property.value ||
|
|
49
|
+
(property.type === 'string' && property.value.trim() === '')) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getClient() {
|
|
59
|
+
// Lazy import to avoid hard dependency unless used
|
|
60
|
+
let MCPClient;
|
|
61
|
+
try {
|
|
62
|
+
MCPClient = await import('mcp-client').then(m => m.MCPClient || m.default);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error(e)
|
|
65
|
+
throw new Error('mcp-client dependency is not installed. Please install mcp-client to use MCP app.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const mcpUrl = this.config?.parameters?.properties?.mcpUrl?.value;
|
|
69
|
+
const authHeader = this.config?.parameters?.properties?.authHeader?.value;
|
|
70
|
+
if (!mcpUrl) {
|
|
71
|
+
throw new Error('MCP URL is not configured in provider auth:mcp');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this._client && this._client.__connected) {
|
|
75
|
+
return this._client;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const client = new MCPClient({ name: 'VADER-MCP', version: '1.0.0' });
|
|
79
|
+
const useSSE = this.config?.parameters?.properties?.useSSE?.value;
|
|
80
|
+
let connectOptions = {};
|
|
81
|
+
if (useSSE) {
|
|
82
|
+
connectOptions = { url: mcpUrl, type: 'sse' };
|
|
83
|
+
} else {
|
|
84
|
+
connectOptions = { url: mcpUrl };
|
|
85
|
+
}
|
|
86
|
+
if (authHeader) {
|
|
87
|
+
connectOptions.headers = { Authorization: authHeader };
|
|
88
|
+
}
|
|
89
|
+
console.log(`Connecting to MCP using ${useSSE ? 'SSE' : 'HTTP'}.`)
|
|
90
|
+
await client.connect(connectOptions);
|
|
91
|
+
console.log('Connected to MCP')
|
|
92
|
+
// mark connected
|
|
93
|
+
client.__connected = true;
|
|
94
|
+
this._client = client;
|
|
95
|
+
return client;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = MCPAuthProvider;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
const {logger} = require('@vida-global/core');
|
|
2
|
+
const {BaseProvider, BaseProviderConfigurator} = require('../../../provider');
|
|
3
|
+
const axios = require('axios');
|
|
4
|
+
const { URL } = require('url');
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const oAuthConfig = {
|
|
9
|
+
scopes: [
|
|
10
|
+
'Calendars.ReadWrite',
|
|
11
|
+
'Calendars.Read',
|
|
12
|
+
'User.Read'
|
|
13
|
+
],
|
|
14
|
+
initiateOAuthUrl: '/vader/oauth/outlook/initiate',
|
|
15
|
+
callbackPath: '/vader/oauth/outlook/callback',
|
|
16
|
+
authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
|
17
|
+
tokenEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OutlookOAuthConfigurator extends BaseProviderConfigurator {
|
|
22
|
+
async storeToken(user, tokenResponse) {
|
|
23
|
+
let tokenInfo = {
|
|
24
|
+
accessToken: tokenResponse.access_token,
|
|
25
|
+
refreshToken: tokenResponse.refresh_token,
|
|
26
|
+
expiresAt: Date.now() + (tokenResponse.expires_in * 1000)
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
await this.redis.set(this.TOKEN_KEY(user), JSON.stringify(tokenInfo));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getAuthorizationUrl(redirectUri, state) {
|
|
33
|
+
const params = new URLSearchParams({
|
|
34
|
+
client_id: this.config.clientId,
|
|
35
|
+
response_type: 'code',
|
|
36
|
+
redirect_uri: redirectUri,
|
|
37
|
+
scope: oAuthConfig.scopes.join(' '),
|
|
38
|
+
response_mode: 'query',
|
|
39
|
+
state: state
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return `${oAuthConfig.authorizationEndpoint}?${params.toString()}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildRoutes(app) {
|
|
46
|
+
app.get(oAuthConfig.initiateOAuthUrl, async (req, res) => {
|
|
47
|
+
const {redirectUri, userId} = req.query;
|
|
48
|
+
|
|
49
|
+
if (!redirectUri || !userId) {
|
|
50
|
+
return res.status(400).send('Missing required parameters: redirectUri and userId');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// State data
|
|
54
|
+
const stateData = {
|
|
55
|
+
redirectUri,
|
|
56
|
+
userId,
|
|
57
|
+
// timestamp: Date.now()
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Configure redirect URI and generate authorization URL
|
|
61
|
+
const authUrl = this.getAuthorizationUrl(redirectUri, Buffer.from(JSON.stringify(stateData)).toString('base64'));
|
|
62
|
+
|
|
63
|
+
res.redirect(authUrl);
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
app.get(oAuthConfig.callbackPath, async (req, res) => {
|
|
67
|
+
let redirectUri = null;
|
|
68
|
+
try {
|
|
69
|
+
redirectUri = await this.handleOAuthCallback(req);
|
|
70
|
+
res.redirect(redirectUri);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('OAuth callback error:', error);
|
|
73
|
+
res.status(500).send('Authentication failed. Please try again.');
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async handleOAuthCallback(req) {
|
|
79
|
+
if (req.query.error) {
|
|
80
|
+
throw new Error(`OAuth error: ${req.query.error}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const {code, state} = req.query;
|
|
84
|
+
if (!code) {
|
|
85
|
+
throw new Error('No authorization code received');
|
|
86
|
+
}
|
|
87
|
+
if (!state) {
|
|
88
|
+
throw new Error('No state parameter received');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const sessionData = JSON.parse(Buffer.from(state, 'base64').toString());
|
|
92
|
+
let {redirectUri, userId} = sessionData;
|
|
93
|
+
if (!redirectUri || !userId) {
|
|
94
|
+
throw new Error('Invalid state parameter');
|
|
95
|
+
}
|
|
96
|
+
redirectUri = decodeURIComponent(redirectUri);
|
|
97
|
+
let user = {id: userId};
|
|
98
|
+
let success = true
|
|
99
|
+
try {
|
|
100
|
+
const response = await axios.post(
|
|
101
|
+
oAuthConfig.tokenEndpoint, {
|
|
102
|
+
client_id: this.config.clientId,
|
|
103
|
+
client_secret: this.config.clientSecret,
|
|
104
|
+
code: code,
|
|
105
|
+
redirect_uri: redirectUri,
|
|
106
|
+
grant_type: 'authorization_code'
|
|
107
|
+
}, {
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
await this.storeToken(user, response.data);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error('Error exchanging code for token:', error);
|
|
115
|
+
success = false;
|
|
116
|
+
}
|
|
117
|
+
const finalUrl = new URL(redirectUri);
|
|
118
|
+
finalUrl.searchParams.append(
|
|
119
|
+
'success', success ? 'true' : 'false'
|
|
120
|
+
);
|
|
121
|
+
finalUrl.searchParams.append(
|
|
122
|
+
'userId', user.id
|
|
123
|
+
)
|
|
124
|
+
return finalUrl.toString();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class OutlookAuthProvider extends BaseProvider {
|
|
130
|
+
type = 'auth';
|
|
131
|
+
name = 'oauth2';
|
|
132
|
+
configurator = OutlookOAuthConfigurator;
|
|
133
|
+
|
|
134
|
+
TOKEN_KEY (user) {
|
|
135
|
+
return `vader:oauth:outlook:${user.id}:token`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
OAUTH_SESSION_KEY (user) {
|
|
139
|
+
return `vader:oauth:outlook:${user.id}:session`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async initialize() {
|
|
143
|
+
if (!this.isServer && this.user) {
|
|
144
|
+
// Load token from Redis if available
|
|
145
|
+
const tokenData = await this.redis.get(this.TOKEN_KEY(this.user));
|
|
146
|
+
if (tokenData) {
|
|
147
|
+
this.tokenInfo = JSON.parse(tokenData);
|
|
148
|
+
}
|
|
149
|
+
if (this.isTokenExpired()) {
|
|
150
|
+
await this.refreshToken();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
this.config = {
|
|
154
|
+
...this.config,
|
|
155
|
+
...oAuthConfig
|
|
156
|
+
}
|
|
157
|
+
logger.info('Outlook Oauth Provider Initialized')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async uninstall () {
|
|
161
|
+
await this.redis.del(this.TOKEN_KEY(this.user));
|
|
162
|
+
await this.redis.del(this.OAUTH_SESSION_KEY(this.user));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async isConfigured() {
|
|
166
|
+
if (!this.config.clientId || !this.config.clientSecret) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!this.tokenInfo) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if the token is expired and try to refresh
|
|
175
|
+
if (this.isTokenExpired() && !(await this.refreshToken())) {
|
|
176
|
+
logger.warn('Outlook Oauth Provider: Token expired and failed to refresh');
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async getManifest() {
|
|
184
|
+
if (this.isServer) {
|
|
185
|
+
return {
|
|
186
|
+
...oAuthConfig
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
return {
|
|
190
|
+
...oAuthConfig,
|
|
191
|
+
parameters: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
accessToken: {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: "Access Token for outlook API.",
|
|
197
|
+
value: await this.getAccessToken(),
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
required: ["accessToken"],
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
isTokenExpired() {
|
|
207
|
+
if (!this.tokenInfo || !this.tokenInfo.expiresAt) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
// Add a 5-minute buffer before expiration
|
|
211
|
+
return Date.now() >= (this.tokenInfo.expiresAt - 300000);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async getAccessToken() {
|
|
215
|
+
if (this.isTokenExpired()) {
|
|
216
|
+
await this.refreshToken();
|
|
217
|
+
}
|
|
218
|
+
return this.tokenInfo?.accessToken;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async refreshToken() {
|
|
222
|
+
if (!this.tokenInfo?.refreshToken) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const response = await axios.post(oAuthConfig.tokenEndpoint, {
|
|
228
|
+
client_id: this.config.clientId,
|
|
229
|
+
client_secret: this.config.clientSecret,
|
|
230
|
+
refresh_token: this.tokenInfo.refreshToken,
|
|
231
|
+
grant_type: 'refresh_token'
|
|
232
|
+
}, {
|
|
233
|
+
headers: {
|
|
234
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await this.storeToken(response.data);
|
|
239
|
+
return true;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
logger.error('Error refreshing token:', error);
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async storeToken(user, tokenResponse) {
|
|
247
|
+
let tokenInfo = {
|
|
248
|
+
accessToken: tokenResponse.access_token,
|
|
249
|
+
refreshToken: tokenResponse.refresh_token,
|
|
250
|
+
expiresAt: Date.now() + (tokenResponse.expires_in * 1000)
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
await this.redis.set(this.TOKEN_KEY(this.user), JSON.stringify(tokenInfo));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = OutlookAuthProvider;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const {BaseProvider} = require('../../../provider');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SquareUpProvider extends BaseProvider {
|
|
6
|
+
type = 'auth';
|
|
7
|
+
name = 'square';
|
|
8
|
+
|
|
9
|
+
async getField (fieldName) {
|
|
10
|
+
return await this.readData(fieldName);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async isConfigured() {
|
|
14
|
+
const {refreshToken, accessToken} = await this.getCredentials();
|
|
15
|
+
return !!(refreshToken && accessToken);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async getClientConfig() {
|
|
19
|
+
const siteUrl = await this.readConfig('siteUrl');
|
|
20
|
+
const clientId = await this.readConfig('clientId');
|
|
21
|
+
const clientSecret = await this.readConfig('clientSecret');
|
|
22
|
+
const redirectUrl = `${siteUrl}/api/v2/providers/${this.type}/${this.name}/receive`;
|
|
23
|
+
return {redirectUrl, clientId, clientSecret};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getAuthUrl(redirectUrl) {
|
|
27
|
+
const {clientId, redirectUrl: defaultRedirectUrl} = await this.getClientConfig();
|
|
28
|
+
const finalRedirectUrl = redirectUrl || defaultRedirectUrl;
|
|
29
|
+
const state = `${this.user.id}|${this.agent.id}|${redirectUrl ? `|${redirectUrl}` : ''}`;
|
|
30
|
+
const scope = (this.appParams.scope || ['APPOINTMENTS_READ', 'CUSTOMERS_READ']).join(' ');
|
|
31
|
+
|
|
32
|
+
// Generate the SquareUp authorization URL
|
|
33
|
+
return `https://connect.squareup.com/oauth2/authorize?client_id=${clientId}&response_type=code&scope=${scope}&redirect_uri=${encodeURIComponent(finalRedirectUrl)}&state=${state}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async exchangeCodeForToken(code) {
|
|
37
|
+
const {clientId, clientSecret, redirectUrl} = await this.getClientConfig();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const response = await axios.post('https://connect.squareup.com/oauth2/token', {
|
|
41
|
+
client_id: clientId,
|
|
42
|
+
client_secret: clientSecret,
|
|
43
|
+
code: code,
|
|
44
|
+
grant_type: 'authorization_code',
|
|
45
|
+
redirect_uri: redirectUrl,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return response.data;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('SQUAREUP PROVIDER ERROR: Failed to exchange code for token:', error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async setCredentials(code) {
|
|
56
|
+
const tokenData = await this.exchangeCodeForToken(code);
|
|
57
|
+
await this.writeData('squareUpOauthAccessToken', tokenData.access_token);
|
|
58
|
+
await this.writeData('squareUpOauthRefreshToken', tokenData.refresh_token);
|
|
59
|
+
await this.writeData('squareUpOauthExpires', Date.now() + tokenData.expires_in * 1000); // in milliseconds
|
|
60
|
+
return 'success';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async getCredentials() {
|
|
64
|
+
const refreshToken = await this.readData('squareUpOauthRefreshToken');
|
|
65
|
+
const accessToken = await this.readData('squareUpOauthAccessToken');
|
|
66
|
+
return {refreshToken, accessToken};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async refreshAccessToken() {
|
|
70
|
+
const {clientId, clientSecret} = await this.getClientConfig();
|
|
71
|
+
const refreshToken = await this.readData('squareUpOauthRefreshToken');
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const response = await axios.post('https://connect.squareup.com/oauth2/token', {
|
|
75
|
+
client_id: clientId,
|
|
76
|
+
client_secret: clientSecret,
|
|
77
|
+
refresh_token: refreshToken,
|
|
78
|
+
grant_type: 'refresh_token',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const tokenData = response.data;
|
|
82
|
+
await this.writeData('squareUpOauthAccessToken', tokenData.access_token);
|
|
83
|
+
await this.writeData('squareUpOauthExpires', Date.now() + tokenData.expires_in * 1000); // in milliseconds
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('SQUAREUP PROVIDER ERROR: Failed to refresh access token:', error);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async revokeCredentials() {
|
|
91
|
+
const accessToken = await this.readData('squareUpOauthAccessToken');
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
if (accessToken) {
|
|
95
|
+
await axios.post('https://connect.squareup.com/oauth2/revoke', {
|
|
96
|
+
client_id: await this.readConfig('clientId'),
|
|
97
|
+
access_token: accessToken,
|
|
98
|
+
});
|
|
99
|
+
await this.deleteData('squareUpOauthAccessToken');
|
|
100
|
+
await this.deleteData('squareUpOauthRefreshToken');
|
|
101
|
+
await this.deleteData('squareUpOauthExpires');
|
|
102
|
+
}
|
|
103
|
+
return 'success';
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('SQUAREUP PROVIDER ERROR: Failed to revoke credentials:', error);
|
|
106
|
+
return 'squareup-error';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = SquareUpProvider;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { logger } = require('@vida-global/core');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MockProvider {
|
|
5
|
+
constructor(providerType, providerName, {user, agent, caller, appParams}, mockData) {
|
|
6
|
+
this.providerType = providerType
|
|
7
|
+
this.providerName = providerName
|
|
8
|
+
this.user = user
|
|
9
|
+
this.agent = agent
|
|
10
|
+
this.caller = caller
|
|
11
|
+
this.appParams = appParams
|
|
12
|
+
this.mockData = mockData
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static getObjectProxy(instance)
|
|
16
|
+
{
|
|
17
|
+
return new Proxy(instance, {
|
|
18
|
+
get(target, prop, receiver) {
|
|
19
|
+
if (prop in target) {
|
|
20
|
+
if (typeof target[prop] === 'function') {
|
|
21
|
+
return target[prop].bind(target)
|
|
22
|
+
} else {
|
|
23
|
+
return target[prop]
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
const method = target.mockData.methods?.find((m) => m.method === prop)
|
|
27
|
+
if (method) {
|
|
28
|
+
return method.value.bind(target)
|
|
29
|
+
}
|
|
30
|
+
return Reflect.get(target, prop, receiver);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async isConfigured() {
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getField(name) {
|
|
41
|
+
const field = this.mockData.fields?.find((f) => f.field === name)
|
|
42
|
+
if (!field) {
|
|
43
|
+
// logger.error(`Could not find field ${name} on provider type ${this.providerType} with name ${this.providerName}`)
|
|
44
|
+
throw Error(`MockData: Could not find field ${name} on provider type ${this.providerType} with name ${this.providerName}`)
|
|
45
|
+
}
|
|
46
|
+
return field.value
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = MockProvider;
|
|
51
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const { VidaServerController } = require('@vida-global/core');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AppController extends VidaServerController {
|
|
5
|
+
|
|
6
|
+
/***********************************************************************************************
|
|
7
|
+
* FUNCTIONS
|
|
8
|
+
***********************************************************************************************/
|
|
9
|
+
async postInvokeFunction() {
|
|
10
|
+
try {
|
|
11
|
+
await this.invokeFunction();
|
|
12
|
+
} catch (err) {
|
|
13
|
+
this.logger.error({message: err.message, stack: err.stack}, 'Unhandled Exception')
|
|
14
|
+
this.statusCode = err.status || 500;
|
|
15
|
+
await this.render({
|
|
16
|
+
error: {message: err.message, status: err.status || 500}
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async invokeFunction() {
|
|
23
|
+
await this.#invokeAppAction('Function');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/***********************************************************************************************
|
|
28
|
+
* HOOKS
|
|
29
|
+
***********************************************************************************************/
|
|
30
|
+
async postInvokeHook() {
|
|
31
|
+
try {
|
|
32
|
+
await this.invokeHook();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.info(error)
|
|
35
|
+
return res.status(500).json({error})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async invokeHook() {
|
|
41
|
+
await this.#invokeAppAction('Hook');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/***********************************************************************************************
|
|
46
|
+
* CALL HELPER
|
|
47
|
+
***********************************************************************************************/
|
|
48
|
+
async postInvokeAppManager() {
|
|
49
|
+
try {
|
|
50
|
+
await this.invokeAppManager();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logger.info(error)
|
|
53
|
+
return res.status(500).json({error})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async invokeAppManager() {
|
|
59
|
+
const { functionArgs,
|
|
60
|
+
functionName,
|
|
61
|
+
userContext } = this.params
|
|
62
|
+
const context = this.appManager.context(userContext);
|
|
63
|
+
let resp = await context[functionName](...functionArgs)
|
|
64
|
+
if (resp.serialize && typeof resp.serialize === 'function') {
|
|
65
|
+
resp = await resp.serialize()
|
|
66
|
+
}
|
|
67
|
+
await this.render(resp);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
/***********************************************************************************************
|
|
72
|
+
* CALL HELPER
|
|
73
|
+
***********************************************************************************************/
|
|
74
|
+
async getOpenApi() {
|
|
75
|
+
const specs = await this.appManager.generateOpenApiSpec();
|
|
76
|
+
await this.render(specs);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
/***********************************************************************************************
|
|
81
|
+
* MISC
|
|
82
|
+
***********************************************************************************************/
|
|
83
|
+
static get routes() {
|
|
84
|
+
return {
|
|
85
|
+
postInvokeAppManager: '/invoke/apps/manager/:functionName',
|
|
86
|
+
postInvokeFunction: '/invoke/app/function/:appId/:appVersion/:functionName',
|
|
87
|
+
postInvokeHook: '/invoke/app/hook/:appId/:appVersion/:hookName',
|
|
88
|
+
getOpenApi: '/openapi.json'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async #invokeAppAction(actionType) {
|
|
94
|
+
const { appId,
|
|
95
|
+
appVersion,
|
|
96
|
+
userContext,
|
|
97
|
+
appManifest } = this.params;
|
|
98
|
+
const actionName = this.params[`${actionType.toLowerCase()}Name`];
|
|
99
|
+
const actionArgs = this.params[`${actionType.toLowerCase()}Args`];
|
|
100
|
+
const context = this.appManager.context(userContext);
|
|
101
|
+
const actionHandler = `handle${actionType}Call`;
|
|
102
|
+
const resp = await context[actionHandler](appId,
|
|
103
|
+
appVersion,
|
|
104
|
+
appManifest,
|
|
105
|
+
actionName,
|
|
106
|
+
actionArgs);
|
|
107
|
+
await this.render(resp);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
module.exports = { AppController };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const { AppServerAppManager } = require('../apps/appManager/appServerAppManager');
|
|
2
|
+
const swaggerUi = require("swagger-ui-express");
|
|
3
|
+
const { VidaServer, logger } = require('@vida-global/core');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AppServer extends VidaServer{
|
|
7
|
+
#appManager;
|
|
8
|
+
#apps;
|
|
9
|
+
|
|
10
|
+
constructor({ apps, useMocks=true }) {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this.#appManager = new AppServerAppManager({ apps, useMocks });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
buildController(controllerCls, request, response) {
|
|
17
|
+
const controller = super.buildController(...arguments);
|
|
18
|
+
controller.appManager = this.#appManager;
|
|
19
|
+
return controller;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
setupMiddleware() {
|
|
24
|
+
super.setupMiddleware();
|
|
25
|
+
this.initializeSwaggerDocs();
|
|
26
|
+
this.use(this.contentLengthMiddleware);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
initializeSwaggerDocs() {
|
|
31
|
+
const swaggerSpecUrl = "/openapi.json"
|
|
32
|
+
const options = {
|
|
33
|
+
swaggerOptions: {
|
|
34
|
+
url: swaggerSpecUrl
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this.use("/api-docs", swaggerUi.serve, swaggerUi.setup(null, options))
|
|
38
|
+
this.logger.info(`API Docs available at: http://${this.host}:${this.port}/api-docs`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
contentLengthMiddleware(err, req, res, next) {
|
|
43
|
+
if (err.status === 413) {
|
|
44
|
+
logger.error({
|
|
45
|
+
error: err,
|
|
46
|
+
route: req.originalUrl,
|
|
47
|
+
method: req.method,
|
|
48
|
+
contentLength: req.headers['content-length'],
|
|
49
|
+
payload: req.body
|
|
50
|
+
}, 'Request entity too large');
|
|
51
|
+
|
|
52
|
+
return res.status(413).json({
|
|
53
|
+
status: 'error',
|
|
54
|
+
message: 'Request entity too large',
|
|
55
|
+
details: {
|
|
56
|
+
maxSize: '50mb',
|
|
57
|
+
receivedSize: req.headers['content-length']
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
next();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
get controllerDirectories() {
|
|
66
|
+
return [__dirname];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
AppServer
|
|
73
|
+
}
|