@juzi/wechaty-puppet-service 1.0.126 → 1.0.128
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 +29 -2
- package/dist/cjs/src/client/puppet-service.js.map +1 -1
- package/dist/cjs/src/client/puppet-service.spec.js +85 -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/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 +29 -2
- package/dist/esm/src/client/puppet-service.js.map +1 -1
- package/dist/esm/src/client/puppet-service.spec.js +86 -1
- 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/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 +144 -0
- package/src/client/puppet-service.ts +44 -2
- package/src/package-json.ts +4 -4
- package/src/server/puppet-implementation.spec.ts +121 -0
- package/src/server/puppet-implementation.ts +35 -0
|
@@ -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.128",
|
|
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.155",
|
|
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.155"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@juzi/wechaty-grpc": "^1.0.
|
|
77
|
+
"@juzi/wechaty-grpc": "^1.0.107",
|
|
78
78
|
"clone-class": "^1.1.1",
|
|
79
79
|
"ducks": "^1.0.2",
|
|
80
80
|
"file-box": "^1.5.5",
|
|
@@ -1,15 +1,159 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
sinon,
|
|
4
5
|
test,
|
|
5
6
|
} from 'tstest'
|
|
6
7
|
import getPort from 'get-port'
|
|
7
8
|
|
|
9
|
+
import { FileBox } from 'file-box'
|
|
10
|
+
import { puppet as grpcPuppet } from '@juzi/wechaty-grpc'
|
|
11
|
+
|
|
8
12
|
import { PuppetMock } from '@juzi/wechaty-puppet-mock'
|
|
9
13
|
|
|
10
14
|
import { PuppetService } from './puppet-service.js'
|
|
11
15
|
import { PuppetServer } from '../mod.js'
|
|
12
16
|
|
|
17
|
+
let batchRoomFallbackTestId = 0
|
|
18
|
+
|
|
19
|
+
function createBatchRoomFallbackPuppet (
|
|
20
|
+
roomRawPayload: (roomId: string) => Promise<{ id: string }>,
|
|
21
|
+
): PuppetService {
|
|
22
|
+
const puppet = new PuppetService({
|
|
23
|
+
token: `batch-room-fallback-test-${batchRoomFallbackTestId++}`,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
;(puppet as any)._payloadStore.room = {
|
|
27
|
+
get: async () => undefined,
|
|
28
|
+
set: async () => undefined,
|
|
29
|
+
}
|
|
30
|
+
;(puppet as any)._grpcManager = {
|
|
31
|
+
client: {
|
|
32
|
+
batchRoomPayload: (_request: unknown, callback: (error: Error) => void) => {
|
|
33
|
+
callback(new Error('batch room payload unavailable'))
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
sinon.stub(puppet, 'roomRawPayload').callsFake(roomRawPayload as any)
|
|
38
|
+
|
|
39
|
+
return puppet
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test('batchRoomRawPayload isolates one invalid room during per-room fallback', async t => {
|
|
43
|
+
const puppet = createBatchRoomFallbackPuppet(async roomId => {
|
|
44
|
+
if (roomId === '10006') {
|
|
45
|
+
throw new Error('invalid roomId: 10006')
|
|
46
|
+
}
|
|
47
|
+
return { id: roomId }
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const result = await puppet.batchRoomRawPayload([
|
|
51
|
+
'room-valid-before',
|
|
52
|
+
'10006',
|
|
53
|
+
'room-valid-after',
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
t.same(
|
|
57
|
+
Array.from(result.keys()),
|
|
58
|
+
[ 'room-valid-before', 'room-valid-after' ],
|
|
59
|
+
'returns every valid room and omits only the invalid room',
|
|
60
|
+
)
|
|
61
|
+
t.equal(
|
|
62
|
+
(puppet.roomRawPayload as any).callCount,
|
|
63
|
+
3,
|
|
64
|
+
'continues the fallback loop after one room fails',
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('batchRoomRawPayload still rejects when every per-room fallback fails', async t => {
|
|
69
|
+
const puppet = createBatchRoomFallbackPuppet(async roomId => {
|
|
70
|
+
throw new Error(`invalid roomId: ${roomId}`)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
await t.rejects(
|
|
74
|
+
puppet.batchRoomRawPayload([ '10006', '10007' ]),
|
|
75
|
+
/batch room payload unavailable/,
|
|
76
|
+
'does not turn a total upstream failure into an empty successful result',
|
|
77
|
+
)
|
|
78
|
+
})
|
|
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
|
+
|
|
13
157
|
test('version()', async t => {
|
|
14
158
|
const puppet = new PuppetService({
|
|
15
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'
|
|
@@ -1184,6 +1185,38 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
1184
1185
|
return callId
|
|
1185
1186
|
}
|
|
1186
1187
|
|
|
1188
|
+
override async callInviteWithMedia (
|
|
1189
|
+
contactIds : string[],
|
|
1190
|
+
file? : FileBoxInterface,
|
|
1191
|
+
options? : CallInviteWithMediaOptions,
|
|
1192
|
+
): Promise<string> {
|
|
1193
|
+
this.log.verbose('PuppetService', 'callInviteWithMedia(%s, %s, %s)',
|
|
1194
|
+
contactIds,
|
|
1195
|
+
file?.name,
|
|
1196
|
+
JSON.stringify(options),
|
|
1197
|
+
)
|
|
1198
|
+
|
|
1199
|
+
const request = new grpcPuppet.CallInviteWithMediaRequest()
|
|
1200
|
+
request.setContactIdsList(contactIds)
|
|
1201
|
+
if (file) {
|
|
1202
|
+
request.setFileBox(await this.serializeFileBox(file))
|
|
1203
|
+
}
|
|
1204
|
+
request.setHangupOnFinish(options?.hangupOnFinish ?? false)
|
|
1205
|
+
request.setHangupDelayMs(options?.hangupDelayMs ?? 0)
|
|
1206
|
+
|
|
1207
|
+
const response = await util.promisify(
|
|
1208
|
+
this.grpcManager.client.callInviteWithMedia
|
|
1209
|
+
.bind(this.grpcManager.client),
|
|
1210
|
+
)(request)
|
|
1211
|
+
|
|
1212
|
+
const callId = response.getCallId()
|
|
1213
|
+
if (!callId) {
|
|
1214
|
+
throw new Error('callInviteWithMedia: puppet server returned empty call_id')
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
return callId
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1187
1220
|
override async callAdd (
|
|
1188
1221
|
callId: string,
|
|
1189
1222
|
contactIds: string[],
|
|
@@ -2294,9 +2327,18 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2294
2327
|
}
|
|
2295
2328
|
} catch (e) {
|
|
2296
2329
|
this.log.error('PuppetService', 'batchRoomRawPayload(%s, %s) error: %s, use one by one method', roomIdList, needGetSet, e)
|
|
2330
|
+
let fallbackSuccessCount = 0
|
|
2297
2331
|
for (const roomId of needGetSet) {
|
|
2298
|
-
|
|
2299
|
-
|
|
2332
|
+
try {
|
|
2333
|
+
const payload = await this.roomRawPayload(roomId)
|
|
2334
|
+
result.set(roomId, payload)
|
|
2335
|
+
fallbackSuccessCount++
|
|
2336
|
+
} catch (fallbackError) {
|
|
2337
|
+
this.log.warn('PuppetService', 'roomRawPayload(%s) failed during batch fallback, skip this room: %s', roomId, fallbackError)
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
if (fallbackSuccessCount === 0) {
|
|
2341
|
+
throw e
|
|
2300
2342
|
}
|
|
2301
2343
|
}
|
|
2302
2344
|
}
|
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.128",
|
|
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.155",
|
|
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.155"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@juzi/wechaty-grpc": "^1.0.
|
|
81
|
+
"@juzi/wechaty-grpc": "^1.0.107",
|
|
82
82
|
"clone-class": "^1.1.1",
|
|
83
83
|
"ducks": "^1.0.2",
|
|
84
84
|
"file-box": "^1.5.5",
|
|
@@ -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
|
|