@friggframework/core 2.0.0-next.1 → 2.0.0-next.11

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.
@@ -0,0 +1,57 @@
1
+ const { createHandler, flushDebugLog } = require('@friggframework/core');
2
+ const express = require('express');
3
+ const bodyParser = require('body-parser');
4
+ const cors = require('cors');
5
+ const Boom = require('@hapi/boom');
6
+ const loadUserManager = require('./routers/middleware/loadUser');
7
+ const serverlessHttp = require('serverless-http');
8
+
9
+ const createApp = (applyMiddleware) => {
10
+ const app = express();
11
+
12
+ app.use(bodyParser.json({ limit: '10mb' }));
13
+ app.use(bodyParser.urlencoded({ extended: true }));
14
+ app.use(
15
+ cors({
16
+ origin: '*',
17
+ credentials: true,
18
+ })
19
+ );
20
+
21
+ app.use(loadUserManager);
22
+
23
+ if (applyMiddleware) applyMiddleware(app);
24
+
25
+ // Handle sending error response and logging server errors to console
26
+ app.use((err, req, res, next) => {
27
+ const boomError = err.isBoom ? err : Boom.boomify(err);
28
+ const {
29
+ output: { statusCode = 500 },
30
+ } = boomError;
31
+
32
+ if (statusCode >= 500) {
33
+ flushDebugLog(boomError);
34
+ res.status(statusCode).json({ error: 'Internal Server Error' });
35
+ } else {
36
+ res.status(statusCode).json({ error: err.message });
37
+ }
38
+ });
39
+
40
+ return app;
41
+ };
42
+
43
+ function createAppHandler(eventName, router, shouldUseDatabase = true) {
44
+ const app = createApp((app) => {
45
+ app.use(router);
46
+ });
47
+ return createHandler({
48
+ eventName,
49
+ method: serverlessHttp(app),
50
+ shouldUseDatabase,
51
+ });
52
+ }
53
+
54
+ module.exports = {
55
+ createApp,
56
+ createAppHandler,
57
+ };
@@ -0,0 +1,87 @@
1
+ const { createFriggBackend, Worker } = require('@friggframework/core');
2
+ const {
3
+ findNearestBackendPackageJson,
4
+ } = require('../../devtools/frigg-cli/utils/backend-path');
5
+ const path = require('node:path');
6
+ const fs = require('fs-extra');
7
+
8
+ const backendPath = findNearestBackendPackageJson();
9
+ if (!backendPath) {
10
+ throw new Error('Could not find backend package.json');
11
+ }
12
+
13
+ const backendDir = path.dirname(backendPath);
14
+ const backendFilePath = path.join(backendDir, 'index.js');
15
+ if (!fs.existsSync(backendFilePath)) {
16
+ throw new Error('Could not find index.js');
17
+ }
18
+
19
+ const backendJsFile = require(backendFilePath);
20
+ const { Router } = require('express');
21
+ const appDefinition = backendJsFile.Definition;
22
+
23
+ const backend = createFriggBackend(appDefinition);
24
+ const loadRouterFromObject = (IntegrationClass, routerObject) => {
25
+ const router = Router();
26
+ const { path, method, event } = routerObject;
27
+ console.log(
28
+ `Registering ${method} ${path} for ${IntegrationClass.Definition.name}`
29
+ );
30
+ router[method.toLowerCase()](path, async (req, res, next) => {
31
+ try {
32
+ const integration = new IntegrationClass({});
33
+ await integration.loadModules();
34
+ await integration.registerEventHandlers();
35
+ const result = await integration.send(event, {req, res, next});
36
+ res.json(result);
37
+ } catch (error) {
38
+ next(error);
39
+ }
40
+ });
41
+
42
+ return router;
43
+ };
44
+ const createQueueWorker = (integrationClass) => {
45
+ class QueueWorker extends Worker {
46
+ async _run(params, context) {
47
+ try {
48
+ let instance;
49
+ if (!params.integrationId) {
50
+ instance = new integrationClass({});
51
+ await instance.loadModules();
52
+ // await instance.loadUserActions();
53
+ await instance.registerEventHandlers();
54
+ console.log(
55
+ `${params.event} for ${integrationClass.Definition.name} integration with no integrationId`
56
+ );
57
+ } else {
58
+ instance =
59
+ await integrationClass.getInstanceFromIntegrationId({
60
+ integrationId: params.integrationId,
61
+ });
62
+ console.log(
63
+ `${params.event} for ${instance.integration.config.type} of integrationId: ${params.integrationId}`
64
+ );
65
+ }
66
+ const res = await instance.send(params.event, {
67
+ data: params.data,
68
+ context,
69
+ });
70
+ return res;
71
+ } catch (error) {
72
+ console.error(
73
+ `Error in ${params.event} for ${integrationClass.Definition.name}:`,
74
+ error
75
+ );
76
+ throw error;
77
+ }
78
+ }
79
+ }
80
+ return QueueWorker;
81
+ };
82
+
83
+ module.exports = {
84
+ ...backend,
85
+ loadRouterFromObject,
86
+ createQueueWorker,
87
+ };
@@ -0,0 +1,26 @@
1
+ const { createIntegrationRouter } = require('@friggframework/core');
2
+ const { createAppHandler } = require('./../app-handler-helpers');
3
+ const { requireLoggedInUser } = require('./middleware/requireLoggedInUser');
4
+ const {
5
+ moduleFactory,
6
+ integrationFactory,
7
+ IntegrationHelper,
8
+ } = require('./../backend-utils');
9
+
10
+ const router = createIntegrationRouter({
11
+ factory: { moduleFactory, integrationFactory, IntegrationHelper },
12
+ requireLoggedInUser,
13
+ getUserId: (req) => req.user.getUserId(),
14
+ });
15
+
16
+ router.route('/redirect/:appId').get((req, res) => {
17
+ res.redirect(
18
+ `${process.env.FRONTEND_URI}/redirect/${
19
+ req.params.appId
20
+ }?${new URLSearchParams(req.query)}`
21
+ );
22
+ });
23
+
24
+ const handler = createAppHandler('HTTP Event: Auth', router);
25
+
26
+ module.exports = { handler, router };
@@ -0,0 +1,42 @@
1
+ const { createAppHandler } = require('./../app-handler-helpers');
2
+ const {
3
+ integrationFactory,
4
+ loadRouterFromObject,
5
+ } = require('./../backend-utils');
6
+ const { Router } = require('express');
7
+
8
+ const handlers = {};
9
+ for (const IntegrationClass of integrationFactory.integrationClasses) {
10
+ const router = Router();
11
+ const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
12
+
13
+ console.log(`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`);
14
+
15
+ for (const routeDef of IntegrationClass.Definition.routes) {
16
+ if (typeof routeDef === 'function') {
17
+ router.use(basePath, routeDef(IntegrationClass));
18
+ console.log(`│ ANY ${basePath}/* (function handler)`);
19
+ } else if (typeof routeDef === 'object') {
20
+ router.use(
21
+ basePath,
22
+ loadRouterFromObject(IntegrationClass, routeDef)
23
+ );
24
+ const method = (routeDef.method || 'ANY').toUpperCase();
25
+ const fullPath = `${basePath}${routeDef.path}`;
26
+ console.log(`│ ${method} ${fullPath}`);
27
+ } else if (routeDef instanceof express.Router) {
28
+ router.use(basePath, routeDef);
29
+ console.log(`│ ANY ${basePath}/* (express router)`);
30
+ }
31
+ }
32
+ console.log('│');
33
+
34
+ handlers[`${IntegrationClass.Definition.name}`] = {
35
+ handler: createAppHandler(
36
+ `HTTP Event: ${IntegrationClass.Definition.name}`,
37
+ router
38
+ ),
39
+ };
40
+ }
41
+
42
+ module.exports = { handlers };
@@ -0,0 +1,15 @@
1
+ const catchAsyncError = require('express-async-handler');
2
+ const { User } = require('../../backend-utils');
3
+
4
+ module.exports = catchAsyncError(async (req, res, next) => {
5
+ const authorizationHeader = req.headers.authorization;
6
+
7
+ if (authorizationHeader) {
8
+ // Removes "Bearer " and trims
9
+ const token = authorizationHeader.split(' ')[1].trim();
10
+ // Load user for later middleware/routes to use
11
+ req.user = await User.newUser({ token });
12
+ }
13
+
14
+ return next();
15
+ });
@@ -0,0 +1,12 @@
1
+ const Boom = require('@hapi/boom');
2
+
3
+ // CheckLoggedIn Middleware
4
+ const requireLoggedInUser = (req, res, next) => {
5
+ if (!req.user || !req.user.isLoggedIn()) {
6
+ throw Boom.unauthorized('Invalid Token');
7
+ }
8
+
9
+ next();
10
+ };
11
+
12
+ module.exports = { requireLoggedInUser };
@@ -0,0 +1,41 @@
1
+ const express = require('express');
2
+ const { createAppHandler } = require('../app-handler-helpers');
3
+ const { checkRequiredParams } = require('@friggframework/core');
4
+ const { User } = require('../backend-utils');
5
+ const catchAsyncError = require('express-async-handler');
6
+
7
+ const router = express();
8
+
9
+ // define the login endpoint
10
+ router.route('/user/login').post(
11
+ catchAsyncError(async (req, res) => {
12
+ const { username, password } = checkRequiredParams(req.body, [
13
+ 'username',
14
+ 'password',
15
+ ]);
16
+ const user = await User.loginUser({ username, password });
17
+ const token = await user.createUserToken(120);
18
+ res.status(201);
19
+ res.json({ token });
20
+ })
21
+ );
22
+
23
+ router.route('/user/create').post(
24
+ catchAsyncError(async (req, res) => {
25
+ const { username, password } = checkRequiredParams(req.body, [
26
+ 'username',
27
+ 'password',
28
+ ]);
29
+ const user = await User.createIndividualUser({
30
+ username,
31
+ password,
32
+ });
33
+ const token = await user.createUserToken(120);
34
+ res.status(201);
35
+ res.json({ token });
36
+ })
37
+ );
38
+
39
+ const handler = createAppHandler('HTTP Event: User', router);
40
+
41
+ module.exports = { handler, router };
@@ -0,0 +1,55 @@
1
+ const { createHandler } = require('@friggframework/core');
2
+ const { WebsocketConnection } = require('@friggframework/core');
3
+
4
+ const handleWebSocketConnection = async (event, context) => {
5
+ // Handle different WebSocket events
6
+ switch (event.requestContext.eventType) {
7
+ case 'CONNECT':
8
+ // Handle new connection
9
+ try {
10
+ const connectionId = event.requestContext.connectionId;
11
+ await WebsocketConnection.create({ connectionId });
12
+ console.log(`Stored new connection: ${connectionId}`);
13
+ return { statusCode: 200, body: 'Connected.' };
14
+ } catch (error) {
15
+ console.error('Error storing connection:', error);
16
+ return { statusCode: 500, body: 'Error connecting.' };
17
+ }
18
+
19
+ case 'DISCONNECT':
20
+ // Handle disconnection
21
+ try {
22
+ const connectionId = event.requestContext.connectionId;
23
+ await WebsocketConnection.deleteOne({ connectionId });
24
+ console.log(`Removed connection: ${connectionId}`);
25
+ return { statusCode: 200, body: 'Disconnected.' };
26
+ } catch (error) {
27
+ console.error('Error removing connection:', error);
28
+ return { statusCode: 500, body: 'Error disconnecting.' };
29
+ }
30
+
31
+ case 'MESSAGE':
32
+ // Handle incoming message
33
+ const message = JSON.parse(event.body);
34
+ console.log('Received message:', message);
35
+
36
+ // Process the message and send a response
37
+ const responseMessage = { message: 'Message received' };
38
+ return {
39
+ statusCode: 200,
40
+ body: JSON.stringify(responseMessage),
41
+ };
42
+
43
+ default:
44
+ return { statusCode: 400, body: 'Unhandled event type.' };
45
+ }
46
+ };
47
+
48
+ const handler = createHandler({
49
+ eventName: 'WebSocket Event',
50
+ method: handleWebSocketConnection,
51
+ shouldUseDatabase: true, // Set to true as we're using the database
52
+ isUserFacingResponse: true, // This is a server-to-server response
53
+ });
54
+
55
+ module.exports = { handler };
@@ -0,0 +1,24 @@
1
+ const { createHandler } = require('@friggframework/core');
2
+ const { integrationFactory, createQueueWorker } = require('../backend-utils');
3
+
4
+ const handlers = {};
5
+ integrationFactory.integrationClasses.forEach((IntegrationClass) => {
6
+ const defaultQueueWorker = createQueueWorker(IntegrationClass);
7
+
8
+ handlers[`${IntegrationClass.Definition.name}`] = {
9
+ queueWorker: createHandler({
10
+ eventName: `Queue Worker for ${IntegrationClass.Definition.name}`,
11
+ isUserFacingResponse: false,
12
+ method: async (event, context) => {
13
+ const worker = new defaultQueueWorker();
14
+ await worker.run(event, context);
15
+ return {
16
+ message: 'Successfully processed the Generic Queue Worker',
17
+ input: event,
18
+ };
19
+ },
20
+ }),
21
+ };
22
+ });
23
+
24
+ module.exports = { handlers };
package/index.js CHANGED
@@ -7,7 +7,12 @@ const {
7
7
  getArrayParamAndVerifyParamType,
8
8
  getAndVerifyType,
9
9
  } = require('./assertions/index');
