@app-connect/core 1.7.8 → 1.7.10
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/handlers/auth.js +10 -4
- package/handlers/contact.js +42 -9
- package/handlers/log.js +4 -4
- package/index.js +101 -79
- package/lib/oauth.js +0 -2
- package/models/accountDataModel.js +34 -0
- package/package.json +70 -69
- package/releaseNotes.json +24 -0
- package/test/connector/registry.test.js +145 -0
- package/test/handlers/admin.test.js +583 -0
- package/test/handlers/auth.test.js +355 -0
- package/test/handlers/contact.test.js +852 -0
- package/test/handlers/log.test.js +868 -0
- package/test/lib/callLogComposer.test.js +1231 -0
- package/test/lib/debugTracer.test.js +328 -0
- package/test/lib/oauth.test.js +359 -0
- package/test/lib/ringcentral.test.js +473 -0
- package/test/lib/util.test.js +282 -0
- package/test/models/accountDataModel.test.js +98 -0
- package/test/models/dynamo/connectorSchema.test.js +189 -0
- package/test/models/models.test.js +539 -0
- package/test/setup.js +176 -176
package/lib/oauth.js
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const Sequelize = require('sequelize');
|
|
2
|
+
const { sequelize } = require('./sequelize');
|
|
3
|
+
|
|
4
|
+
// Model for account data with composite primary key
|
|
5
|
+
exports.AccountDataModel = sequelize.define('accountData', {
|
|
6
|
+
rcAccountId: {
|
|
7
|
+
type: Sequelize.STRING,
|
|
8
|
+
primaryKey: true,
|
|
9
|
+
},
|
|
10
|
+
platformName: {
|
|
11
|
+
type: Sequelize.STRING,
|
|
12
|
+
primaryKey: true,
|
|
13
|
+
},
|
|
14
|
+
dataKey: {
|
|
15
|
+
type: Sequelize.STRING,
|
|
16
|
+
primaryKey: true,
|
|
17
|
+
},
|
|
18
|
+
data: {
|
|
19
|
+
type: Sequelize.JSON,
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
exports.getOrRefreshAccountData = async function getOrRefreshAccountData({ rcAccountId, platformName, dataKey, forceRefresh, fetchFn }) {
|
|
24
|
+
const existing = await exports.AccountDataModel.findOne({ where: { rcAccountId, platformName, dataKey } });
|
|
25
|
+
if (existing && !forceRefresh) return existing.data;
|
|
26
|
+
|
|
27
|
+
const fresh = await fetchFn();
|
|
28
|
+
if (existing) {
|
|
29
|
+
await existing.update({ data: fresh });
|
|
30
|
+
} else {
|
|
31
|
+
await exports.AccountDataModel.create({ rcAccountId, platformName, dataKey, data: fresh });
|
|
32
|
+
}
|
|
33
|
+
return fresh;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,69 +1,70 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@app-connect/core",
|
|
3
|
-
"version": "1.7.
|
|
4
|
-
"description": "RingCentral App Connect Core",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"RingCentral",
|
|
12
|
-
"App Connect"
|
|
13
|
-
],
|
|
14
|
-
"author": "RingCentral Labs",
|
|
15
|
-
"license": "MIT",
|
|
16
|
-
"peerDependencies": {
|
|
17
|
-
"axios": "^1.12.2",
|
|
18
|
-
"express": "^4.
|
|
19
|
-
"moment": "^2.29.4",
|
|
20
|
-
"moment-timezone": "^0.5.39",
|
|
21
|
-
"pg": "^8.8.0",
|
|
22
|
-
"sequelize": "^6.29.0"
|
|
23
|
-
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@aws-sdk/client-dynamodb": "^3.751.0",
|
|
26
|
-
"@aws-sdk/client-s3": "^3.947.0",
|
|
27
|
-
"@aws-sdk/s3-request-presigner": "^3.947.0",
|
|
28
|
-
"body-parser": "^1.20.
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"test
|
|
43
|
-
"test:
|
|
44
|
-
"test:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"@
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"moment
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@app-connect/core",
|
|
3
|
+
"version": "1.7.10",
|
|
4
|
+
"description": "RingCentral App Connect Core",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"RingCentral",
|
|
12
|
+
"App Connect"
|
|
13
|
+
],
|
|
14
|
+
"author": "RingCentral Labs",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"axios": "^1.12.2",
|
|
18
|
+
"express": "^4.22.1",
|
|
19
|
+
"moment": "^2.29.4",
|
|
20
|
+
"moment-timezone": "^0.5.39",
|
|
21
|
+
"pg": "^8.8.0",
|
|
22
|
+
"sequelize": "^6.29.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@aws-sdk/client-dynamodb": "^3.751.0",
|
|
26
|
+
"@aws-sdk/client-s3": "^3.947.0",
|
|
27
|
+
"@aws-sdk/s3-request-presigner": "^3.947.0",
|
|
28
|
+
"body-parser": "^1.20.4",
|
|
29
|
+
"body-parser-xml": "^2.0.5",
|
|
30
|
+
"client-oauth2": "^4.3.3",
|
|
31
|
+
"cors": "^2.8.5",
|
|
32
|
+
"country-state-city": "^3.2.1",
|
|
33
|
+
"dotenv": "^16.0.3",
|
|
34
|
+
"dynamoose": "^4.0.3",
|
|
35
|
+
"jsonwebtoken": "^9.0.0",
|
|
36
|
+
"mixpanel": "^0.18.0",
|
|
37
|
+
"shortid": "^2.2.17",
|
|
38
|
+
"tz-lookup": "^6.1.25",
|
|
39
|
+
"ua-parser-js": "^1.0.38"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "jest",
|
|
43
|
+
"test:watch": "jest --watch",
|
|
44
|
+
"test:coverage": "jest --coverage",
|
|
45
|
+
"test:ci": "jest --ci --coverage --watchAll=false"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^9.22.0",
|
|
49
|
+
"@octokit/rest": "^19.0.5",
|
|
50
|
+
"axios": "^1.12.2",
|
|
51
|
+
"eslint": "^9.22.0",
|
|
52
|
+
"express": "^4.22.1",
|
|
53
|
+
"globals": "^16.0.0",
|
|
54
|
+
"jest": "^29.3.1",
|
|
55
|
+
"moment": "^2.29.4",
|
|
56
|
+
"moment-timezone": "^0.5.39",
|
|
57
|
+
"nock": "^13.2.9",
|
|
58
|
+
"pg": "^8.8.0",
|
|
59
|
+
"sequelize": "^6.29.0",
|
|
60
|
+
"sqlite3": "^5.1.2",
|
|
61
|
+
"supertest": "^6.3.1"
|
|
62
|
+
},
|
|
63
|
+
"overrides": {
|
|
64
|
+
"js-object-utilities": "2.2.1"
|
|
65
|
+
},
|
|
66
|
+
"bugs": {
|
|
67
|
+
"url": "https://github.com/ringcentral/rc-unified-crm-extension/issues"
|
|
68
|
+
},
|
|
69
|
+
"homepage": "https://github.com/ringcentral/rc-unified-crm-extension#readme"
|
|
70
|
+
}
|
package/releaseNotes.json
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.7.10": {
|
|
3
|
+
"global": [
|
|
4
|
+
{
|
|
5
|
+
"type": "Fix",
|
|
6
|
+
"description": "Upon completing warm transfer, it opens contact page for a second time"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"type": "New",
|
|
10
|
+
"description": "RingCX RingSense call logging event support for Server-side logging"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"1.7.9": {
|
|
15
|
+
"global": [
|
|
16
|
+
{
|
|
17
|
+
"type": "Better",
|
|
18
|
+
"description": "Contact match speed optimized"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "Better",
|
|
22
|
+
"description": "Minor improvements on calldown list"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
2
26
|
"1.7.8": {
|
|
3
27
|
"global": [
|
|
4
28
|
{
|
|
@@ -268,4 +268,149 @@ describe('ConnectorRegistry Interface Registration with Composition', () => {
|
|
|
268
268
|
expect(composedConnector.getAuthType).toBeDefined();
|
|
269
269
|
expect(await composedConnector.getAuthType()).toBe('apiKey');
|
|
270
270
|
});
|
|
271
|
+
|
|
272
|
+
test('should set and get default manifest', () => {
|
|
273
|
+
const defaultManifest = {
|
|
274
|
+
name: 'Default CRM',
|
|
275
|
+
version: '1.0.0',
|
|
276
|
+
features: ['call_logging', 'contact_sync']
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
connectorRegistry.setDefaultManifest(defaultManifest);
|
|
280
|
+
|
|
281
|
+
// Get manifest with fallback should return default
|
|
282
|
+
const manifest = connectorRegistry.getManifest('nonExistentPlatform', true);
|
|
283
|
+
expect(manifest).toEqual(defaultManifest);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('should throw error when getting manifest without fallback and platform not found', () => {
|
|
287
|
+
expect(() => {
|
|
288
|
+
connectorRegistry.getManifest('nonExistentPlatform', false);
|
|
289
|
+
}).toThrow('Manifest not found for platform: nonExistentPlatform');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('should throw error when getting manifest with fallback but no default set', () => {
|
|
293
|
+
connectorRegistry.manifests.clear();
|
|
294
|
+
expect(() => {
|
|
295
|
+
connectorRegistry.getManifest('nonExistentPlatform', true);
|
|
296
|
+
}).toThrow('Manifest not found for platform: nonExistentPlatform');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('should register connector with manifest', () => {
|
|
300
|
+
const mockConnector = {
|
|
301
|
+
getAuthType: () => 'apiKey',
|
|
302
|
+
createCallLog: jest.fn(),
|
|
303
|
+
updateCallLog: jest.fn()
|
|
304
|
+
};
|
|
305
|
+
const manifest = {
|
|
306
|
+
name: 'Test CRM',
|
|
307
|
+
version: '2.0.0',
|
|
308
|
+
authType: 'oauth'
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
connectorRegistry.registerConnector('testPlatformWithManifest', mockConnector, manifest);
|
|
312
|
+
|
|
313
|
+
const retrievedManifest = connectorRegistry.getManifest('testPlatformWithManifest');
|
|
314
|
+
expect(retrievedManifest).toEqual(manifest);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('should set and get release notes', () => {
|
|
318
|
+
const releaseNotes = {
|
|
319
|
+
version: '1.5.0',
|
|
320
|
+
date: '2024-01-15',
|
|
321
|
+
changes: ['Bug fixes', 'New features']
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
connectorRegistry.setReleaseNotes(releaseNotes);
|
|
325
|
+
|
|
326
|
+
// getReleaseNotes currently returns the same object regardless of platform
|
|
327
|
+
const notes = connectorRegistry.getReleaseNotes('anyPlatform');
|
|
328
|
+
expect(notes).toEqual(releaseNotes);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('should get registered platforms', () => {
|
|
332
|
+
const connector1 = {
|
|
333
|
+
getAuthType: () => 'apiKey',
|
|
334
|
+
createCallLog: jest.fn(),
|
|
335
|
+
updateCallLog: jest.fn()
|
|
336
|
+
};
|
|
337
|
+
const connector2 = {
|
|
338
|
+
getAuthType: () => 'oauth',
|
|
339
|
+
createCallLog: jest.fn(),
|
|
340
|
+
updateCallLog: jest.fn()
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
connectorRegistry.registerConnector('platform1', connector1);
|
|
344
|
+
connectorRegistry.registerConnector('platform2', connector2);
|
|
345
|
+
|
|
346
|
+
const platforms = connectorRegistry.getRegisteredPlatforms();
|
|
347
|
+
expect(platforms).toContain('platform1');
|
|
348
|
+
expect(platforms).toContain('platform2');
|
|
349
|
+
expect(platforms).toHaveLength(2);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('should check if platform is registered', () => {
|
|
353
|
+
const mockConnector = {
|
|
354
|
+
getAuthType: () => 'apiKey',
|
|
355
|
+
createCallLog: jest.fn(),
|
|
356
|
+
updateCallLog: jest.fn()
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
connectorRegistry.registerConnector('registeredPlatform', mockConnector);
|
|
360
|
+
|
|
361
|
+
expect(connectorRegistry.isRegistered('registeredPlatform')).toBe(true);
|
|
362
|
+
expect(connectorRegistry.isRegistered('unregisteredPlatform')).toBe(false);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('should throw error for original connector when not found', () => {
|
|
366
|
+
expect(() => {
|
|
367
|
+
connectorRegistry.getOriginalConnector('nonExistentPlatform');
|
|
368
|
+
}).toThrow('Connector not found for platform: nonExistentPlatform');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test('should validate connector interface with missing required methods', () => {
|
|
372
|
+
const incompleteConnector = {
|
|
373
|
+
getAuthType: () => 'apiKey',
|
|
374
|
+
createCallLog: jest.fn()
|
|
375
|
+
// Missing updateCallLog
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
expect(() => {
|
|
379
|
+
connectorRegistry.registerConnector('incompletePlatform', incompleteConnector);
|
|
380
|
+
}).toThrow('Connector incompletePlatform missing required method: updateCallLog');
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('should return proxy connector when platform not found but proxy exists', () => {
|
|
384
|
+
const proxyConnector = {
|
|
385
|
+
getAuthType: () => 'proxy',
|
|
386
|
+
createCallLog: jest.fn(),
|
|
387
|
+
updateCallLog: jest.fn(),
|
|
388
|
+
proxy: true
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
connectorRegistry.registerConnector('proxy', proxyConnector);
|
|
392
|
+
|
|
393
|
+
const connector = connectorRegistry.getConnector('unknownPlatformWithProxy');
|
|
394
|
+
expect(connector).toBe(proxyConnector);
|
|
395
|
+
expect(connector.proxy).toBe(true);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('should handle getAuthType error in getConnectorCapabilities', async () => {
|
|
399
|
+
const mockConnector = {
|
|
400
|
+
getAuthType: jest.fn().mockRejectedValue(new Error('Auth type error')),
|
|
401
|
+
createCallLog: jest.fn(),
|
|
402
|
+
updateCallLog: jest.fn()
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
connectorRegistry.registerConnector('errorPlatform', mockConnector);
|
|
406
|
+
|
|
407
|
+
const capabilities = await connectorRegistry.getConnectorCapabilities('errorPlatform');
|
|
408
|
+
expect(capabilities.authType).toBe('unknown');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('should handle unregistering non-existent interface gracefully', () => {
|
|
412
|
+
// This should not throw
|
|
413
|
+
connectorRegistry.unregisterConnectorInterface('nonExistentPlatform', 'nonExistentInterface');
|
|
414
|
+
expect(connectorRegistry.hasPlatformInterface('nonExistentPlatform', 'nonExistentInterface')).toBe(false);
|
|
415
|
+
});
|
|
271
416
|
});
|