@app-connect/core 1.7.1 → 1.7.3
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/README.md +1 -1
- package/connector/proxy/engine.js +163 -0
- package/connector/proxy/index.js +492 -0
- package/connector/registry.js +11 -8
- package/handlers/admin.js +12 -3
- package/handlers/auth.js +20 -10
- package/handlers/contact.js +26 -9
- package/handlers/disposition.js +19 -6
- package/handlers/log.js +62 -18
- package/index.js +48 -2
- package/lib/callLogComposer.js +196 -10
- package/lib/oauth.js +0 -1
- package/models/dynamo/connectorSchema.js +146 -0
- package/package.json +1 -1
- package/releaseNotes.json +40 -0
- package/test/connector/proxy/engine.test.js +93 -0
- package/test/connector/proxy/index.test.js +279 -0
- package/test/connector/proxy/sample.json +161 -0
- package/test/{adapter → connector}/registry.test.js +4 -4
- package/test/handlers/auth.test.js +3 -1
package/lib/callLogComposer.js
CHANGED
|
@@ -41,6 +41,11 @@ async function composeCallLog(params) {
|
|
|
41
41
|
startTime,
|
|
42
42
|
duration,
|
|
43
43
|
result,
|
|
44
|
+
ringSenseTranscript,
|
|
45
|
+
ringSenseSummary,
|
|
46
|
+
ringSenseAIScore,
|
|
47
|
+
ringSenseBulletedSummary,
|
|
48
|
+
ringSenseLink,
|
|
44
49
|
platform
|
|
45
50
|
} = params;
|
|
46
51
|
|
|
@@ -62,14 +67,18 @@ async function composeCallLog(params) {
|
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
if (userSettings?.addRingCentralUserName?.value) {
|
|
65
|
-
const ringcentralUsername = (callLog.direction === 'Inbound' ? callLog?.to?.name : callLog?.from?.name) ??
|
|
66
|
-
|
|
70
|
+
const ringcentralUsername = (callLog.direction === 'Inbound' ? callLog?.to?.name : callLog?.from?.name) ?? null;
|
|
71
|
+
if (ringcentralUsername) {
|
|
72
|
+
body = upsertRingCentralUserName({ body, userName: ringcentralUsername, logFormat });
|
|
73
|
+
}
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
if (userSettings?.addRingCentralNumber?.value ?? false) {
|
|
77
|
+
const ringcentralNumber = callLog.direction === 'Inbound' ? callLog?.to?.phoneNumber : callLog?.from?.phoneNumber;
|
|
78
|
+
if (ringcentralNumber) {
|
|
79
|
+
const ringcentralExtensionNumber = callLog.direction === 'Inbound' ? callLog?.from?.extensionNumber : callLog?.to?.extensionNumber;
|
|
80
|
+
body = upsertRingCentralNumberAndExtension({ body, number: ringcentralNumber, extension: ringcentralExtensionNumber ?? '', logFormat });
|
|
81
|
+
}
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
if (subject && (userSettings?.addCallLogSubject?.value ?? true)) {
|
|
@@ -115,6 +124,26 @@ async function composeCallLog(params) {
|
|
|
115
124
|
body = upsertTranscript({ body, transcript, logFormat });
|
|
116
125
|
}
|
|
117
126
|
|
|
127
|
+
if (ringSenseTranscript && (userSettings?.addCallLogRingSenseRecordingTranscript?.value ?? true)) {
|
|
128
|
+
body = upsertRingSenseTranscript({ body, transcript: ringSenseTranscript, logFormat });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (ringSenseSummary && (userSettings?.addCallLogRingSenseRecordingSummary?.value ?? true)) {
|
|
132
|
+
body = upsertRingSenseSummary({ body, summary: ringSenseSummary, logFormat });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (ringSenseAIScore && (userSettings?.addCallLogRingSenseRecordingAIScore?.value ?? true)) {
|
|
136
|
+
body = upsertRingSenseAIScore({ body, score: ringSenseAIScore, logFormat });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (ringSenseBulletedSummary && (userSettings?.addCallLogRingSenseRecordingBulletedSummary?.value ?? true)) {
|
|
140
|
+
body = upsertRingSenseBulletedSummary({ body, summary: ringSenseBulletedSummary, logFormat });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (ringSenseLink && (userSettings?.addCallLogRingSenseRecordingLink?.value ?? true)) {
|
|
144
|
+
body = upsertRingSenseLink({ body, link: ringSenseLink, logFormat });
|
|
145
|
+
}
|
|
146
|
+
|
|
118
147
|
if (callLog?.legs && (userSettings?.addCallLogLegs?.value ?? true)) {
|
|
119
148
|
body = upsertLegs({ body, legs: callLog.legs, logFormat });
|
|
120
149
|
}
|
|
@@ -382,7 +411,7 @@ function upsertCallDuration({ body, duration, logFormat }) {
|
|
|
382
411
|
// More flexible regex that handles both with and without newlines
|
|
383
412
|
const durationRegex = /- Duration: ([^\n-]+)(?=\n-|\n|$)/;
|
|
384
413
|
if (durationRegex.test(result)) {
|
|
385
|
-
result = result.replace(durationRegex, `- Duration: ${formattedDuration}
|
|
414
|
+
result = result.replace(durationRegex, `- Duration: ${formattedDuration}`);
|
|
386
415
|
} else {
|
|
387
416
|
result += `- Duration: ${formattedDuration}\n`;
|
|
388
417
|
}
|
|
@@ -415,7 +444,7 @@ function upsertCallResult({ body, result, logFormat }) {
|
|
|
415
444
|
// More flexible regex that handles both with and without newlines
|
|
416
445
|
const resultRegex = /- Result: ([^\n-]+)(?=\n-|\n|$)/;
|
|
417
446
|
if (resultRegex.test(bodyResult)) {
|
|
418
|
-
bodyResult = bodyResult.replace(resultRegex, `- Result: ${result}
|
|
447
|
+
bodyResult = bodyResult.replace(resultRegex, `- Result: ${result}`);
|
|
419
448
|
} else {
|
|
420
449
|
bodyResult += `- Result: ${result}\n`;
|
|
421
450
|
}
|
|
@@ -429,8 +458,8 @@ function upsertCallRecording({ body, recordingLink, logFormat }) {
|
|
|
429
458
|
let result = body;
|
|
430
459
|
|
|
431
460
|
if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
|
|
432
|
-
|
|
433
|
-
|
|
461
|
+
// More flexible regex that handles both <li> wrapped and unwrapped content, and existing <a> anchors
|
|
462
|
+
const recordingLinkRegex = /(?:<li>)?<b>Call recording link<\/b>:\s*(?:<a[^>]*>[^<]*<\/a>|[^<]+)(?:<\/li>|(?=<|$))/i;
|
|
434
463
|
if (recordingLink) {
|
|
435
464
|
if (recordingLinkRegex.test(result)) {
|
|
436
465
|
if (recordingLink.startsWith('http')) {
|
|
@@ -615,6 +644,158 @@ function upsertLegs({ body, legs, logFormat }) {
|
|
|
615
644
|
return result;
|
|
616
645
|
}
|
|
617
646
|
|
|
647
|
+
function upsertRingSenseTranscript({ body, transcript, logFormat }) {
|
|
648
|
+
if (!transcript) return body;
|
|
649
|
+
|
|
650
|
+
let result = body;
|
|
651
|
+
const clearedTranscript = transcript.replace(/\n+$/, '');
|
|
652
|
+
if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
|
|
653
|
+
const formattedTranscript = clearedTranscript.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
|
654
|
+
const transcriptRegex = /<div><b>RingSense transcript<\/b><br>(.+?)<\/div>/;
|
|
655
|
+
if (transcriptRegex.test(result)) {
|
|
656
|
+
result = result.replace(transcriptRegex, `<div><b>RingSense transcript</b><br>${formattedTranscript}</div>`);
|
|
657
|
+
} else {
|
|
658
|
+
result += `<div><b>RingSense transcript</b><br>${formattedTranscript}</div>`;
|
|
659
|
+
}
|
|
660
|
+
} else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
|
|
661
|
+
const transcriptRegex = /### RingSense transcript\n([\s\S]*?)(?=\n### |\n$|$)/;
|
|
662
|
+
if (transcriptRegex.test(result)) {
|
|
663
|
+
result = result.replace(transcriptRegex, `### RingSense transcript\n${clearedTranscript}\n`);
|
|
664
|
+
} else {
|
|
665
|
+
result += `### RingSense transcript\n${clearedTranscript}\n`;
|
|
666
|
+
}
|
|
667
|
+
} else {
|
|
668
|
+
const transcriptRegex = /- RingSense transcript:([\s\S]*?)--- END/;
|
|
669
|
+
if (transcriptRegex.test(result)) {
|
|
670
|
+
result = result.replace(transcriptRegex, `- RingSense transcript:\n${clearedTranscript}\n--- END`);
|
|
671
|
+
} else {
|
|
672
|
+
result += `- RingSense transcript:\n${clearedTranscript}\n--- END\n`;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return result;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function upsertRingSenseSummary({ body, summary, logFormat }) {
|
|
679
|
+
if (!summary) return body;
|
|
680
|
+
|
|
681
|
+
let result = body;
|
|
682
|
+
// remove new line in last line of summary
|
|
683
|
+
const clearedSummary = summary.replace(/\n+$/, '');
|
|
684
|
+
if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
|
|
685
|
+
const summaryRegex = /<div><b>RingSense summary<\/b><br>(.+?)<\/div>/;
|
|
686
|
+
const formattedSummary = clearedSummary.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
|
687
|
+
if (summaryRegex.test(result)) {
|
|
688
|
+
result = result.replace(summaryRegex, `<div><b>RingSense summary</b><br>${formattedSummary}</div>`);
|
|
689
|
+
} else {
|
|
690
|
+
result += `<div><b>RingSense summary</b><br>${formattedSummary}</div>`;
|
|
691
|
+
}
|
|
692
|
+
} else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
|
|
693
|
+
const summaryRegex = /### RingSense summary\n([\s\S]*?)(?=\n### |\n$|$)/;
|
|
694
|
+
if (summaryRegex.test(result)) {
|
|
695
|
+
result = result.replace(summaryRegex, `### RingSense summary\n${summary}\n`);
|
|
696
|
+
} else {
|
|
697
|
+
result += `### RingSense summary\n${summary}\n`;
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
const summaryRegex = /- RingSense summary:([\s\S]*?)--- END/;
|
|
701
|
+
if (summaryRegex.test(result)) {
|
|
702
|
+
result = result.replace(summaryRegex, `- RingSense summary:\n${summary}\n--- END`);
|
|
703
|
+
} else {
|
|
704
|
+
result += `- RingSense summary:\n${summary}\n--- END\n`;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return result;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function upsertRingSenseAIScore({ body, score, logFormat }) {
|
|
711
|
+
if (!score) return body;
|
|
712
|
+
|
|
713
|
+
let result = body;
|
|
714
|
+
if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
|
|
715
|
+
const scoreRegex = /(?:<li>)?<b>Call score<\/b>:\s*([^<\n]+)(?:<\/li>|(?=<|$))/i;
|
|
716
|
+
if (scoreRegex.test(result)) {
|
|
717
|
+
result = result.replace(scoreRegex, `<li><b>Call score</b>: ${score}</li>`);
|
|
718
|
+
} else {
|
|
719
|
+
result += `<li><b>Call score</b>: ${score}</li>`;
|
|
720
|
+
}
|
|
721
|
+
} else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
|
|
722
|
+
const scoreRegex = /\*\*Call score\*\*: [^\n]*\n*/;
|
|
723
|
+
if (scoreRegex.test(result)) {
|
|
724
|
+
result = result.replace(scoreRegex, `**Call score**: ${score}\n`);
|
|
725
|
+
} else {
|
|
726
|
+
result += `**Call score**: ${score}\n`;
|
|
727
|
+
}
|
|
728
|
+
} else {
|
|
729
|
+
const scoreRegex = /- Call score:\s*([^<\n]+)(?=\n|$)/i;
|
|
730
|
+
if (scoreRegex.test(result)) {
|
|
731
|
+
result = result.replace(scoreRegex, `- Call score: ${score}`);
|
|
732
|
+
} else {
|
|
733
|
+
result += `- Call score: ${score}\n`;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function upsertRingSenseBulletedSummary({ body, summary, logFormat }) {
|
|
740
|
+
if (!summary) return body;
|
|
741
|
+
|
|
742
|
+
let result = body;
|
|
743
|
+
const clearedSummary = summary.replace(/\n+$/, '');
|
|
744
|
+
if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
|
|
745
|
+
const summaryRegex = /<div><b>RingSense bulleted summary<\/b><br>(.+?)<\/div>/;
|
|
746
|
+
const formattedSummary = clearedSummary.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
|
747
|
+
if (summaryRegex.test(result)) {
|
|
748
|
+
result = result.replace(summaryRegex, `<div><b>RingSense bulleted summary</b><br>${formattedSummary}</div>`);
|
|
749
|
+
} else {
|
|
750
|
+
result += `<div><b>RingSense bulleted summary</b><br>${formattedSummary}</div>`;
|
|
751
|
+
}
|
|
752
|
+
} else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
|
|
753
|
+
const summaryRegex = /### RingSense bulleted summary\n([\s\S]*?)(?=\n### |\n$|$)/;
|
|
754
|
+
if (summaryRegex.test(result)) {
|
|
755
|
+
result = result.replace(summaryRegex, `### RingSense bulleted summary\n${summary}\n`);
|
|
756
|
+
} else {
|
|
757
|
+
result += `### RingSense bulleted summary\n${summary}\n`;
|
|
758
|
+
}
|
|
759
|
+
} else {
|
|
760
|
+
const summaryRegex = /- RingSense bulleted summary:\s*([^<\n]+)(?=\n|$)/i;
|
|
761
|
+
if (summaryRegex.test(result)) {
|
|
762
|
+
result = result.replace(summaryRegex, `- RingSense bulleted summary:\n${summary}\n--- END`);
|
|
763
|
+
} else {
|
|
764
|
+
result += `- RingSense bulleted summary:\n${summary}\n--- END\n`;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return result;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function upsertRingSenseLink({ body, link, logFormat }) {
|
|
771
|
+
if (!link) return body;
|
|
772
|
+
|
|
773
|
+
let result = body;
|
|
774
|
+
if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
|
|
775
|
+
const linkRegex = /(?:<li>)?<b>RingSense recording link<\/b>:\s*(?:<a[^>]*>[^<]*<\/a>|[^<]+)(?:<\/li>|(?=<|$))/i;
|
|
776
|
+
if (linkRegex.test(result)) {
|
|
777
|
+
result = result.replace(linkRegex, `<li><b>RingSense recording link</b>: <a target="_blank" href="${link}">open</a></li>`);
|
|
778
|
+
} else {
|
|
779
|
+
result += `<li><b>RingSense recording link</b>: <a target="_blank" href="${link}">open</a></li>`;
|
|
780
|
+
}
|
|
781
|
+
} else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
|
|
782
|
+
const linkRegex = /\*\*RingSense recording link\*\*:\s*([^<\n]+)(?=\n|$)/i;
|
|
783
|
+
if (linkRegex.test(result)) {
|
|
784
|
+
result = result.replace(linkRegex, `**RingSense recording link**: ${link}\n`);
|
|
785
|
+
} else {
|
|
786
|
+
result += `**RingSense recording link**: ${link}\n`;
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
const linkRegex = /- RingSense recording link:\s*([^<\n]+)(?=\n|$)/i;
|
|
790
|
+
if (linkRegex.test(result)) {
|
|
791
|
+
result = result.replace(linkRegex, `- RingSense recording link: ${link}`);
|
|
792
|
+
} else {
|
|
793
|
+
result += `- RingSense recording link: ${link}\n`;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
798
|
+
|
|
618
799
|
module.exports = {
|
|
619
800
|
composeCallLog,
|
|
620
801
|
// Export individual upsert functions for backward compatibility
|
|
@@ -631,4 +812,9 @@ module.exports = {
|
|
|
631
812
|
upsertAiNote,
|
|
632
813
|
upsertTranscript,
|
|
633
814
|
upsertLegs,
|
|
815
|
+
upsertRingSenseTranscript,
|
|
816
|
+
upsertRingSenseSummary,
|
|
817
|
+
upsertRingSenseAIScore,
|
|
818
|
+
upsertRingSenseBulletedSummary,
|
|
819
|
+
upsertRingSenseLink,
|
|
634
820
|
};
|
package/lib/oauth.js
CHANGED
|
@@ -3,7 +3,6 @@ const ClientOAuth2 = require('client-oauth2');
|
|
|
3
3
|
const moment = require('moment');
|
|
4
4
|
const { UserModel } = require('../models/userModel');
|
|
5
5
|
const connectorRegistry = require('../connector/registry');
|
|
6
|
-
const dynamoose = require('dynamoose');
|
|
7
6
|
|
|
8
7
|
// oauthApp strategy is default to 'code' which use credentials to get accessCode, then exchange for accessToken and refreshToken.
|
|
9
8
|
// To change to other strategies, please refer to: https://github.com/mulesoft-labs/js-client-oauth2
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const dynamoose = require('dynamoose');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
const CONNECTOR_STATUS = {
|
|
5
|
+
PRIVATE: 'private',
|
|
6
|
+
UNDER_REVIEW: 'under_review',
|
|
7
|
+
APPROVED: 'approved',
|
|
8
|
+
REJECTED: 'rejected',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const connectorSchema = new dynamoose.Schema({
|
|
12
|
+
accountId: {
|
|
13
|
+
type: String,
|
|
14
|
+
hashKey: true,
|
|
15
|
+
},
|
|
16
|
+
id: {
|
|
17
|
+
type: String,
|
|
18
|
+
rangeKey: true,
|
|
19
|
+
},
|
|
20
|
+
// Reference to original connector (for partition records)
|
|
21
|
+
originalAccountId: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: false, // Only set for partition records (under_review, approved)
|
|
24
|
+
},
|
|
25
|
+
// Basic Information
|
|
26
|
+
name: {
|
|
27
|
+
type: String,
|
|
28
|
+
required: true,
|
|
29
|
+
},
|
|
30
|
+
displayName: String,
|
|
31
|
+
description: String,
|
|
32
|
+
iconUrl: String,
|
|
33
|
+
// Status and Workflow
|
|
34
|
+
status: {
|
|
35
|
+
type: String,
|
|
36
|
+
required: true,
|
|
37
|
+
enum: Object.values(CONNECTOR_STATUS),
|
|
38
|
+
default: CONNECTOR_STATUS.PRIVATE,
|
|
39
|
+
index: {
|
|
40
|
+
name: 'statusIdIndex',
|
|
41
|
+
global: true,
|
|
42
|
+
rangeKey: 'id',
|
|
43
|
+
project: ['accountId', 'name', 'displayName', 'developer', 'originalAccountId'],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
creatorId: String,
|
|
47
|
+
// Developer Information
|
|
48
|
+
developer: {
|
|
49
|
+
type: Object,
|
|
50
|
+
schema: {
|
|
51
|
+
name: String,
|
|
52
|
+
websiteUrl: String,
|
|
53
|
+
supportUrl: String,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
// Manifest Management
|
|
57
|
+
manifest: {
|
|
58
|
+
type: Object,
|
|
59
|
+
required: true,
|
|
60
|
+
},
|
|
61
|
+
proxyConfig: {
|
|
62
|
+
type: Object,
|
|
63
|
+
required: false,
|
|
64
|
+
},
|
|
65
|
+
proxyId: {
|
|
66
|
+
type: String,
|
|
67
|
+
index: {
|
|
68
|
+
name: 'proxyIdIndex',
|
|
69
|
+
global: true,
|
|
70
|
+
project: ['id', 'accountId', 'creatorId', 'name', 'displayName', 'status', 'developer', 'originalAccountId', 'proxyConfig'],
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
// Review and Approval
|
|
74
|
+
submittedAt: Number,
|
|
75
|
+
reviewedBy: String,
|
|
76
|
+
reviewedAt: Number,
|
|
77
|
+
reviewNotes: String,
|
|
78
|
+
rejectionReason: String,
|
|
79
|
+
demoAccounts: String,
|
|
80
|
+
// Usage and Analytics
|
|
81
|
+
usageCount: {
|
|
82
|
+
type: Number,
|
|
83
|
+
default: 0,
|
|
84
|
+
},
|
|
85
|
+
lastUsedAt: Number,
|
|
86
|
+
allowedAccounts: {
|
|
87
|
+
type: Array,
|
|
88
|
+
schema: [String],
|
|
89
|
+
},
|
|
90
|
+
encodedSecretKey: String,
|
|
91
|
+
}, {
|
|
92
|
+
saveUnknown: ['manifest.**', 'proxyConfig.**'],
|
|
93
|
+
timestamps: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const tableOptions = {
|
|
97
|
+
prefix: process.env.DEVELOPER_DYNAMODB_TABLE_PREFIX,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (process.env.NODE_ENV === 'production') {
|
|
101
|
+
tableOptions.create = false;
|
|
102
|
+
tableOptions.waitForActive = false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const Connector = dynamoose.model('connectors', connectorSchema, tableOptions);
|
|
106
|
+
|
|
107
|
+
function getDeveloperCipherKey() {
|
|
108
|
+
if (!process.env.DEVELOPER_APP_SERVER_SECRET_KEY) {
|
|
109
|
+
throw new Error('DEVELOPER_APP_SERVER_SECRET_KEY is not defined');
|
|
110
|
+
}
|
|
111
|
+
if (process.env.DEVELOPER_APP_SERVER_SECRET_KEY.length < 32) {
|
|
112
|
+
// pad secret key with spaces if it is less than 32 bytes
|
|
113
|
+
return process.env.DEVELOPER_APP_SERVER_SECRET_KEY.padEnd(32, ' ');
|
|
114
|
+
}
|
|
115
|
+
if (process.env.DEVELOPER_APP_SERVER_SECRET_KEY.length > 32) {
|
|
116
|
+
// truncate secret key if it is more than 32 bytes
|
|
117
|
+
return process.env.DEVELOPER_APP_SERVER_SECRET_KEY.slice(0, 32);
|
|
118
|
+
}
|
|
119
|
+
return process.env.DEVELOPER_APP_SERVER_SECRET_KEY;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function decode(encryptedData) {
|
|
123
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', getDeveloperCipherKey(), Buffer.alloc(16, 0));
|
|
124
|
+
return decipher.update(encryptedData, 'hex', 'utf8') + decipher.final('utf8');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ADD static method to get connector by proxyId
|
|
128
|
+
Connector.getProxyConfig = async (proxyId) => {
|
|
129
|
+
const connectors = await Connector
|
|
130
|
+
.query('proxyId')
|
|
131
|
+
.eq(proxyId)
|
|
132
|
+
.using('proxyIdIndex')
|
|
133
|
+
.exec();
|
|
134
|
+
if (connectors.length > 0) {
|
|
135
|
+
const proxyConfig = connectors[0].proxyConfig;
|
|
136
|
+
const encodedSecretKey = connectors[0].encodedSecretKey;
|
|
137
|
+
const secretKey = encodedSecretKey ? decode(encodedSecretKey) : null;
|
|
138
|
+
if (secretKey) {
|
|
139
|
+
proxyConfig.secretKey = secretKey;
|
|
140
|
+
}
|
|
141
|
+
return proxyConfig;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
exports.Connector = Connector;
|
package/package.json
CHANGED
package/releaseNotes.json
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.7.3": {
|
|
3
|
+
"global": [
|
|
4
|
+
{
|
|
5
|
+
"type": "New",
|
|
6
|
+
"description": "RingSense data logging in server side logging"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"type": "New",
|
|
10
|
+
"description": "New phone setting item to group phone related settings"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "Better",
|
|
14
|
+
"description": "HUD enabled by default"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type": "Fix",
|
|
18
|
+
"description": "A small issue on contact search feature"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "Fix",
|
|
22
|
+
"description": "A small issue on embedded URLs"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "Fix",
|
|
26
|
+
"description": "Server-side logging logged calls cannot be edited for the first attempt"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
2
30
|
"1.7.1": {
|
|
3
31
|
"global": [
|
|
4
32
|
{
|
|
@@ -11,6 +39,18 @@
|
|
|
11
39
|
}
|
|
12
40
|
]
|
|
13
41
|
},
|
|
42
|
+
"1.6.11":{
|
|
43
|
+
"global": [
|
|
44
|
+
{
|
|
45
|
+
"type": "Fix",
|
|
46
|
+
"description": "A small issue on contact search feature"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "Fix",
|
|
50
|
+
"description": "A small issue on embedded URLs"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
14
54
|
"1.6.10": {
|
|
15
55
|
"global": [
|
|
16
56
|
{
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
jest.mock('axios', () => jest.fn());
|
|
4
|
+
const axios = require('axios');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
renderTemplateString,
|
|
8
|
+
renderDeep,
|
|
9
|
+
joinUrl,
|
|
10
|
+
performRequest,
|
|
11
|
+
} = require('../../../connector/proxy/engine');
|
|
12
|
+
|
|
13
|
+
describe('proxy engine utilities', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
axios.mockReset();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('renderTemplateString handles full and partial templates', () => {
|
|
19
|
+
const context = { a: { b: 123 }, name: 'Alice' };
|
|
20
|
+
expect(renderTemplateString('{{a.b}}', context)).toBe(123);
|
|
21
|
+
expect(renderTemplateString('Hello {{name}}', context)).toBe('Hello Alice');
|
|
22
|
+
expect(renderTemplateString('Missing {{x.y}} here', context)).toBe('Missing here');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('renderDeep renders nested objects and arrays', () => {
|
|
26
|
+
const context = { id: 42, name: 'Alice', items: ['x', 'y'] };
|
|
27
|
+
const input = {
|
|
28
|
+
url: '/users/{{id}}',
|
|
29
|
+
body: { name: '{{name}}', tags: ['a', '{{items.1}}'] },
|
|
30
|
+
};
|
|
31
|
+
const out = renderDeep(input, context);
|
|
32
|
+
expect(out.url).toBe('/users/42');
|
|
33
|
+
expect(out.body.name).toBe('Alice');
|
|
34
|
+
expect(out.body.tags).toEqual(['a', 'y']);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('joinUrl joins base and path and preserves absolute urls', () => {
|
|
38
|
+
expect(joinUrl('https://api.example.com', '/v1/items')).toBe('https://api.example.com/v1/items');
|
|
39
|
+
expect(joinUrl('https://api.example.com/', 'v1/items')).toBe('https://api.example.com/v1/items');
|
|
40
|
+
expect(joinUrl('', 'https://full.example.com/x')).toBe('https://full.example.com/x');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('performRequest composes url, headers, params, body and auth', async () => {
|
|
44
|
+
axios.mockResolvedValue({ data: { ok: true } });
|
|
45
|
+
const config = {
|
|
46
|
+
secretKey: 'shh-key',
|
|
47
|
+
auth: {
|
|
48
|
+
type: 'apiKey',
|
|
49
|
+
scheme: 'Basic',
|
|
50
|
+
credentialTemplate: '{{apiKey}}',
|
|
51
|
+
encode: 'base64',
|
|
52
|
+
headerName: 'Authorization'
|
|
53
|
+
},
|
|
54
|
+
requestDefaults: {
|
|
55
|
+
baseUrl: 'https://api.example.com',
|
|
56
|
+
timeoutSeconds: 10,
|
|
57
|
+
defaultHeaders: { Accept: 'application/json', 'X-Secret-Key': '{{secretKey}}' }
|
|
58
|
+
},
|
|
59
|
+
operations: {
|
|
60
|
+
createThing: {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
url: '/things/{{thingId}}',
|
|
63
|
+
headers: { 'Content-Type': 'application/json' },
|
|
64
|
+
query: { search: '{{q}}' },
|
|
65
|
+
body: { id: '{{thingId}}', name: '{{name}}' }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const user = { accessToken: 'token-123' };
|
|
70
|
+
await performRequest({
|
|
71
|
+
config,
|
|
72
|
+
opName: 'createThing',
|
|
73
|
+
inputs: { thingId: 7, name: 'Widget', q: 'alpha' },
|
|
74
|
+
user,
|
|
75
|
+
authHeader: undefined
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(axios).toHaveBeenCalledTimes(1);
|
|
79
|
+
const args = axios.mock.calls[0][0];
|
|
80
|
+
expect(args.url).toBe('https://api.example.com/things/7');
|
|
81
|
+
expect(args.method).toBe('POST');
|
|
82
|
+
expect(args.params).toEqual({ search: 'alpha' });
|
|
83
|
+
expect(args.data).toEqual({ id: 7, name: 'Widget' });
|
|
84
|
+
expect(args.timeout).toBe(10000);
|
|
85
|
+
expect(args.headers.Accept).toBe('application/json');
|
|
86
|
+
expect(args.headers['Content-Type']).toBe('application/json');
|
|
87
|
+
expect(args.headers['X-Secret-Key']).toBe('shh-key');
|
|
88
|
+
// Basic base64('token-123')
|
|
89
|
+
expect(args.headers.Authorization).toMatch(/^Basic /);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
|