10
- const { Delegate, Worker, loadInstalledModules, createHandler } = require('./core/index');
10
+ const {
11
+ Delegate,
12
+ Worker,
13
+ loadInstalledModules,
14
+ createHandler,
15
+ } = require('./core/index');
11
16
  const {
12
17
  mongoose,
13
18
  connectToDatabase,
@@ -17,7 +22,8 @@ const {
17
22
  OrganizationUser,
18
23
  State,
19
24
  Token,
20
- UserModel
25
+ UserModel,
26
+ WebsocketConnection,
21
27
  } = require('./database/index');
22
28
  const { Encrypt, Cryptor } = require('./encrypt/encrypt');
23
29
  const {
@@ -26,7 +32,7 @@ const {
26
32
  HaltError,
27
33
  RequiredPropertyError,
28
34
  ParameterTypeError,
29
- } = require('./errors/index');
35
+ } = require('./errors/index');
30
36
  const {
31
37
  IntegrationBase,
32
38
  IntegrationModel,
@@ -36,14 +42,10 @@ const {
36
42
  IntegrationHelper,
37
43
  createIntegrationRouter,
38
44
  checkRequiredParams,
39
- createFriggBackend
45
+ createFriggBackend,
40
46
  } = require('./integrations/index');
41
47
  const { TimeoutCatcher } = require('./lambda/index');
42
- const {
43
- debug,
44
- initDebugLog,
45
- flushDebugLog
46
- } = require('./logs/index');
48
+ const { debug, initDebugLog, flushDebugLog } = require('./logs/index');
47
49
  const {
48
50
  Credential,
49
51
  EntityManager,
@@ -55,11 +57,13 @@ const {
55
57
  Requester,
56
58
  ModuleConstants,
57
59
  ModuleFactory,
58
- Auther
60
+ Auther,
59
61
  } = require('./module-plugin/index');
60
62
 
61
63
  // const {Sync } = require('./syncs/model');
62
64
 
65
+ const { QueuerUtil } = require('./queues');
66
+
63
67
  module.exports = {
64
68
  // assertions
65
69
  expectShallowEqualDbObject,
@@ -69,11 +73,13 @@ module.exports = {
69
73
  getParamAndVerifyParamType,
70
74
  getArrayParamAndVerifyParamType,
71
75
  getAndVerifyType,
76
+
72
77
  // core
73
78
  Delegate,
74
79
  Worker,
75
80
  loadInstalledModules,
76
81
  createHandler,
82
+
77
83
  // database
78
84
  mongoose,
79
85
  connectToDatabase,
@@ -84,15 +90,19 @@ module.exports = {
84
90
  State,
85
91
  Token,
86
92
  UserModel,
93
+ WebsocketConnection,
94
+
87
95
  // encrypt
88
96
  Encrypt,
89
97
  Cryptor,
98
+
90
99
  // errors
91
100
  BaseError,
92
101
  FetchError,
93
102
  HaltError,
94
103
  RequiredPropertyError,
95
104
  ParameterTypeError,
105
+
96
106
  // integrations
97
107
  IntegrationBase,
98
108
  IntegrationModel,
@@ -103,12 +113,15 @@ module.exports = {
103
113
  checkRequiredParams,
104
114
  createIntegrationRouter,
105
115
  createFriggBackend,
116
+
106
117
  // lambda
107
118
  TimeoutCatcher,
119
+
108
120
  // logs
109
121
  debug,
110
122
  initDebugLog,
111
123
  flushDebugLog,
124
+
112
125
  // module plugin
113
126
  Credential,
114
127
  EntityManager,
@@ -120,5 +133,8 @@ module.exports = {
120
133
  Requester,
121
134
  ModuleConstants,
122
135
  ModuleFactory,
123
- Auther
124
- }
136
+ Auther,
137
+
138
+ // queues
139
+ QueuerUtil,
140
+ };
@@ -7,7 +7,11 @@ function createIntegrationRouter(params) {
7
7
  const router = get(params, 'router', express());
8
8
  const factory = get(params, 'factory');
9
9
  const getUserId = get(params, 'getUserId', (req) => null);
10
- const requireLoggedInUser = get(params, 'requireLoggedInUser', (req, res, next) => next());
10
+ const requireLoggedInUser = get(
11
+ params,
12
+ 'requireLoggedInUser',
13
+ (req, res, next) => next()
14
+ );
11
15
 
12
16
  router.all('/api/entities*', requireLoggedInUser);
13
17
  router.all('/api/authorize', requireLoggedInUser);
@@ -43,22 +47,23 @@ function checkRequiredParams(params, requiredKeys) {
43
47
  }
44
48
 
45
49
  function setIntegrationRoutes(router, factory, getUserId) {
46
- const {moduleFactory, integrationFactory, IntegrationHelper} = factory;
50
+ const { moduleFactory, integrationFactory, IntegrationHelper } = factory;
47
51
  router.route('/api/integrations').get(
48
52
  catchAsyncError(async (req, res) => {
49
53
  const results = await integrationFactory.getIntegrationOptions();
50
- results.entities.authorized = await moduleFactory.getEntitiesForUser(
51
- getUserId(req)
52
- );
53
- results.integrations = await IntegrationHelper.getIntegrationsForUserId(
54
- getUserId(req)
55
- );
54
+ results.entities.authorized =
55
+ await moduleFactory.getEntitiesForUser(getUserId(req));
56
+ results.integrations =
57
+ await IntegrationHelper.getIntegrationsForUserId(
58
+ getUserId(req)
59
+ );
56
60
 
57
61
  for (const integrationRecord of results.integrations) {
58
- const integration = await integrationFactory.getInstanceFromIntegrationId({
59
- integrationId: integrationRecord.id,
60
- userId: getUserId(req),
61
- });
62
+ const integration =
63
+ await integrationFactory.getInstanceFromIntegrationId({
64
+ integrationId: integrationRecord.id,
65
+ userId: getUserId(req),
66
+ });
62
67
  integrationRecord.userActions = integration.userActions;
63
68
  }
64
69
  res.json(results);
@@ -75,21 +80,22 @@ function setIntegrationRoutes(router, factory, getUserId) {
75
80
  get(params.config, 'type');
76
81
 
77
82
  // create integration
78
- const integration =
79
- await integrationFactory.createIntegration(
80
- params.entities,
81
- getUserId(req),
82
- params.config,
83
- );
83
+ const integration = await integrationFactory.createIntegration(
84
+ params.entities,
85
+ getUserId(req),
86
+ params.config
87
+ );
84
88
 
85
89
  // post integration initialization
86
90
  debug(
87
91
  `Calling onCreate on the ${integration?.constructor?.Config?.name} Integration with no arguments`
88
92
  );
89
- await integration.onCreate();
93
+ await integration.send('ON_CREATE', {});
90
94
 
91
95
  res.status(201).json(
92
- await IntegrationHelper.getFormattedIntegration(integration.record)
96
+ await IntegrationHelper.getFormattedIntegration(
97
+ integration.record
98
+ )
93
99
  );
94
100
  })
95
101
  );
@@ -108,19 +114,19 @@ function setIntegrationRoutes(router, factory, getUserId) {
108
114
  `Calling onUpdate on the ${integration?.constructor?.Config?.name} Integration arguments: `,
109
115
  params
110
116
  );
111
- await integration.onUpdate(params);
117
+ await integration.send('ON_UPDATE', params);
112
118
 
113
119
  res.json(
114
- await IntegrationHelper.getFormattedIntegration(integration.record)
120
+ await IntegrationHelper.getFormattedIntegration(
121
+ integration.record
122
+ )
115
123
  );
116
124
  })
117
125
  );
118
126
 
119
127
  router.route('/api/integrations/:integrationId').delete(
120
128
  catchAsyncError(async (req, res) => {
121
- const params = checkRequiredParams(req.params, [
122
- 'integrationId',
123
- ]);
129
+ const params = checkRequiredParams(req.params, ['integrationId']);
124
130
  const integration =
125
131
  await integrationFactory.getInstanceFromIntegrationId({
126
132
  userId: getUserId(req),
@@ -128,9 +134,9 @@ function setIntegrationRoutes(router, factory, getUserId) {
128
134
  });
129
135
 
130
136
  debug(
131
- `Calling onUpdate on the ${integration?.constructor?.Config?.name} Integration with no arguments`
137
+ `Calling onUpdate on the ${integration?.constructor?.Definition?.name} Integration with no arguments`
132
138
  );
133
- await integration.onDelete();
139
+ await integration.send('ON_DELETE');
134
140
  await IntegrationHelper.deleteIntegrationForUserById(
135
141
  getUserId(req),
136
142
  params.integrationId
@@ -142,79 +148,101 @@ function setIntegrationRoutes(router, factory, getUserId) {
142
148
 
143
149
  router.route('/api/integrations/:integrationId/config/options').get(
144
150
  catchAsyncError(async (req, res) => {
145
- const params = checkRequiredParams(req.params, [
146
- 'integrationId',
147
- ]);
151
+ const params = checkRequiredParams(req.params, ['integrationId']);
148
152
  const integration =
149
153
  await integrationFactory.getInstanceFromIntegrationId(params);
150
- res.json(await integration.getConfigOptions());
154
+ res.json(await integration.send('GET_CONFIG_OPTIONS'));
151
155
  })
152
156
  );
153
157
 
154
- router.route('/api/integrations/:integrationId/config/options/refresh').post(
155
- catchAsyncError(async (req, res) => {
156
- const params = checkRequiredParams(req.params, [
157
- 'integrationId',
158
- ]);
159
- const integration =
160
- await integrationFactory.getInstanceFromIntegrationId(params);
161
-
162
- res.json(
163
- await integration.refreshConfigOptions(req.body)
164
- );
165
- })
166
- );
167
-
168
- router.route('/api/integrations/:integrationId/actions/:actionId/options').get(
158
+ router
159
+ .route('/api/integrations/:integrationId/config/options/refresh')
160
+ .post(
161
+ catchAsyncError(async (req, res) => {
162
+ const params = checkRequiredParams(req.params, [
163
+ 'integrationId',
164
+ ]);
165
+ const integration =
166
+ await integrationFactory.getInstanceFromIntegrationId(
167
+ params
168
+ );
169
+
170
+ res.json(
171
+ await integration.send('REFRESH_CONFIG_OPTIONS', req.body)
172
+ );
173
+ })
174
+ );
175
+ router.route('/api/integrations/:integrationId/actions').all(
169
176
  catchAsyncError(async (req, res) => {
170
- const params = checkRequiredParams(req.params, [
171
- 'integrationId',
172
- 'actionId'
173
- ]);
177
+ const params = checkRequiredParams(req.params, ['integrationId']);
174
178
  const integration =
175
179
  await integrationFactory.getInstanceFromIntegrationId(params);
176
-
177
- res.json(
178
- await integration.getActionOptions(params.actionId)
179
- );
180
+ res.json(await integration.send('GET_USER_ACTIONS', req.body));
180
181
  })
181
182
  );
182
183
 
183
- router.route('/api/integrations/:integrationId/actions/:actionId/options/refresh').post(
184
- catchAsyncError(async (req, res) => {
185
- const params = checkRequiredParams(req.params, [
186
- 'integrationId',
187
- 'actionId'
188
- ]);
189
- const integration =
190
- await integrationFactory.getInstanceFromIntegrationId(params);
184
+ router
185
+ .route('/api/integrations/:integrationId/actions/:actionId/options')
186
+ .all(
187
+ catchAsyncError(async (req, res) => {
188
+ const params = checkRequiredParams(req.params, [
189
+ 'integrationId',
190
+ 'actionId',
191
+ ]);
192
+ const integration =
193
+ await integrationFactory.getInstanceFromIntegrationId(
194
+ params
195
+ );
196
+
197
+ res.json(
198
+ await integration.send('GET_USER_ACTION_OPTIONS', {
199
+ actionId: params.actionId,
200
+ data: req.body,
201
+ })
202
+ );
203
+ })
204
+ );
191
205
 
192
- res.json(
193
- await integration.refreshActionOptions(params.actionId, req.body)
194
- );
195
- })
196
- );
206
+ router
207
+ .route(
208
+ '/api/integrations/:integrationId/actions/:actionId/options/refresh'
209
+ )
210
+ .post(
211
+ catchAsyncError(async (req, res) => {
212
+ const params = checkRequiredParams(req.params, [
213
+ 'integrationId',
214
+ 'actionId',
215
+ ]);
216
+ const integration =
217
+ await integrationFactory.getInstanceFromIntegrationId(
218
+ params
219
+ );
220
+
221
+ res.json(
222
+ await integration.send('REFRESH_USER_ACTION_OPTIONS', {
223
+ actionId: params.actionId,
224
+ data: req.body,
225
+ })
226
+ );
227
+ })
228
+ );
197
229
 
198
230
  router.route('/api/integrations/:integrationId/actions/:actionId').post(
199
231
  catchAsyncError(async (req, res) => {
200
232
  const params = checkRequiredParams(req.params, [
201
233
  'integrationId',
202
- 'actionId'
234
+ 'actionId',
203
235
  ]);
204
236
  const integration =
205
237
  await integrationFactory.getInstanceFromIntegrationId(params);
206
238
 
207
- res.json(
208
- await integration.notify(params.actionId, req.body)
209
- );
239
+ res.json(await integration.send(params.actionId, req.body));
210
240
  })
211
241
  );
212
242
 
213
243
  router.route('/api/integrations/:integrationId').get(
214
244
  catchAsyncError(async (req, res) => {
215
- const params = checkRequiredParams(req.params, [
216
- 'integrationId',
217
- ]);
245
+ const params = checkRequiredParams(req.params, ['integrationId']);
218
246
  const integration = await IntegrationHelper.getIntegrationById(
219
247
  params.integrationId
220
248
  );
@@ -231,13 +259,12 @@ function setIntegrationRoutes(router, factory, getUserId) {
231
259
 
232
260
  router.route('/api/integrations/:integrationId/test-auth').get(
233
261
  catchAsyncError(async (req, res) => {
234
- const params = checkRequiredParams(req.params, [
235
- 'integrationId',
236
- ]);
237
- const instance = await integrationFactory.getInstanceFromIntegrationId({
238
- userId: getUserId(req),
239
- integrationId: params.integrationId,
240
- });
262
+ const params = checkRequiredParams(req.params, ['integrationId']);
263
+ const instance =
264
+ await integrationFactory.getInstanceFromIntegrationId({
265
+ userId: getUserId(req),
266
+ integrationId: params.integrationId,
267
+ });
241
268
 
242
269
  if (!instance) {
243
270
  throw Boom.notFound();
@@ -260,7 +287,7 @@ function setIntegrationRoutes(router, factory, getUserId) {
260
287
  }
261
288
 
262
289
  function setEntityRoutes(router, factory, getUserId) {
263
- const {moduleFactory, IntegrationHelper} = factory;
290
+ const { moduleFactory, IntegrationHelper } = factory;
264
291
  const getModuleInstance = async (req, entityType) => {
265
292
  if (!moduleFactory.checkIsValidType(entityType)) {
266
293
  throw Boom.badRequest(
@@ -269,14 +296,15 @@ function setEntityRoutes(router, factory, getUserId) {
269
296
  )}`
270
297
  );
271
298
  }
272
- return await moduleFactory.getInstanceFromTypeName(entityType, getUserId(req));
299
+ return await moduleFactory.getInstanceFromTypeName(
300
+ entityType,
301
+ getUserId(req)
302
+ );
273
303
  };
274
304
 
275
305
  router.route('/api/authorize').get(
276
306
  catchAsyncError(async (req, res) => {
277
- const params = checkRequiredParams(req.query, [
278
- 'entityType',
279
- ]);
307
+ const params = checkRequiredParams(req.query, ['entityType']);
280
308
  const module = await getModuleInstance(req, params.entityType);
281
309
  const areRequirementsValid =
282
310
  module.validateAuthorizationRequirements();
@@ -330,11 +358,9 @@ function setEntityRoutes(router, factory, getUserId) {
330
358
  null,
331
359
  null,
332
360
  getUserId(req)
333
- )
334
-
335
- res.json(
336
- await module.findOrCreateEntity(entityDetails)
337
361
  );
362
+
363
+ res.json(await module.findOrCreateEntity(entityDetails));
338
364
  })
339
365
  );
340
366
 
@@ -349,9 +375,7 @@ function setEntityRoutes(router, factory, getUserId) {
349
375
  throw Boom.forbidden('Credential does not belong to user');
350
376
  }
351
377
 
352
- const params = checkRequiredParams(req.query, [
353
- 'entityType',
354
- ]);
378
+ const params = checkRequiredParams(req.query, ['entityType']);
355
379
  const module = await getModuleInstance(req, params.entityType);
356
380
 
357
381
  res.json(await module.getEntityOptions());
@@ -384,7 +408,7 @@ function setEntityRoutes(router, factory, getUserId) {
384
408
  ],
385
409
  });
386
410
  } else {
387
- res.json({status: 'ok'});
411
+ res.json({ status: 'ok' });
388
412
  }
389
413
  })
390
414
  );
@@ -409,7 +433,7 @@ function setEntityRoutes(router, factory, getUserId) {
409
433
  catchAsyncError(async (req, res) => {
410
434
  const params = checkRequiredParams(req.params, [
411
435
  'entityId',
412
- getUserId(req)
436
+ getUserId(req),
413
437
  ]);
414
438
  const module = await moduleFactory.getModuleInstanceFromEntityId(
415
439
  params.entityId,
@@ -428,7 +452,7 @@ function setEntityRoutes(router, factory, getUserId) {
428
452
  catchAsyncError(async (req, res) => {
429
453
  const params = checkRequiredParams(req.params, [
430
454
  'entityId',
431
- getUserId(req)
455
+ getUserId(req),
432
456
  ]);
433
457
  const module = await moduleFactory.getModuleInstanceFromEntityId(
434
458
  params.entityId,
@@ -1,6 +1,5 @@
1
1
  const { RequiredPropertyError } = require('../errors');
2
2
  const { get, getAndVerifyType } = require('../assertions');
3
- const { ModuleManager } = require('../module-plugin');
4
3
 
5
4
  class Options {
6
5
  constructor(params) {
@@ -18,7 +17,7 @@ class Options {
18
17
  }
19
18
 
20
19
  this.display = {};
21
- this.display.name = get(params.display, 'name');
20
+ this.display.name = get(params.display, 'label');
22
21
  this.display.description = get(params.display, 'description');
23
22
  this.display.detailsUrl = get(params.display, 'detailsUrl');
24
23
  this.display.icon = get(params.display, 'icon');
@@ -26,7 +25,7 @@ class Options {
26
25
 
27
26
  get() {
28
27
  return {
29
- type: this.module.getName(),
28
+ type: this.module.definition.getName(),
30
29
 
31
30
  // Flag for if the User can configure any settings
32
31
  hasUserConfig: this.hasUserConfig,
@@ -25,15 +25,14 @@
25
25
  // 1. Add definition of expected params to API Class (or could just be credential?)
26
26
  // 2.
27
27
 
28
-
29
28
  const { Delegate } = require('../core');
30
29
  const { get } = require('../assertions');
31
30
  const _ = require('lodash');
32
- const {flushDebugLog} = require('../logs');
31
+ const { flushDebugLog } = require('../logs');
33
32
  const { Credential } = require('./credential');
34
33
  const { Entity } = require('./entity');
35
34
  const { mongoose } = require('../database/mongoose');
36
- const {ModuleConstants} = require("./ModuleConstants");
35
+ const { ModuleConstants } = require('./ModuleConstants');
37
36
 
38
37
  class Auther extends Delegate {
39
38
  static validateDefinition(definition) {
@@ -55,21 +54,35 @@ class Auther extends Delegate {
55
54
  if (!definition.requiredAuthMethods) {
56
55
  throw new Error('Auther definition requires requiredAuthMethods');
57
56
  } else {
58
- if (definition.API.requesterType === ModuleConstants.authType.oauth2 &&
59
- !definition.requiredAuthMethods.getToken) {
60
- throw new Error('Auther definition requires requiredAuthMethods.getToken');
57
+ if (
58
+ definition.API.requesterType ===
59
+ ModuleConstants.authType.oauth2 &&
60
+ !definition.requiredAuthMethods.getToken
61
+ ) {
62
+ throw new Error(
63
+ 'Auther definition requires requiredAuthMethods.getToken'
64
+ );
61
65
  }
62
66
  if (!definition.requiredAuthMethods.getEntityDetails) {
63
- throw new Error('Auther definition requires requiredAuthMethods.getEntityDetails');
67
+ throw new Error(
68
+ 'Auther definition requires requiredAuthMethods.getEntityDetails'
69
+ );
64
70
  }
65
71
  if (!definition.requiredAuthMethods.getCredentialDetails) {
66
- throw new Error('Auther definition requires requiredAuthMethods.getCredentialDetails');
72
+ throw new Error(
73
+ 'Auther definition requires requiredAuthMethods.getCredentialDetails'
74
+ );
67
75
  }
68
76
  if (!definition.requiredAuthMethods.apiPropertiesToPersist) {
69
- throw new Error('Auther definition requires requiredAuthMethods.apiPropertiesToPersist');
70
- } else if (definition.Credential){
71
- for (const prop of definition.requiredAuthMethods.apiPropertiesToPersist?.credential) {
72
- if (!definition.Credential.schema.paths.hasOwnProperty(prop)) {
77
+ throw new Error(
78
+ 'Auther definition requires requiredAuthMethods.apiPropertiesToPersist'
79
+ );
80
+ } else if (definition.Credential) {
81
+ for (const prop of definition.requiredAuthMethods
82
+ .apiPropertiesToPersist?.credential) {
83
+ if (
84
+ !definition.Credential.schema.paths.hasOwnProperty(prop)
85
+ ) {
73
86
  throw new Error(
74
87
  `Auther definition requires Credential schema to have property ${prop}`
75
88
  );
@@ -77,7 +90,9 @@ class Auther extends Delegate {
77
90
  }
78
91
  }
79
92
  if (!definition.requiredAuthMethods.testAuthRequest) {
80
- throw new Error('Auther definition requires requiredAuthMethods.testAuth');
93
+ throw new Error(
94
+ 'Auther definition requires requiredAuthMethods.testAuth'
95
+ );
81
96
  }
82
97
  }
83
98
  }
@@ -97,14 +112,17 @@ class Auther extends Delegate {
97
112
  this.name = definition.moduleName;
98
113
  this.modelName = definition.modelName;
99
114
  this.apiClass = definition.API;
100
- this.CredentialModel = definition.Credential || this.getCredentialModel();
115
+ this.CredentialModel =
116
+ definition.Credential || this.getCredentialModel();
101
117
  this.EntityModel = definition.Entity || this.getEntityModel();
102
118
  }
103
119
 
104
120
  static async getInstance(params) {
105
121
  const instance = new this(params);
106
122
  if (params.entityId) {
107
- instance.entity = await instance.EntityModel.findById(params.entityId);
123
+ instance.entity = await instance.EntityModel.findById(
124
+ params.entityId
125
+ );
108
126
  instance.credential = await instance.CredentialModel.findById(
109
127
  instance.entity.credential
110
128
  );
@@ -132,7 +150,7 @@ class Auther extends Delegate {
132
150
  }
133
151
 
134
152
  static getEntityModelFromDefinition(definition) {
135
- const partialModule = new this({definition});
153
+ const partialModule = new this({ definition });
136
154
  return partialModule.getEntityModel();
137
155
  }
138
156
 
@@ -151,30 +169,38 @@ class Auther extends Delegate {
151
169
  getEntityModel() {
152
170
  if (!this.EntityModel) {
153
171
  const prefix = this.modelName ?? _.upperFirst(this.getName());
154
- const arrayToDefaultObject = (array, defaultValue) => _.mapValues(_.keyBy(array), () => defaultValue);
155
- const schema = new mongoose.Schema(arrayToDefaultObject(this.apiPropertiesToPersist.entity, {
156
- type: mongoose.Schema.Types.Mixed,
157
- trim: true,
158
- }));
172
+ const arrayToDefaultObject = (array, defaultValue) =>
173
+ _.mapValues(_.keyBy(array), () => defaultValue);
174
+ const schema = new mongoose.Schema(
175
+ arrayToDefaultObject(this.apiPropertiesToPersist.entity, {
176
+ type: mongoose.Schema.Types.Mixed,
177
+ trim: true,
178
+ })
179
+ );
159
180
  const name = `${prefix}Entity`;
160
181
  this.EntityModel =
161
- Entity.discriminators?.[name] || Entity.discriminator(name, schema);
182
+ Entity.discriminators?.[name] ||
183
+ Entity.discriminator(name, schema);
162
184
  }
163
185
  return this.EntityModel;
164
186
  }
165
187
 
166
188
  getCredentialModel() {
167
189
  if (!this.CredentialModel) {
168
- const arrayToDefaultObject = (array, defaultValue) => _.mapValues(_.keyBy(array), () => defaultValue);
169
- const schema = new mongoose.Schema(arrayToDefaultObject(this.apiPropertiesToPersist.credential, {
170
- type: mongoose.Schema.Types.Mixed,
171
- trim: true,
172
- lhEncrypt: true
173
- }));
190
+ const arrayToDefaultObject = (array, defaultValue) =>
191
+ _.mapValues(_.keyBy(array), () => defaultValue);
192
+ const schema = new mongoose.Schema(
193
+ arrayToDefaultObject(this.apiPropertiesToPersist.credential, {
194
+ type: mongoose.Schema.Types.Mixed,
195
+ trim: true,
196
+ lhEncrypt: true,
197
+ })
198
+ );
174
199
  const prefix = this.modelName ?? _.upperFirst(this.getName());
175
200
  const name = `${prefix}Credential`;
176
201
  this.CredentialModel =
177
- Credential.discriminators?.[name] || Credential.discriminator(name, schema);
202
+ Credential.discriminators?.[name] ||
203
+ Credential.discriminator(name, schema);
178
204
  }
179
205
  return this.CredentialModel;
180
206
  }
@@ -197,7 +223,10 @@ class Auther extends Delegate {
197
223
  async validateAuthorizationRequirements() {
198
224
  const requirements = await this.getAuthorizationRequirements();
199
225
  let valid = true;
200
- if (['oauth1', 'oauth2'].includes(requirements.type) && !requirements.url) {
226
+ if (
227
+ ['oauth1', 'oauth2'].includes(requirements.type) &&
228
+ !requirements.url
229
+ ) {
201
230
  valid = false;
202
231
  }
203
232
  return valid;
@@ -238,20 +267,32 @@ class Auther extends Delegate {
238
267
  throw new Error('Authorization failed');
239
268
  }
240
269
  const entityDetails = await this.getEntityDetails(
241
- this.api, params, tokenResponse, this.userId
270
+ this.api,
271
+ params,
272
+ tokenResponse,
273
+ this.userId
274
+ );
275
+ Object.assign(
276
+ entityDetails.details,
277
+ this.apiParamsFromEntity(this.api)
242
278
  );
243
- Object.assign(entityDetails.details, this.apiParamsFromEntity(this.api));
244
279
  await this.findOrCreateEntity(entityDetails);
245
280
  return {
246
281
  credential_id: this.credential.id,
247
282
  entity_id: this.entity.id,
248
283
  type: this.getName(),
249
- }
284
+ };
250
285
  }
251
286
 
252
287
  async onTokenUpdate() {
253
- const credentialDetails = await this.getCredentialDetails(this.api, this.userId);
254
- Object.assign(credentialDetails.details, this.apiParamsFromCredential(this.api));
288
+ const credentialDetails = await this.getCredentialDetails(
289
+ this.api,
290
+ this.userId
291
+ );
292
+ Object.assign(
293
+ credentialDetails.details,
294
+ this.apiParamsFromCredential(this.api)
295
+ );
255
296
  credentialDetails.details.auth_is_valid = true;
256
297
  await this.updateOrCreateCredential(credentialDetails);
257
298
  }
@@ -259,11 +300,9 @@ class Auther extends Delegate {
259
300
  async receiveNotification(notifier, delegateString, object = null) {
260
301
  if (delegateString === this.api.DLGT_TOKEN_UPDATE) {
261
302
  await this.onTokenUpdate();
262
- }
263
- else if (delegateString === this.api.DLGT_TOKEN_DEAUTHORIZED) {
303
+ } else if (delegateString === this.api.DLGT_TOKEN_DEAUTHORIZED) {
264
304
  await this.deauthorize();
265
- }
266
- else if (delegateString === this.api.DLGT_INVALID_AUTH) {
305
+ } else if (delegateString === this.api.DLGT_INVALID_AUTH) {
267
306
  await this.markCredentialsInvalid();
268
307
  }
269
308
  }
@@ -286,10 +325,10 @@ class Auther extends Delegate {
286
325
  const search = await this.EntityModel.find(identifiers);
287
326
  if (search.length > 1) {
288
327
  throw new Error(
289
- 'Multiple entities found with the same identifiers: ' + JSON.stringify(identifiers)
328
+ 'Multiple entities found with the same identifiers: ' +
329
+ JSON.stringify(identifiers)
290
330
  );
291
- }
292
- else if (search.length === 0) {
331
+ } else if (search.length === 0) {
293
332
  this.entity = await this.EntityModel.create({
294
333
  credential: this.credential.id,
295
334
  ...details,
@@ -308,25 +347,27 @@ class Auther extends Delegate {
308
347
  const identifiers = get(credentialDetails, 'identifiers');
309
348
  const details = get(credentialDetails, 'details');
310
349
 
311
- if (!this.credential){
312
- const credentialSearch = await this.CredentialModel.find(identifiers);
350
+ if (!this.credential) {
351
+ const credentialSearch = await this.CredentialModel.find(
352
+ identifiers
353
+ );
313
354
  if (credentialSearch.length > 1) {
314
- throw new Error(`Multiple credentials found with same identifiers: ${identifiers}`);
315
- }
316
- else if (credentialSearch.length === 1) {
355
+ throw new Error(
356
+ `Multiple credentials found with same identifiers: ${identifiers}`
357
+ );
358
+ } else if (credentialSearch.length === 1) {
317
359
  // found exactly one credential with these identifiers
318
360
  this.credential = credentialSearch[0];
319
- }
320
- else {
361
+ } else {
321
362
  // found no credential with these identifiers (match none for insert)
322
- this.credential = {$exists: false};
363
+ this.credential = { $exists: false };
323
364
  }
324
365
  }
325
366
  // update credential or create if none was found
326
367
  this.credential = await this.CredentialModel.findOneAndUpdate(
327
- {_id: this.credential},
328
- {$set: {...identifiers, ...details}},
329
- {useFindAndModify: true, new: true, upsert: true}
368
+ { _id: this.credential },
369
+ { $set: { ...identifiers, ...details } },
370
+ { useFindAndModify: true, new: true, upsert: true }
330
371
  );
331
372
  }
332
373
 
@@ -340,7 +381,9 @@ class Auther extends Delegate {
340
381
  async deauthorize() {
341
382
  this.api = new this.apiClass();
342
383
  if (this.entity?.credential) {
343
- await this.CredentialModel.deleteOne({ _id: this.entity.credential });
384
+ await this.CredentialModel.deleteOne({
385
+ _id: this.entity.credential,
386
+ });
344
387
  this.entity.credential = undefined;
345
388
  await this.entity.save();
346
389
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0-next.1",
4
+ "version": "2.0.0-next.11",
5
5
  "dependencies": {
6
6
  "@hapi/boom": "^10.0.1",
7
7
  "aws-sdk": "^2.1200.0",
@@ -9,16 +9,16 @@
9
9
  "common-tags": "^1.8.2",
10
10
  "express": "^4.18.2",
11
11
  "express-async-handler": "^1.2.0",
12
- "lodash": "^4.17.21",
13
- "lodash.get": "^4.4.2",
12
+ "lodash": "4.17.21",
13
+ "lodash.get": "4.4.2",
14
14
  "mongoose": "6.11.6",
15
15
  "node-fetch": "^2.6.7"
16
16
  },
17
17
  "devDependencies": {
18
- "@friggframework/eslint-config": "2.0.0-next.1",
19
- "@friggframework/prettier-config": "2.0.0-next.1",
20
- "@friggframework/test": "2.0.0-next.1",
21
- "@types/lodash": "^4.14.191",
18
+ "@friggframework/eslint-config": "2.0.0-next.11",
19
+ "@friggframework/prettier-config": "2.0.0-next.11",
20
+ "@friggframework/test": "2.0.0-next.11",
21
+ "@types/lodash": "4.17.15",
22
22
  "@typescript-eslint/eslint-plugin": "^8.0.0",
23
23
  "chai": "^4.3.6",
24
24
  "eslint": "^8.22.0",
@@ -48,5 +48,5 @@
48
48
  },
49
49
  "homepage": "https://github.com/friggframework/frigg#readme",
50
50
  "description": "",
51
- "gitHead": "44b619e288b23d879673518076cf9e4acacb3c71"
51
+ "gitHead": "984b8e4323df5f3422b6da5416a0d15be209f4d3"
52
52
  }