@juzi/wechaty-puppet-service 1.0.127 → 1.0.129
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/dist/cjs/src/client/puppet-service.d.ts +2 -0
- package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
- package/dist/cjs/src/client/puppet-service.js +20 -0
- package/dist/cjs/src/client/puppet-service.js.map +1 -1
- package/dist/cjs/src/client/puppet-service.spec.js +45 -0
- package/dist/cjs/src/client/puppet-service.spec.js.map +1 -1
- package/dist/cjs/src/package-json.js +4 -4
- package/dist/cjs/src/server/event-stream-manager.d.ts.map +1 -1
- package/dist/cjs/src/server/event-stream-manager.js +7 -0
- package/dist/cjs/src/server/event-stream-manager.js.map +1 -1
- package/dist/cjs/src/server/puppet-implementation.d.ts.map +1 -1
- package/dist/cjs/src/server/puppet-implementation.js +29 -0
- package/dist/cjs/src/server/puppet-implementation.js.map +1 -1
- package/dist/cjs/src/server/puppet-implementation.spec.d.ts +3 -0
- package/dist/cjs/src/server/puppet-implementation.spec.d.ts.map +1 -0
- package/dist/cjs/src/server/puppet-implementation.spec.js +79 -0
- package/dist/cjs/src/server/puppet-implementation.spec.js.map +1 -0
- package/dist/esm/src/client/puppet-service.d.ts +2 -0
- package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
- package/dist/esm/src/client/puppet-service.js +20 -0
- package/dist/esm/src/client/puppet-service.js.map +1 -1
- package/dist/esm/src/client/puppet-service.spec.js +45 -0
- package/dist/esm/src/client/puppet-service.spec.js.map +1 -1
- package/dist/esm/src/package-json.js +4 -4
- package/dist/esm/src/server/event-stream-manager.d.ts.map +1 -1
- package/dist/esm/src/server/event-stream-manager.js +7 -0
- package/dist/esm/src/server/event-stream-manager.js.map +1 -1
- package/dist/esm/src/server/puppet-implementation.d.ts.map +1 -1
- package/dist/esm/src/server/puppet-implementation.js +29 -0
- package/dist/esm/src/server/puppet-implementation.js.map +1 -1
- package/dist/esm/src/server/puppet-implementation.spec.d.ts +3 -0
- package/dist/esm/src/server/puppet-implementation.spec.d.ts.map +1 -0
- package/dist/esm/src/server/puppet-implementation.spec.js +77 -0
- package/dist/esm/src/server/puppet-implementation.spec.js.map +1 -0
- package/package.json +4 -4
- package/src/client/puppet-service.spec.ts +80 -0
- package/src/client/puppet-service.ts +36 -0
- package/src/package-json.ts +4 -4
- package/src/server/event-stream-manager.ts +7 -0
- package/src/server/puppet-implementation.spec.ts +121 -0
- package/src/server/puppet-implementation.ts +35 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
|
+
import { sinon, test, } from 'tstest';
|
|
3
|
+
import { FileBox } from 'file-box';
|
|
4
|
+
import { puppet as grpcPuppet } from '@juzi/wechaty-grpc';
|
|
5
|
+
import { PuppetMock } from '@juzi/wechaty-puppet-mock';
|
|
6
|
+
import { puppetImplementation } from './puppet-implementation.js';
|
|
7
|
+
function buildRequest(options) {
|
|
8
|
+
const request = new grpcPuppet.CallInviteWithMediaRequest();
|
|
9
|
+
request.setContactIdsList(options.contactIds);
|
|
10
|
+
request.setFileBox(options.fileBox ?? '');
|
|
11
|
+
request.setHangupOnFinish(options.hangupOnFinish ?? false);
|
|
12
|
+
request.setHangupDelayMs(options.hangupDelayMs ?? 0);
|
|
13
|
+
return request;
|
|
14
|
+
}
|
|
15
|
+
async function invokeCallInviteWithMedia(puppet, request) {
|
|
16
|
+
const impl = puppetImplementation(puppet, FileBox);
|
|
17
|
+
return new Promise(resolve => {
|
|
18
|
+
void impl.callInviteWithMedia({ request }, ((error, response) => resolve({ error, response })));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
test('callInviteWithMedia rejects an empty contact_ids list', async (t) => {
|
|
22
|
+
const puppet = new PuppetMock();
|
|
23
|
+
const stub = sinon.stub().resolves('call-id');
|
|
24
|
+
puppet.callInviteWithMedia = stub;
|
|
25
|
+
const { error } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
26
|
+
contactIds: [],
|
|
27
|
+
hangupOnFinish: true,
|
|
28
|
+
}));
|
|
29
|
+
t.match(error?.message, /contact_ids is required/, 'reports the missing contact_ids');
|
|
30
|
+
t.equal(stub.callCount, 0, 'never reaches the puppet');
|
|
31
|
+
});
|
|
32
|
+
test('callInviteWithMedia rejects an empty file_box combined with hangup_on_finish false', async (t) => {
|
|
33
|
+
const puppet = new PuppetMock();
|
|
34
|
+
const stub = sinon.stub().resolves('call-id');
|
|
35
|
+
puppet.callInviteWithMedia = stub;
|
|
36
|
+
const { error } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
37
|
+
contactIds: ['contact-1'],
|
|
38
|
+
hangupOnFinish: false,
|
|
39
|
+
}));
|
|
40
|
+
t.match(error?.message, /use CallInvite directly/, 'points the caller at the equivalent CallInvite');
|
|
41
|
+
t.equal(stub.callCount, 0, 'never reaches the puppet');
|
|
42
|
+
});
|
|
43
|
+
test('callInviteWithMedia forwards a file-less invite as an undefined file', async (t) => {
|
|
44
|
+
const puppet = new PuppetMock();
|
|
45
|
+
const stub = sinon.stub().resolves('call-id-no-file');
|
|
46
|
+
puppet.callInviteWithMedia = stub;
|
|
47
|
+
const { error, response } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
48
|
+
contactIds: ['contact-1'],
|
|
49
|
+
hangupDelayMs: 1500,
|
|
50
|
+
hangupOnFinish: true,
|
|
51
|
+
}));
|
|
52
|
+
t.equal(error, null, 'accepts the file-less invite');
|
|
53
|
+
t.equal(response?.getCallId(), 'call-id-no-file', 'returns the callId from the puppet');
|
|
54
|
+
t.same(stub.args[0], [['contact-1'], undefined, { hangupDelayMs: 1500, hangupOnFinish: true }], 'forwards contactIds, an undefined file and the orchestration options');
|
|
55
|
+
});
|
|
56
|
+
test('callInviteWithMedia deserializes a non-empty file_box into a FileBox', async (t) => {
|
|
57
|
+
const puppet = new PuppetMock();
|
|
58
|
+
const stub = sinon.stub().resolves('call-id-with-file');
|
|
59
|
+
puppet.callInviteWithMedia = stub;
|
|
60
|
+
const file = FileBox.fromUrl('https://example.com/notice.mp3', { name: 'notice.mp3' });
|
|
61
|
+
const { error } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
62
|
+
contactIds: ['contact-1'],
|
|
63
|
+
fileBox: JSON.stringify(file),
|
|
64
|
+
}));
|
|
65
|
+
t.equal(error, null, 'accepts the invite carrying a file');
|
|
66
|
+
t.equal(stub.args[0]?.[1]?.name, 'notice.mp3', 'hands the puppet a FileBox rebuilt from file_box');
|
|
67
|
+
});
|
|
68
|
+
test('callInviteWithMedia rejects an empty callId returned by the puppet', async (t) => {
|
|
69
|
+
const puppet = new PuppetMock();
|
|
70
|
+
puppet.callInviteWithMedia = sinon.stub().resolves('');
|
|
71
|
+
const { error } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
72
|
+
contactIds: ['contact-1'],
|
|
73
|
+
hangupOnFinish: true,
|
|
74
|
+
}));
|
|
75
|
+
t.match(error?.message, /empty callId/, 'does not pass an unusable empty callId back on the wire');
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=puppet-implementation.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"puppet-implementation.spec.js","sourceRoot":"","sources":["../../../../src/server/puppet-implementation.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juzi/wechaty-puppet-service",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.129",
|
|
4
4
|
"description": "Puppet Service for Wechaty",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@chatie/eslint-config": "^1.0.4",
|
|
52
52
|
"@chatie/semver": "^0.4.7",
|
|
53
53
|
"@chatie/tsconfig": "^4.6.3",
|
|
54
|
-
"@juzi/wechaty-puppet": "^1.0.
|
|
54
|
+
"@juzi/wechaty-puppet": "^1.0.156",
|
|
55
55
|
"@juzi/wechaty-puppet-mock": "^1.0.1",
|
|
56
56
|
"@swc/core": "1.3.39",
|
|
57
57
|
"@types/google-protobuf": "^3.15.5",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"why-is-node-running": "^2.2.1"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"@juzi/wechaty-puppet": "^1.0.
|
|
74
|
+
"@juzi/wechaty-puppet": "^1.0.156"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@juzi/wechaty-grpc": "^1.0.
|
|
77
|
+
"@juzi/wechaty-grpc": "^1.0.108",
|
|
78
78
|
"clone-class": "^1.1.1",
|
|
79
79
|
"ducks": "^1.0.2",
|
|
80
80
|
"file-box": "^1.5.5",
|
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
} from 'tstest'
|
|
7
7
|
import getPort from 'get-port'
|
|
8
8
|
|
|
9
|
+
import { FileBox } from 'file-box'
|
|
10
|
+
import { puppet as grpcPuppet } from '@juzi/wechaty-grpc'
|
|
11
|
+
|
|
9
12
|
import { PuppetMock } from '@juzi/wechaty-puppet-mock'
|
|
10
13
|
|
|
11
14
|
import { PuppetService } from './puppet-service.js'
|
|
@@ -74,6 +77,83 @@ test('batchRoomRawPayload still rejects when every per-room fallback fails', asy
|
|
|
74
77
|
)
|
|
75
78
|
})
|
|
76
79
|
|
|
80
|
+
let callInviteWithMediaTestId = 0
|
|
81
|
+
|
|
82
|
+
function createCallInviteWithMediaPuppet (
|
|
83
|
+
callId: string,
|
|
84
|
+
): { puppet: PuppetService, requestList: grpcPuppet.CallInviteWithMediaRequest[] } {
|
|
85
|
+
const puppet = new PuppetService({
|
|
86
|
+
token: `call-invite-with-media-test-${callInviteWithMediaTestId++}`,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const requestList: grpcPuppet.CallInviteWithMediaRequest[] = []
|
|
90
|
+
|
|
91
|
+
;(puppet as any)._grpcManager = {
|
|
92
|
+
client: {
|
|
93
|
+
callInviteWithMedia: (
|
|
94
|
+
request : grpcPuppet.CallInviteWithMediaRequest,
|
|
95
|
+
callback : (error: null | Error, response: grpcPuppet.CallInviteWithMediaResponse) => void,
|
|
96
|
+
) => {
|
|
97
|
+
requestList.push(request)
|
|
98
|
+
|
|
99
|
+
const response = new grpcPuppet.CallInviteWithMediaResponse()
|
|
100
|
+
response.setCallId(callId)
|
|
101
|
+
callback(null, response)
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { puppet, requestList }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
test('callInviteWithMedia maps the file and the orchestration options onto the request', async t => {
|
|
110
|
+
const { puppet, requestList } = createCallInviteWithMediaPuppet('call-id-with-file')
|
|
111
|
+
|
|
112
|
+
const file = FileBox.fromUrl('https://example.com/notice.mp3', { name: 'notice.mp3' })
|
|
113
|
+
|
|
114
|
+
const callId = await puppet.callInviteWithMedia(
|
|
115
|
+
[ 'contact-1', 'contact-2' ],
|
|
116
|
+
file,
|
|
117
|
+
{ hangupDelayMs: 800, hangupOnFinish: true },
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
t.equal(callId, 'call-id-with-file', 'returns the call_id from the server response')
|
|
121
|
+
t.equal(requestList.length, 1, 'sends exactly one request')
|
|
122
|
+
|
|
123
|
+
const request = requestList[0]!
|
|
124
|
+
t.same(request.getContactIdsList(), [ 'contact-1', 'contact-2' ], 'maps contactIds onto contact_ids')
|
|
125
|
+
t.equal(request.getHangupOnFinish(), true, 'maps hangupOnFinish onto hangup_on_finish')
|
|
126
|
+
t.equal(request.getHangupDelayMs(), 800, 'maps hangupDelayMs onto hangup_delay_ms')
|
|
127
|
+
t.equal(
|
|
128
|
+
JSON.parse(request.getFileBox()).name,
|
|
129
|
+
'notice.mp3',
|
|
130
|
+
'serializes the FileBox onto file_box',
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('callInviteWithMedia leaves file_box empty and defaults the options when they are omitted', async t => {
|
|
135
|
+
const { puppet, requestList } = createCallInviteWithMediaPuppet('call-id-no-file')
|
|
136
|
+
|
|
137
|
+
const callId = await puppet.callInviteWithMedia([ 'contact-1' ])
|
|
138
|
+
|
|
139
|
+
t.equal(callId, 'call-id-no-file', 'returns the call_id from the server response')
|
|
140
|
+
|
|
141
|
+
const request = requestList[0]!
|
|
142
|
+
t.equal(request.getFileBox(), '', 'leaves file_box empty when no file is given')
|
|
143
|
+
t.equal(request.getHangupOnFinish(), false, 'defaults hangup_on_finish to false')
|
|
144
|
+
t.equal(request.getHangupDelayMs(), 0, 'defaults hangup_delay_ms to 0')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('callInviteWithMedia rejects when the server returns an empty call_id', async t => {
|
|
148
|
+
const { puppet } = createCallInviteWithMediaPuppet('')
|
|
149
|
+
|
|
150
|
+
await t.rejects(
|
|
151
|
+
puppet.callInviteWithMedia([ 'contact-1' ], undefined, { hangupOnFinish: true }),
|
|
152
|
+
/empty call_id/,
|
|
153
|
+
'does not hand back an unusable empty call_id',
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
|
|
77
157
|
test('version()', async t => {
|
|
78
158
|
const puppet = new PuppetService({
|
|
79
159
|
token: 'test',
|
|
@@ -56,6 +56,7 @@ import { PayloadStore } from './payload-store.js'
|
|
|
56
56
|
import { OptionalBooleanUnwrapper, OptionalBooleanWrapper, callRecordPbToPayload, channelPayloadToPb, channelPbToPayload, chatHistoryPbToPayload, contactPbToPayload, postPayloadToPb, roomMemberPbToPayload, urlLinkPbToPayload, channelCardPayloadToPb, channelCardPbToPayload, miniProgramPayloadToPb, urlLinkPayloadToPb, locationPayloadToPb } from '../utils/pb-payload-helper.js'
|
|
57
57
|
import { puppetCallMediaTypeToGrpc, grpcCallTypeToPuppetMedia } from '../utils/call-media-mapping.js'
|
|
58
58
|
import type { MessageBroadcastTargets } from '@juzi/wechaty-puppet/dist/esm/src/schemas/message.js'
|
|
59
|
+
import type { CallInviteWithMediaOptions } from '@juzi/wechaty-puppet/dist/esm/src/schemas/call.js'
|
|
59
60
|
import { timeoutPromise } from 'gerror'
|
|
60
61
|
import { BooleanIndicator } from 'state-switch'
|
|
61
62
|
import type { Contact } from '@juzi/wechaty-puppet/types'
|
|
@@ -428,6 +429,9 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
428
429
|
case grpcPuppet.EventType.EVENT_TYPE_VERIFY_SLIDE:
|
|
429
430
|
this.emit('verify-slide', JSON.parse(payload) as PUPPET.payloads.EventVerifySlide)
|
|
430
431
|
break
|
|
432
|
+
case grpcPuppet.EventType.EVENT_TYPE_SAME_NET_VERIFY:
|
|
433
|
+
this.emit('same-net-verify', JSON.parse(payload) as PUPPET.payloads.EventSameNetVerify)
|
|
434
|
+
break
|
|
431
435
|
case grpcPuppet.EventType.EVENT_TYPE_CALL:
|
|
432
436
|
this.emit('call', JSON.parse(payload) as PUPPET.payloads.EventCall)
|
|
433
437
|
break
|
|
@@ -1184,6 +1188,38 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
1184
1188
|
return callId
|
|
1185
1189
|
}
|
|
1186
1190
|
|
|
1191
|
+
override async callInviteWithMedia (
|
|
1192
|
+
contactIds : string[],
|
|
1193
|
+
file? : FileBoxInterface,
|
|
1194
|
+
options? : CallInviteWithMediaOptions,
|
|
1195
|
+
): Promise<string> {
|
|
1196
|
+
this.log.verbose('PuppetService', 'callInviteWithMedia(%s, %s, %s)',
|
|
1197
|
+
contactIds,
|
|
1198
|
+
file?.name,
|
|
1199
|
+
JSON.stringify(options),
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
const request = new grpcPuppet.CallInviteWithMediaRequest()
|
|
1203
|
+
request.setContactIdsList(contactIds)
|
|
1204
|
+
if (file) {
|
|
1205
|
+
request.setFileBox(await this.serializeFileBox(file))
|
|
1206
|
+
}
|
|
1207
|
+
request.setHangupOnFinish(options?.hangupOnFinish ?? false)
|
|
1208
|
+
request.setHangupDelayMs(options?.hangupDelayMs ?? 0)
|
|
1209
|
+
|
|
1210
|
+
const response = await util.promisify(
|
|
1211
|
+
this.grpcManager.client.callInviteWithMedia
|
|
1212
|
+
.bind(this.grpcManager.client),
|
|
1213
|
+
)(request)
|
|
1214
|
+
|
|
1215
|
+
const callId = response.getCallId()
|
|
1216
|
+
if (!callId) {
|
|
1217
|
+
throw new Error('callInviteWithMedia: puppet server returned empty call_id')
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return callId
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1187
1223
|
override async callAdd (
|
|
1188
1224
|
callId: string,
|
|
1189
1225
|
contactIds: string[],
|
package/src/package-json.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import type { PackageJson } from 'type-fest'
|
|
5
5
|
export const packageJson: PackageJson = {
|
|
6
6
|
"name": "@juzi/wechaty-puppet-service",
|
|
7
|
-
"version": "1.0.
|
|
7
|
+
"version": "1.0.129",
|
|
8
8
|
"description": "Puppet Service for Wechaty",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"exports": {
|
|
@@ -55,7 +55,7 @@ export const packageJson: PackageJson = {
|
|
|
55
55
|
"@chatie/eslint-config": "^1.0.4",
|
|
56
56
|
"@chatie/semver": "^0.4.7",
|
|
57
57
|
"@chatie/tsconfig": "^4.6.3",
|
|
58
|
-
"@juzi/wechaty-puppet": "^1.0.
|
|
58
|
+
"@juzi/wechaty-puppet": "^1.0.156",
|
|
59
59
|
"@juzi/wechaty-puppet-mock": "^1.0.1",
|
|
60
60
|
"@swc/core": "1.3.39",
|
|
61
61
|
"@types/google-protobuf": "^3.15.5",
|
|
@@ -75,10 +75,10 @@ export const packageJson: PackageJson = {
|
|
|
75
75
|
"why-is-node-running": "^2.2.1"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@juzi/wechaty-puppet": "^1.0.
|
|
78
|
+
"@juzi/wechaty-puppet": "^1.0.156"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@juzi/wechaty-grpc": "^1.0.
|
|
81
|
+
"@juzi/wechaty-grpc": "^1.0.108",
|
|
82
82
|
"clone-class": "^1.1.1",
|
|
83
83
|
"ducks": "^1.0.2",
|
|
84
84
|
"file-box": "^1.5.5",
|
|
@@ -322,6 +322,13 @@ class EventStreamManager {
|
|
|
322
322
|
this.offCallbackList.push(off)
|
|
323
323
|
break
|
|
324
324
|
}
|
|
325
|
+
case 'same-net-verify': {
|
|
326
|
+
const listener = (payload: PUPPET.payloads.EventSameNetVerify) => this.grpcEmit(grpcPuppet.EventType.EVENT_TYPE_SAME_NET_VERIFY, payload)
|
|
327
|
+
this.puppet.on('same-net-verify', listener)
|
|
328
|
+
const off = () => this.puppet.off('same-net-verify', listener)
|
|
329
|
+
this.offCallbackList.push(off)
|
|
330
|
+
break
|
|
331
|
+
}
|
|
325
332
|
case 'call': {
|
|
326
333
|
const listener = (payload: PUPPET.payloads.EventCall) => this.grpcEmit(grpcPuppet.EventType.EVENT_TYPE_CALL, payload)
|
|
327
334
|
this.puppet.on('call', listener)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
sinon,
|
|
5
|
+
test,
|
|
6
|
+
} from 'tstest'
|
|
7
|
+
|
|
8
|
+
import { FileBox } from 'file-box'
|
|
9
|
+
import { puppet as grpcPuppet } from '@juzi/wechaty-grpc'
|
|
10
|
+
|
|
11
|
+
import { PuppetMock } from '@juzi/wechaty-puppet-mock'
|
|
12
|
+
|
|
13
|
+
import { puppetImplementation } from './puppet-implementation.js'
|
|
14
|
+
|
|
15
|
+
interface CallInviteWithMediaCase {
|
|
16
|
+
contactIds : string[]
|
|
17
|
+
fileBox? : string
|
|
18
|
+
hangupOnFinish? : boolean
|
|
19
|
+
hangupDelayMs? : number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildRequest (
|
|
23
|
+
options: CallInviteWithMediaCase,
|
|
24
|
+
): grpcPuppet.CallInviteWithMediaRequest {
|
|
25
|
+
const request = new grpcPuppet.CallInviteWithMediaRequest()
|
|
26
|
+
request.setContactIdsList(options.contactIds)
|
|
27
|
+
request.setFileBox(options.fileBox ?? '')
|
|
28
|
+
request.setHangupOnFinish(options.hangupOnFinish ?? false)
|
|
29
|
+
request.setHangupDelayMs(options.hangupDelayMs ?? 0)
|
|
30
|
+
return request
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function invokeCallInviteWithMedia (
|
|
34
|
+
puppet : any,
|
|
35
|
+
request : grpcPuppet.CallInviteWithMediaRequest,
|
|
36
|
+
): Promise<{ error: null | Error, response: null | grpcPuppet.CallInviteWithMediaResponse }> {
|
|
37
|
+
const impl = puppetImplementation(puppet, FileBox)
|
|
38
|
+
|
|
39
|
+
return new Promise(resolve => {
|
|
40
|
+
void impl.callInviteWithMedia(
|
|
41
|
+
{ request } as any,
|
|
42
|
+
((error: any, response: any) => resolve({ error, response })) as any,
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test('callInviteWithMedia rejects an empty contact_ids list', async t => {
|
|
48
|
+
const puppet = new PuppetMock() as any
|
|
49
|
+
const stub = sinon.stub().resolves('call-id')
|
|
50
|
+
puppet.callInviteWithMedia = stub
|
|
51
|
+
|
|
52
|
+
const { error } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
53
|
+
contactIds : [],
|
|
54
|
+
hangupOnFinish : true,
|
|
55
|
+
}))
|
|
56
|
+
|
|
57
|
+
t.match(error?.message, /contact_ids is required/, 'reports the missing contact_ids')
|
|
58
|
+
t.equal(stub.callCount, 0, 'never reaches the puppet')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('callInviteWithMedia rejects an empty file_box combined with hangup_on_finish false', async t => {
|
|
62
|
+
const puppet = new PuppetMock() as any
|
|
63
|
+
const stub = sinon.stub().resolves('call-id')
|
|
64
|
+
puppet.callInviteWithMedia = stub
|
|
65
|
+
|
|
66
|
+
const { error } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
67
|
+
contactIds : [ 'contact-1' ],
|
|
68
|
+
hangupOnFinish : false,
|
|
69
|
+
}))
|
|
70
|
+
|
|
71
|
+
t.match(error?.message, /use CallInvite directly/, 'points the caller at the equivalent CallInvite')
|
|
72
|
+
t.equal(stub.callCount, 0, 'never reaches the puppet')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('callInviteWithMedia forwards a file-less invite as an undefined file', async t => {
|
|
76
|
+
const puppet = new PuppetMock() as any
|
|
77
|
+
const stub = sinon.stub().resolves('call-id-no-file')
|
|
78
|
+
puppet.callInviteWithMedia = stub
|
|
79
|
+
|
|
80
|
+
const { error, response } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
81
|
+
contactIds : [ 'contact-1' ],
|
|
82
|
+
hangupDelayMs : 1500,
|
|
83
|
+
hangupOnFinish : true,
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
t.equal(error, null, 'accepts the file-less invite')
|
|
87
|
+
t.equal(response?.getCallId(), 'call-id-no-file', 'returns the callId from the puppet')
|
|
88
|
+
t.same(
|
|
89
|
+
stub.args[0],
|
|
90
|
+
[ [ 'contact-1' ], undefined, { hangupDelayMs: 1500, hangupOnFinish: true } ],
|
|
91
|
+
'forwards contactIds, an undefined file and the orchestration options',
|
|
92
|
+
)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('callInviteWithMedia deserializes a non-empty file_box into a FileBox', async t => {
|
|
96
|
+
const puppet = new PuppetMock() as any
|
|
97
|
+
const stub = sinon.stub().resolves('call-id-with-file')
|
|
98
|
+
puppet.callInviteWithMedia = stub
|
|
99
|
+
|
|
100
|
+
const file = FileBox.fromUrl('https://example.com/notice.mp3', { name: 'notice.mp3' })
|
|
101
|
+
|
|
102
|
+
const { error } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
103
|
+
contactIds : [ 'contact-1' ],
|
|
104
|
+
fileBox : JSON.stringify(file),
|
|
105
|
+
}))
|
|
106
|
+
|
|
107
|
+
t.equal(error, null, 'accepts the invite carrying a file')
|
|
108
|
+
t.equal(stub.args[0]?.[1]?.name, 'notice.mp3', 'hands the puppet a FileBox rebuilt from file_box')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('callInviteWithMedia rejects an empty callId returned by the puppet', async t => {
|
|
112
|
+
const puppet = new PuppetMock() as any
|
|
113
|
+
puppet.callInviteWithMedia = sinon.stub().resolves('')
|
|
114
|
+
|
|
115
|
+
const { error } = await invokeCallInviteWithMedia(puppet, buildRequest({
|
|
116
|
+
contactIds : [ 'contact-1' ],
|
|
117
|
+
hangupOnFinish : true,
|
|
118
|
+
}))
|
|
119
|
+
|
|
120
|
+
t.match(error?.message, /empty callId/, 'does not pass an unusable empty callId back on the wire')
|
|
121
|
+
})
|
|
@@ -3060,6 +3060,41 @@ function puppetImplementation (
|
|
|
3060
3060
|
}
|
|
3061
3061
|
},
|
|
3062
3062
|
|
|
3063
|
+
callInviteWithMedia: async (call, callback) => {
|
|
3064
|
+
log.verbose('PuppetServiceImpl', 'callInviteWithMedia()')
|
|
3065
|
+
|
|
3066
|
+
try {
|
|
3067
|
+
const contactIds = call.request.getContactIdsList()
|
|
3068
|
+
if (contactIds.length === 0) {
|
|
3069
|
+
throw new Error('callInviteWithMedia: contact_ids is required')
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
const jsonText = call.request.getFileBox()
|
|
3073
|
+
const file = jsonText ? FileBoxUuid.fromJSON(jsonText) : undefined
|
|
3074
|
+
const hangupOnFinish = call.request.getHangupOnFinish()
|
|
3075
|
+
const hangupDelayMs = call.request.getHangupDelayMs()
|
|
3076
|
+
|
|
3077
|
+
if (!file && !hangupOnFinish) {
|
|
3078
|
+
throw new Error('callInviteWithMedia: hangup_on_finish must be true when file_box is empty, otherwise it is equivalent to CallInvite — use CallInvite directly')
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
const callId = await puppet.callInviteWithMedia(contactIds, file, {
|
|
3082
|
+
hangupDelayMs,
|
|
3083
|
+
hangupOnFinish,
|
|
3084
|
+
})
|
|
3085
|
+
if (!callId) {
|
|
3086
|
+
throw new Error('callInviteWithMedia: puppet returned empty callId')
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
const response = new grpcPuppet.CallInviteWithMediaResponse()
|
|
3090
|
+
response.setCallId(callId)
|
|
3091
|
+
|
|
3092
|
+
return callback(null, response)
|
|
3093
|
+
} catch (e) {
|
|
3094
|
+
return grpcError('callInviteWithMedia', e, callback)
|
|
3095
|
+
}
|
|
3096
|
+
},
|
|
3097
|
+
|
|
3063
3098
|
callAdd: async (call, callback) => {
|
|
3064
3099
|
log.verbose('PuppetServiceImpl', 'callAdd()')
|
|
3065
3100
|
|