@juzi/wechaty-puppet-service 1.0.129 → 1.0.130

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.
Files changed (46) hide show
  1. package/dist/cjs/src/client/puppet-service.d.ts +2 -0
  2. package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
  3. package/dist/cjs/src/client/puppet-service.js +42 -0
  4. package/dist/cjs/src/client/puppet-service.js.map +1 -1
  5. package/dist/cjs/src/client/puppet-service.spec.js +107 -0
  6. package/dist/cjs/src/client/puppet-service.spec.js.map +1 -1
  7. package/dist/cjs/src/package-json.js +4 -4
  8. package/dist/cjs/src/server/event-stream-manager.d.ts.map +1 -1
  9. package/dist/cjs/src/server/event-stream-manager.js +14 -0
  10. package/dist/cjs/src/server/event-stream-manager.js.map +1 -1
  11. package/dist/cjs/src/server/event-stream-manager.spec.d.ts +3 -0
  12. package/dist/cjs/src/server/event-stream-manager.spec.d.ts.map +1 -0
  13. package/dist/cjs/src/server/event-stream-manager.spec.js +22 -0
  14. package/dist/cjs/src/server/event-stream-manager.spec.js.map +1 -0
  15. package/dist/cjs/src/server/puppet-implementation.d.ts.map +1 -1
  16. package/dist/cjs/src/server/puppet-implementation.js +44 -0
  17. package/dist/cjs/src/server/puppet-implementation.js.map +1 -1
  18. package/dist/cjs/src/server/puppet-implementation.spec.js +98 -0
  19. package/dist/cjs/src/server/puppet-implementation.spec.js.map +1 -1
  20. package/dist/esm/src/client/puppet-service.d.ts +2 -0
  21. package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
  22. package/dist/esm/src/client/puppet-service.js +42 -0
  23. package/dist/esm/src/client/puppet-service.js.map +1 -1
  24. package/dist/esm/src/client/puppet-service.spec.js +107 -0
  25. package/dist/esm/src/client/puppet-service.spec.js.map +1 -1
  26. package/dist/esm/src/package-json.js +4 -4
  27. package/dist/esm/src/server/event-stream-manager.d.ts.map +1 -1
  28. package/dist/esm/src/server/event-stream-manager.js +14 -0
  29. package/dist/esm/src/server/event-stream-manager.js.map +1 -1
  30. package/dist/esm/src/server/event-stream-manager.spec.d.ts +3 -0
  31. package/dist/esm/src/server/event-stream-manager.spec.d.ts.map +1 -0
  32. package/dist/esm/src/server/event-stream-manager.spec.js +20 -0
  33. package/dist/esm/src/server/event-stream-manager.spec.js.map +1 -0
  34. package/dist/esm/src/server/puppet-implementation.d.ts.map +1 -1
  35. package/dist/esm/src/server/puppet-implementation.js +44 -0
  36. package/dist/esm/src/server/puppet-implementation.js.map +1 -1
  37. package/dist/esm/src/server/puppet-implementation.spec.js +98 -0
  38. package/dist/esm/src/server/puppet-implementation.spec.js.map +1 -1
  39. package/package.json +4 -4
  40. package/src/client/puppet-service.spec.ts +147 -0
  41. package/src/client/puppet-service.ts +54 -0
  42. package/src/package-json.ts +4 -4
  43. package/src/server/event-stream-manager.spec.ts +30 -0
  44. package/src/server/event-stream-manager.ts +14 -0
  45. package/src/server/puppet-implementation.spec.ts +127 -0
  46. package/src/server/puppet-implementation.ts +50 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juzi/wechaty-puppet-service",
3
- "version": "1.0.129",
3
+ "version": "1.0.130",
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.156",
54
+ "@juzi/wechaty-puppet": "^1.0.157",
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.156"
74
+ "@juzi/wechaty-puppet": "^1.0.157"
75
75
  },
76
76
  "dependencies": {
77
- "@juzi/wechaty-grpc": "^1.0.108",
77
+ "@juzi/wechaty-grpc": "^1.0.109",
78
78
  "clone-class": "^1.1.1",
79
79
  "ducks": "^1.0.2",
80
80
  "file-box": "^1.5.5",
@@ -154,6 +154,153 @@ test('callInviteWithMedia rejects when the server returns an empty call_id', asy
154
154
  )
155
155
  })
156
156
 
157
+ let orgBroadcastTestId = 0
158
+
159
+ function createOrgBroadcastPuppet (
160
+ payloadResponse?: grpcPuppet.OrgBroadcastPayloadResponse,
161
+ ): { puppet: PuppetService, executeRequestList: grpcPuppet.OrgBroadcastExecuteRequest[] } {
162
+ const puppet = new PuppetService({
163
+ token: `org-broadcast-test-${orgBroadcastTestId++}`,
164
+ })
165
+
166
+ const executeRequestList: grpcPuppet.OrgBroadcastExecuteRequest[] = []
167
+
168
+ ;(puppet as any)._grpcManager = {
169
+ client: {
170
+ orgBroadcastExecute: (
171
+ request : grpcPuppet.OrgBroadcastExecuteRequest,
172
+ callback : (error: null | Error, response: grpcPuppet.OrgBroadcastExecuteResponse) => void,
173
+ ) => {
174
+ executeRequestList.push(request)
175
+ callback(null, new grpcPuppet.OrgBroadcastExecuteResponse())
176
+ },
177
+ orgBroadcastPayload: (
178
+ request : grpcPuppet.OrgBroadcastPayloadRequest,
179
+ callback : (error: null | Error, response?: grpcPuppet.OrgBroadcastPayloadResponse) => void,
180
+ ) => {
181
+ void request
182
+ callback(null, payloadResponse)
183
+ },
184
+ },
185
+ }
186
+
187
+ return { puppet, executeRequestList }
188
+ }
189
+
190
+ test('orgBroadcastPayload maps every response field onto OrgBroadcastPayload', async t => {
191
+ const roomTarget = new grpcPuppet.OrgBroadcastTarget()
192
+ roomTarget.setRoomId('R:room-1')
193
+ roomTarget.setStatus(grpcPuppet.OrgBroadcastTargetStatus.ORG_BROADCAST_TARGET_STATUS_UNCONFIRMED)
194
+
195
+ const sentRoomTarget = new grpcPuppet.OrgBroadcastTarget()
196
+ sentRoomTarget.setRoomId('R:room-2')
197
+ sentRoomTarget.setStatus(grpcPuppet.OrgBroadcastTargetStatus.ORG_BROADCAST_TARGET_STATUS_SENT)
198
+
199
+ const response = new grpcPuppet.OrgBroadcastPayloadResponse()
200
+ response.setId('4728994241730369413')
201
+ response.setSendType(2)
202
+ response.setConversationType(1)
203
+ response.setCreatorId('creator-1')
204
+ response.setExecTime(1758600000000)
205
+ response.setStatus(3)
206
+ response.setCanCancel(true)
207
+ response.setAllowSelect(true)
208
+ response.setSent(false)
209
+ response.setTotalCount(7)
210
+ response.setSentCount(5)
211
+ response.setContentListJson('[{"contentType":2}]')
212
+ response.setTargetsList([ roomTarget, sentRoomTarget ])
213
+
214
+ const { puppet } = createOrgBroadcastPuppet(response)
215
+ const payload = await puppet.orgBroadcastPayload('4728994241730369413')
216
+
217
+ t.same(payload, {
218
+ id : '4728994241730369413',
219
+ sendType : 2,
220
+ conversationType : 1,
221
+ creatorId : 'creator-1',
222
+ execTime : 1758600000000,
223
+ status : 3,
224
+ canCancel : true,
225
+ allowSelect : true,
226
+ sent : false,
227
+ totalCount : 7,
228
+ sentCount : 5,
229
+ contentListJson : '[{"contentType":2}]',
230
+ targets : [
231
+ { contactId: undefined, roomId: 'R:room-1', status: 4 },
232
+ { contactId: undefined, roomId: 'R:room-2', status: 1 },
233
+ ],
234
+ }, 'maps every field and drops the empty contact_id of room targets')
235
+ })
236
+
237
+ test('orgBroadcastPayload maps empty creator_id and contact targets', async t => {
238
+ const contactTarget = new grpcPuppet.OrgBroadcastTarget()
239
+ contactTarget.setContactId('contact-1')
240
+
241
+ const response = new grpcPuppet.OrgBroadcastPayloadResponse()
242
+ response.setId('org-broadcast-2')
243
+ response.setTargetsList([ contactTarget ])
244
+
245
+ const { puppet } = createOrgBroadcastPuppet(response)
246
+ const payload = await puppet.orgBroadcastPayload('org-broadcast-2')
247
+
248
+ t.equal(payload.creatorId, undefined, 'maps an empty creator_id to undefined')
249
+ t.same(payload.targets, [ { contactId: 'contact-1', roomId: undefined, status: 0 } ], 'drops the empty room_id of contact targets')
250
+ })
251
+
252
+ test('orgBroadcastExecute sends all targets as an empty target_ids when targetIds is omitted', async t => {
253
+ const { puppet, executeRequestList } = createOrgBroadcastPuppet()
254
+
255
+ await puppet.orgBroadcastExecute('org-broadcast-1')
256
+
257
+ t.equal(executeRequestList.length, 1, 'sends exactly one request')
258
+ t.equal(executeRequestList[0]!.getId(), 'org-broadcast-1', 'maps orgBroadcastId onto id')
259
+ t.same(executeRequestList[0]!.getTargetIdsList(), [], 'leaves target_ids empty')
260
+ })
261
+
262
+ test('orgBroadcastExecute forwards the selected targets', async t => {
263
+ const { puppet, executeRequestList } = createOrgBroadcastPuppet()
264
+
265
+ await puppet.orgBroadcastExecute('org-broadcast-1', [ 'contact-1', 'contact-2' ])
266
+
267
+ t.same(executeRequestList[0]!.getTargetIdsList(), [ 'contact-1', 'contact-2' ], 'maps targetIds onto target_ids')
268
+ })
269
+
270
+ test('orgBroadcastExecute rejects an explicit empty targetIds instead of sending to all targets', async t => {
271
+ const { puppet, executeRequestList } = createOrgBroadcastPuppet()
272
+
273
+ await t.rejects(
274
+ puppet.orgBroadcastExecute('org-broadcast-1', []),
275
+ /targetIds is empty/,
276
+ 'does not turn an empty selection into sending to all targets',
277
+ )
278
+ t.equal(executeRequestList.length, 0, 'never reaches the server')
279
+ })
280
+
281
+ test('org broadcast grpc events are emitted as puppet events', async t => {
282
+ const { puppet } = createOrgBroadcastPuppet()
283
+
284
+ const createdSpy = sinon.spy()
285
+ const sentSpy = sinon.spy()
286
+ puppet.on('org-broadcast-created', createdSpy)
287
+ puppet.on('org-broadcast-sent', sentSpy)
288
+
289
+ const createdEvent = new grpcPuppet.EventResponse()
290
+ createdEvent.setType(grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_CREATED)
291
+ createdEvent.setPayload(JSON.stringify({ orgBroadcastId: 'org-broadcast-1', messageId: 'message-1' }))
292
+
293
+ const sentEvent = new grpcPuppet.EventResponse()
294
+ sentEvent.setType(grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_SENT)
295
+ sentEvent.setPayload(JSON.stringify({ orgBroadcastId: 'org-broadcast-1' }))
296
+
297
+ await (puppet as any).onGrpcStreamEvent(createdEvent)
298
+ await (puppet as any).onGrpcStreamEvent(sentEvent)
299
+
300
+ t.same(createdSpy.args[0], [ { orgBroadcastId: 'org-broadcast-1', messageId: 'message-1' } ], 'emits org-broadcast-created')
301
+ t.same(sentSpy.args[0], [ { orgBroadcastId: 'org-broadcast-1' } ], 'emits org-broadcast-sent')
302
+ })
303
+
157
304
  test('version()', async t => {
158
305
  const puppet = new PuppetService({
159
306
  token: 'test',
@@ -435,6 +435,12 @@ class PuppetService extends PUPPET.Puppet {
435
435
  case grpcPuppet.EventType.EVENT_TYPE_CALL:
436
436
  this.emit('call', JSON.parse(payload) as PUPPET.payloads.EventCall)
437
437
  break
438
+ case grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_CREATED:
439
+ this.emit('org-broadcast-created', JSON.parse(payload) as PUPPET.payloads.EventOrgBroadcastCreated)
440
+ break
441
+ case grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_SENT:
442
+ this.emit('org-broadcast-sent', JSON.parse(payload) as PUPPET.payloads.EventOrgBroadcastSent)
443
+ break
438
444
 
439
445
  default:
440
446
  // Huan(202003): in default, the `type` type should be `never`, please check.
@@ -3728,6 +3734,54 @@ class PuppetService extends PUPPET.Puppet {
3728
3734
  return result
3729
3735
  }
3730
3736
 
3737
+ override async orgBroadcastPayload (orgBroadcastId: string): Promise<PUPPET.payloads.OrgBroadcast> {
3738
+ this.log.verbose('PuppetService', 'orgBroadcastPayload(%s)', orgBroadcastId)
3739
+
3740
+ const request = new grpcPuppet.OrgBroadcastPayloadRequest()
3741
+ request.setId(orgBroadcastId)
3742
+
3743
+ const response = await util.promisify(
3744
+ this.grpcManager.client.orgBroadcastPayload.bind(this.grpcManager.client),
3745
+ )(request)
3746
+
3747
+ return {
3748
+ id : response.getId(),
3749
+ sendType : response.getSendType(),
3750
+ conversationType : response.getConversationType() as PUPPET.types.OrgBroadcastConversationType,
3751
+ creatorId : response.getCreatorId() || undefined,
3752
+ execTime : response.getExecTime(),
3753
+ status : response.getStatus(),
3754
+ canCancel : response.getCanCancel(),
3755
+ allowSelect : response.getAllowSelect(),
3756
+ sent : response.getSent(),
3757
+ totalCount : response.getTotalCount(),
3758
+ sentCount : response.getSentCount(),
3759
+ contentListJson : response.getContentListJson(),
3760
+ targets : response.getTargetsList().map(targetPb => ({
3761
+ contactId : targetPb.getContactId() || undefined,
3762
+ roomId : targetPb.getRoomId() || undefined,
3763
+ status : targetPb.getStatus() as number as PUPPET.types.OrgBroadcastTargetStatus,
3764
+ })),
3765
+ }
3766
+ }
3767
+
3768
+ override async orgBroadcastExecute (orgBroadcastId: string, targetIds?: string[]): Promise<void> {
3769
+ this.log.verbose('PuppetService', 'orgBroadcastExecute(%s, %s)', orgBroadcastId, targetIds?.length)
3770
+
3771
+ // on the wire an empty target_ids means all targets, so an explicit empty list must not pass through
3772
+ if (targetIds && targetIds.length === 0) {
3773
+ throw new Error('targetIds is empty, omit it to send to all targets of the org broadcast')
3774
+ }
3775
+
3776
+ const request = new grpcPuppet.OrgBroadcastExecuteRequest()
3777
+ request.setId(orgBroadcastId)
3778
+ request.setTargetIdsList(targetIds ?? [])
3779
+
3780
+ await util.promisify(
3781
+ this.grpcManager.client.orgBroadcastExecute.bind(this.grpcManager.client),
3782
+ )(request)
3783
+ }
3784
+
3731
3785
  healthCheckInterval?: NodeJS.Timeout
3732
3786
  startHealthCheck () {
3733
3787
  this.healthCheckInterval = setInterval(() => {
@@ -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.129",
7
+ "version": "1.0.130",
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.156",
58
+ "@juzi/wechaty-puppet": "^1.0.157",
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.156"
78
+ "@juzi/wechaty-puppet": "^1.0.157"
79
79
  },
80
80
  "dependencies": {
81
- "@juzi/wechaty-grpc": "^1.0.108",
81
+ "@juzi/wechaty-grpc": "^1.0.109",
82
82
  "clone-class": "^1.1.1",
83
83
  "ducks": "^1.0.2",
84
84
  "file-box": "^1.5.5",
@@ -0,0 +1,30 @@
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 { puppet as grpcPuppet } from '@juzi/wechaty-grpc'
9
+ import { PuppetMock } from '@juzi/wechaty-puppet-mock'
10
+
11
+ import { EventStreamManager } from './event-stream-manager.js'
12
+
13
+ test('org broadcast puppet events are forwarded as their own grpc event types', async t => {
14
+ const puppet = new PuppetMock() as any
15
+ const manager = new EventStreamManager(puppet)
16
+
17
+ const write = sinon.spy()
18
+ ;(manager as any).eventStream = { write }
19
+ manager.connectPuppetEventToStreamingCall()
20
+
21
+ puppet.emit('org-broadcast-created', { orgBroadcastId: 'org-broadcast-1', messageId: 'message-1' })
22
+ puppet.emit('org-broadcast-sent', { orgBroadcastId: 'org-broadcast-1' })
23
+
24
+ const [ created, sent ] = write.args.map(args => args[0] as grpcPuppet.EventResponse)
25
+
26
+ t.equal(created?.getType(), grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_CREATED, 'forwards org-broadcast-created as EVENT_TYPE_ORG_BROADCAST_CREATED')
27
+ t.same(JSON.parse(created!.getPayload()), { orgBroadcastId: 'org-broadcast-1', messageId: 'message-1' }, 'keeps the created payload')
28
+ t.equal(sent?.getType(), grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_SENT, 'forwards org-broadcast-sent as EVENT_TYPE_ORG_BROADCAST_SENT')
29
+ t.same(JSON.parse(sent!.getPayload()), { orgBroadcastId: 'org-broadcast-1' }, 'keeps the sent payload')
30
+ })
@@ -336,6 +336,20 @@ class EventStreamManager {
336
336
  this.offCallbackList.push(off)
337
337
  break
338
338
  }
339
+ case 'org-broadcast-created': {
340
+ const listener = (payload: PUPPET.payloads.EventOrgBroadcastCreated) => this.grpcEmit(grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_CREATED, payload)
341
+ this.puppet.on('org-broadcast-created', listener)
342
+ const off = () => this.puppet.off('org-broadcast-created', listener)
343
+ this.offCallbackList.push(off)
344
+ break
345
+ }
346
+ case 'org-broadcast-sent': {
347
+ const listener = (payload: PUPPET.payloads.EventOrgBroadcastSent) => this.grpcEmit(grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_SENT, payload)
348
+ this.puppet.on('org-broadcast-sent', listener)
349
+ const off = () => this.puppet.off('org-broadcast-sent', listener)
350
+ this.offCallbackList.push(off)
351
+ break
352
+ }
339
353
  case 'reset':
340
354
  // the `reset` event should be dealed internally, should not send out
341
355
  break
@@ -119,3 +119,130 @@ test('callInviteWithMedia rejects an empty callId returned by the puppet', async
119
119
 
120
120
  t.match(error?.message, /empty callId/, 'does not pass an unusable empty callId back on the wire')
121
121
  })
122
+
123
+ function invokeOrgBroadcast<Req, Res> (
124
+ puppet : any,
125
+ method : 'orgBroadcastPayload' | 'orgBroadcastExecute',
126
+ request : Req,
127
+ ): Promise<{ error: null | Error, response: null | Res }> {
128
+ const impl = puppetImplementation(puppet, FileBox)
129
+
130
+ return new Promise(resolve => {
131
+ void (impl[method] as any)(
132
+ { request } as any,
133
+ ((error: any, response: any) => resolve({ error, response })) as any,
134
+ )
135
+ })
136
+ }
137
+
138
+ test('orgBroadcastPayload serializes every payload field onto the response', async t => {
139
+ const puppet = new PuppetMock() as any
140
+ const stub = sinon.stub().resolves({
141
+ id : 'org-broadcast-1',
142
+ sendType : 2,
143
+ conversationType : 1,
144
+ creatorId : 'creator-1',
145
+ execTime : 1758600000000,
146
+ status : 3,
147
+ canCancel : true,
148
+ allowSelect : true,
149
+ sent : false,
150
+ totalCount : 7,
151
+ sentCount : 5,
152
+ contentListJson : '[{"contentType":2}]',
153
+ targets : [ { roomId: 'R:1', status: 4 }, { roomId: 'R:2', status: 1 } ],
154
+ })
155
+ puppet.orgBroadcastPayload = stub
156
+
157
+ const request = new grpcPuppet.OrgBroadcastPayloadRequest()
158
+ request.setId('org-broadcast-1')
159
+
160
+ const { error, response } = await invokeOrgBroadcast<grpcPuppet.OrgBroadcastPayloadRequest, grpcPuppet.OrgBroadcastPayloadResponse>(puppet, 'orgBroadcastPayload', request)
161
+
162
+ t.equal(error, null, 'returns without error')
163
+ t.same(stub.args[0], [ 'org-broadcast-1' ], 'passes the id to the puppet')
164
+ t.same(response?.toObject(), {
165
+ id : 'org-broadcast-1',
166
+ sendType : 2,
167
+ conversationType: 1,
168
+ creatorId : 'creator-1',
169
+ execTime : 1758600000000,
170
+ status : 3,
171
+ canCancel : true,
172
+ allowSelect : true,
173
+ sent : false,
174
+ totalCount : 7,
175
+ sentCount : 5,
176
+ contentListJson : '[{"contentType":2}]',
177
+ targetsList : [
178
+ { contactId: '', roomId: 'R:1', status: 4 },
179
+ { contactId: '', roomId: 'R:2', status: 1 },
180
+ ],
181
+ }, 'serializes every field')
182
+ })
183
+
184
+ test('orgBroadcastPayload serializes a missing creatorId and contact targets as empty strings', async t => {
185
+ const puppet = new PuppetMock() as any
186
+ puppet.orgBroadcastPayload = sinon.stub().resolves({
187
+ id : 'org-broadcast-2',
188
+ sendType : 0,
189
+ conversationType : 0,
190
+ execTime : 0,
191
+ status : 0,
192
+ canCancel : false,
193
+ allowSelect : false,
194
+ sent : false,
195
+ totalCount : 1,
196
+ sentCount : 0,
197
+ contentListJson : '[]',
198
+ targets : [ { contactId: 'contact-1', status: 0 } ],
199
+ })
200
+
201
+ const request = new grpcPuppet.OrgBroadcastPayloadRequest()
202
+ request.setId('org-broadcast-2')
203
+
204
+ const { response } = await invokeOrgBroadcast<grpcPuppet.OrgBroadcastPayloadRequest, grpcPuppet.OrgBroadcastPayloadResponse>(puppet, 'orgBroadcastPayload', request)
205
+
206
+ t.equal(response?.getCreatorId(), '', 'serializes a missing creatorId as empty')
207
+ t.same(response?.getTargetsList()[0]?.toObject(), { contactId: 'contact-1', roomId: '', status: 0 }, 'leaves room_id empty for a contact target')
208
+ })
209
+
210
+ test('orgBroadcastExecute turns an empty target_ids into sending to all targets', async t => {
211
+ const puppet = new PuppetMock() as any
212
+ const stub = sinon.stub().resolves()
213
+ puppet.orgBroadcastExecute = stub
214
+
215
+ const request = new grpcPuppet.OrgBroadcastExecuteRequest()
216
+ request.setId('org-broadcast-1')
217
+
218
+ const { error } = await invokeOrgBroadcast(puppet, 'orgBroadcastExecute', request)
219
+
220
+ t.equal(error, null, 'returns without error')
221
+ t.same(stub.args[0], [ 'org-broadcast-1', undefined ], 'omits targetIds')
222
+ })
223
+
224
+ test('orgBroadcastExecute forwards the selected target_ids', async t => {
225
+ const puppet = new PuppetMock() as any
226
+ const stub = sinon.stub().resolves()
227
+ puppet.orgBroadcastExecute = stub
228
+
229
+ const request = new grpcPuppet.OrgBroadcastExecuteRequest()
230
+ request.setId('org-broadcast-1')
231
+ request.setTargetIdsList([ 'contact-1' ])
232
+
233
+ await invokeOrgBroadcast(puppet, 'orgBroadcastExecute', request)
234
+
235
+ t.same(stub.args[0], [ 'org-broadcast-1', [ 'contact-1' ] ], 'passes targetIds through')
236
+ })
237
+
238
+ test('orgBroadcastExecute reports a puppet failure as a grpc error', async t => {
239
+ const puppet = new PuppetMock() as any
240
+ puppet.orgBroadcastExecute = sinon.stub().rejects(new Error('broadcast not found'))
241
+
242
+ const request = new grpcPuppet.OrgBroadcastExecuteRequest()
243
+ request.setId('org-broadcast-missing')
244
+
245
+ const { error } = await invokeOrgBroadcast(puppet, 'orgBroadcastExecute', request)
246
+
247
+ t.match(error?.message, /broadcast not found/, 'surfaces the puppet error')
248
+ })
@@ -3017,6 +3017,56 @@ function puppetImplementation (
3017
3017
  }
3018
3018
  },
3019
3019
 
3020
+ orgBroadcastPayload: async (call, callback) => {
3021
+ log.verbose('PuppetServiceImpl', 'orgBroadcastPayload()')
3022
+
3023
+ try {
3024
+ const orgBroadcastId = call.request.getId()
3025
+ const payload = await puppet.orgBroadcastPayload(orgBroadcastId)
3026
+
3027
+ const response = new grpcPuppet.OrgBroadcastPayloadResponse()
3028
+ response.setId(payload.id)
3029
+ response.setSendType(payload.sendType)
3030
+ response.setConversationType(payload.conversationType)
3031
+ response.setCreatorId(payload.creatorId ?? '')
3032
+ response.setExecTime(payload.execTime)
3033
+ response.setStatus(payload.status)
3034
+ response.setCanCancel(payload.canCancel)
3035
+ response.setAllowSelect(payload.allowSelect)
3036
+ response.setSent(payload.sent)
3037
+ response.setTotalCount(payload.totalCount)
3038
+ response.setSentCount(payload.sentCount)
3039
+ response.setContentListJson(payload.contentListJson)
3040
+ response.setTargetsList(payload.targets.map(target => {
3041
+ const targetPb = new grpcPuppet.OrgBroadcastTarget()
3042
+ targetPb.setContactId(target.contactId ?? '')
3043
+ targetPb.setRoomId(target.roomId ?? '')
3044
+ targetPb.setStatus(target.status as number as grpcPuppet.OrgBroadcastTargetStatusMap[keyof grpcPuppet.OrgBroadcastTargetStatusMap])
3045
+ return targetPb
3046
+ }))
3047
+
3048
+ return callback(null, response)
3049
+ } catch (e) {
3050
+ return grpcError('orgBroadcastPayload', e, callback)
3051
+ }
3052
+ },
3053
+
3054
+ orgBroadcastExecute: async (call, callback) => {
3055
+ log.verbose('PuppetServiceImpl', 'orgBroadcastExecute()')
3056
+
3057
+ try {
3058
+ const orgBroadcastId = call.request.getId()
3059
+ const targetIds = call.request.getTargetIdsList()
3060
+
3061
+ // an empty target_ids on the wire means all targets of the org broadcast
3062
+ await puppet.orgBroadcastExecute(orgBroadcastId, targetIds.length ? targetIds : undefined)
3063
+
3064
+ return callback(null, new grpcPuppet.OrgBroadcastExecuteResponse())
3065
+ } catch (e) {
3066
+ return grpcError('orgBroadcastExecute', e, callback)
3067
+ }
3068
+ },
3069
+
3020
3070
  download: async (call) => {
3021
3071
  log.verbose('PuppetServiceImpl', 'download()')
3022
3072