@kodelyth/googlechat 2026.5.39 → 2026.5.42

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 (91) hide show
  1. package/api.ts +3 -0
  2. package/channel-config-api.ts +1 -0
  3. package/channel-plugin-api.ts +1 -0
  4. package/config-api.ts +2 -0
  5. package/contract-api.ts +5 -0
  6. package/dist/actions-YK1wn4ed.js +160 -0
  7. package/dist/api-BkZX4VNX.js +633 -0
  8. package/dist/api.js +3 -0
  9. package/dist/channel-DFZdjXD6.js +584 -0
  10. package/dist/channel-config-api.js +6 -0
  11. package/dist/channel-plugin-api.js +2 -0
  12. package/dist/channel.runtime-en3RNg9S.js +998 -0
  13. package/dist/contract-api.js +3 -0
  14. package/dist/doctor-contract-8SF6XoKj.js +151 -0
  15. package/dist/doctor-contract-api.js +2 -0
  16. package/dist/index.js +22 -0
  17. package/dist/runtime-api-DUH2Cg-0.js +29 -0
  18. package/dist/runtime-api.js +2 -0
  19. package/dist/secret-contract-DWX4ikgT.js +99 -0
  20. package/dist/secret-contract-api.js +2 -0
  21. package/dist/setup-entry.js +15 -0
  22. package/dist/setup-plugin-api.js +75 -0
  23. package/dist/setup-surface-B3Fa7XRx.js +321 -0
  24. package/dist/test-api.js +3 -0
  25. package/doctor-contract-api.ts +1 -0
  26. package/index.ts +20 -0
  27. package/klaw.plugin.json +2 -967
  28. package/package.json +4 -4
  29. package/runtime-api.ts +55 -0
  30. package/secret-contract-api.ts +5 -0
  31. package/setup-entry.ts +13 -0
  32. package/setup-plugin-api.ts +3 -0
  33. package/src/accounts.ts +181 -0
  34. package/src/actions.test.ts +289 -0
  35. package/src/actions.ts +227 -0
  36. package/src/api.ts +316 -0
  37. package/src/approval-auth.test.ts +24 -0
  38. package/src/approval-auth.ts +32 -0
  39. package/src/auth.ts +218 -0
  40. package/src/channel-config.test.ts +39 -0
  41. package/src/channel.adapters.ts +340 -0
  42. package/src/channel.deps.runtime.ts +29 -0
  43. package/src/channel.runtime.ts +17 -0
  44. package/src/channel.setup.ts +98 -0
  45. package/src/channel.test.ts +784 -0
  46. package/src/channel.ts +277 -0
  47. package/src/config-schema.test.ts +31 -0
  48. package/src/config-schema.ts +3 -0
  49. package/src/doctor-contract.test.ts +75 -0
  50. package/src/doctor-contract.ts +182 -0
  51. package/src/doctor.ts +57 -0
  52. package/src/gateway.ts +63 -0
  53. package/src/google-auth.runtime.test.ts +543 -0
  54. package/src/google-auth.runtime.ts +568 -0
  55. package/src/group-policy.ts +17 -0
  56. package/src/monitor-access.test.ts +491 -0
  57. package/src/monitor-access.ts +465 -0
  58. package/src/monitor-durable.test.ts +39 -0
  59. package/src/monitor-durable.ts +23 -0
  60. package/src/monitor-reply-delivery.ts +156 -0
  61. package/src/monitor-routing.ts +65 -0
  62. package/src/monitor-types.ts +33 -0
  63. package/src/monitor-webhook.test.ts +587 -0
  64. package/src/monitor-webhook.ts +303 -0
  65. package/src/monitor.reply-delivery.test.ts +144 -0
  66. package/src/monitor.test.ts +159 -0
  67. package/src/monitor.ts +527 -0
  68. package/src/monitor.webhook-routing.test.ts +257 -0
  69. package/src/runtime.ts +9 -0
  70. package/src/secret-contract.test.ts +60 -0
  71. package/src/secret-contract.ts +161 -0
  72. package/src/setup-core.ts +40 -0
  73. package/src/setup-surface.ts +243 -0
  74. package/src/setup.test.ts +619 -0
  75. package/src/targets.test.ts +453 -0
  76. package/src/targets.ts +66 -0
  77. package/src/types.config.ts +3 -0
  78. package/src/types.ts +73 -0
  79. package/test-api.ts +2 -0
  80. package/tsconfig.json +16 -0
  81. package/api.js +0 -7
  82. package/channel-config-api.js +0 -7
  83. package/channel-plugin-api.js +0 -7
  84. package/contract-api.js +0 -7
  85. package/doctor-contract-api.js +0 -7
  86. package/index.js +0 -7
  87. package/runtime-api.js +0 -7
  88. package/secret-contract-api.js +0 -7
  89. package/setup-entry.js +0 -7
  90. package/setup-plugin-api.js +0 -7
  91. package/test-api.js +0 -7
@@ -0,0 +1,257 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { IncomingMessage } from "node:http";
3
+ import {
4
+ createEmptyPluginRegistry,
5
+ setActivePluginRegistry,
6
+ } from "klaw/plugin-sdk/plugin-test-runtime";
7
+ import { createMockServerResponse } from "klaw/plugin-sdk/test-env";
8
+ import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
9
+ import type { KlawConfig, PluginRuntime } from "../runtime-api.js";
10
+ import type { ResolvedGoogleChatAccount } from "./accounts.js";
11
+ import { verifyGoogleChatRequest } from "./auth.js";
12
+ import {
13
+ handleGoogleChatWebhookRequest,
14
+ registerGoogleChatWebhookTarget,
15
+ } from "./monitor-routing.js";
16
+
17
+ vi.mock("./auth.js", () => ({
18
+ verifyGoogleChatRequest: vi.fn(),
19
+ }));
20
+
21
+ function createWebhookRequest(params: {
22
+ authorization?: string;
23
+ payload: unknown;
24
+ path?: string;
25
+ }): IncomingMessage {
26
+ const req = new EventEmitter() as IncomingMessage & {
27
+ destroyed?: boolean;
28
+ destroy: (error?: Error) => IncomingMessage;
29
+ on: (event: string, listener: (...args: unknown[]) => void) => IncomingMessage;
30
+ };
31
+ req.method = "POST";
32
+ req.url = params.path ?? "/googlechat";
33
+ req.headers = {
34
+ authorization: params.authorization ?? "",
35
+ "content-type": "application/json",
36
+ };
37
+ req.destroyed = false;
38
+ (req as unknown as { socket: { remoteAddress: string } }).socket = {
39
+ remoteAddress: "127.0.0.1",
40
+ };
41
+ req.destroy = () => {
42
+ req.destroyed = true;
43
+ return req;
44
+ };
45
+
46
+ const originalOn = req.on.bind(req);
47
+ let bodyScheduled = false;
48
+ req.on = ((event: string, listener: (...args: unknown[]) => void) => {
49
+ const result = originalOn(event, listener);
50
+ if (!bodyScheduled && event === "data") {
51
+ bodyScheduled = true;
52
+ void Promise.resolve().then(() => {
53
+ req.emit("data", Buffer.from(JSON.stringify(params.payload), "utf-8"));
54
+ if (!req.destroyed) {
55
+ req.emit("end");
56
+ }
57
+ });
58
+ }
59
+ return result;
60
+ }) as IncomingMessage["on"];
61
+
62
+ return req;
63
+ }
64
+
65
+ function createHeaderOnlyWebhookRequest(params: {
66
+ authorization?: string;
67
+ path?: string;
68
+ }): IncomingMessage {
69
+ const req = new EventEmitter() as IncomingMessage;
70
+ req.method = "POST";
71
+ req.url = params.path ?? "/googlechat";
72
+ req.headers = {
73
+ authorization: params.authorization ?? "",
74
+ "content-type": "application/json",
75
+ };
76
+ (req as unknown as { socket: { remoteAddress: string } }).socket = {
77
+ remoteAddress: "127.0.0.1",
78
+ };
79
+ return req;
80
+ }
81
+
82
+ const baseAccount = (accountId: string) =>
83
+ ({
84
+ accountId,
85
+ enabled: true,
86
+ credentialSource: "none",
87
+ config: {},
88
+ }) as ResolvedGoogleChatAccount;
89
+
90
+ function registerTwoTargets() {
91
+ const sinkA = vi.fn();
92
+ const sinkB = vi.fn();
93
+ const logA = vi.fn();
94
+ const logB = vi.fn();
95
+ const core = {} as PluginRuntime;
96
+ const config = {} as KlawConfig;
97
+
98
+ const unregisterA = registerGoogleChatWebhookTarget({
99
+ account: baseAccount("A"),
100
+ config,
101
+ runtime: { log: logA },
102
+ core,
103
+ path: "/googlechat",
104
+ statusSink: sinkA,
105
+ mediaMaxMb: 5,
106
+ });
107
+ const unregisterB = registerGoogleChatWebhookTarget({
108
+ account: baseAccount("B"),
109
+ config,
110
+ runtime: { log: logB },
111
+ core,
112
+ path: "/googlechat",
113
+ statusSink: sinkB,
114
+ mediaMaxMb: 5,
115
+ });
116
+
117
+ return {
118
+ logA,
119
+ logB,
120
+ sinkA,
121
+ sinkB,
122
+ unregister: () => {
123
+ unregisterA();
124
+ unregisterB();
125
+ },
126
+ };
127
+ }
128
+
129
+ async function dispatchWebhookRequest(req: IncomingMessage) {
130
+ const res = createMockServerResponse();
131
+ const handled = await handleGoogleChatWebhookRequest(req, res);
132
+ expect(handled).toBe(true);
133
+ return res;
134
+ }
135
+
136
+ async function expectVerifiedRoute(params: {
137
+ request: IncomingMessage;
138
+ expectedStatus: number;
139
+ sinkA: ReturnType<typeof vi.fn>;
140
+ sinkB: ReturnType<typeof vi.fn>;
141
+ expectedSink: "none" | "A" | "B";
142
+ }) {
143
+ const res = await dispatchWebhookRequest(params.request);
144
+ expect(res.statusCode).toBe(params.expectedStatus);
145
+ const expectedCounts =
146
+ params.expectedSink === "A" ? [1, 0] : params.expectedSink === "B" ? [0, 1] : [0, 0];
147
+ expect(params.sinkA).toHaveBeenCalledTimes(expectedCounts[0]);
148
+ expect(params.sinkB).toHaveBeenCalledTimes(expectedCounts[1]);
149
+ }
150
+
151
+ function mockSecondVerifierSuccess() {
152
+ vi.mocked(verifyGoogleChatRequest)
153
+ .mockResolvedValueOnce({ ok: false, reason: "invalid" })
154
+ .mockResolvedValueOnce({ ok: true });
155
+ }
156
+
157
+ describe("Google Chat webhook routing", () => {
158
+ afterEach(() => {
159
+ setActivePluginRegistry(createEmptyPluginRegistry());
160
+ });
161
+
162
+ afterAll(() => {
163
+ vi.doUnmock("./auth.js");
164
+ vi.resetModules();
165
+ });
166
+
167
+ it("rejects ambiguous routing when multiple targets on the same path verify successfully", async () => {
168
+ vi.mocked(verifyGoogleChatRequest).mockResolvedValue({ ok: true });
169
+
170
+ const { sinkA, sinkB, unregister } = registerTwoTargets();
171
+
172
+ try {
173
+ await expectVerifiedRoute({
174
+ request: createWebhookRequest({
175
+ authorization: "Bearer test-token",
176
+ payload: { type: "ADDED_TO_SPACE", space: { name: "spaces/AAA" } },
177
+ }),
178
+ expectedStatus: 401,
179
+ sinkA,
180
+ sinkB,
181
+ expectedSink: "none",
182
+ });
183
+ } finally {
184
+ unregister();
185
+ }
186
+ });
187
+
188
+ it("routes to the single verified target when earlier targets fail verification", async () => {
189
+ mockSecondVerifierSuccess();
190
+
191
+ const { logA, logB, sinkA, sinkB, unregister } = registerTwoTargets();
192
+
193
+ try {
194
+ await expectVerifiedRoute({
195
+ request: createWebhookRequest({
196
+ authorization: "Bearer test-token",
197
+ payload: { type: "ADDED_TO_SPACE", space: { name: "spaces/BBB" } },
198
+ }),
199
+ expectedStatus: 200,
200
+ sinkA,
201
+ sinkB,
202
+ expectedSink: "B",
203
+ });
204
+ expect(logA).not.toHaveBeenCalled();
205
+ expect(logB).not.toHaveBeenCalled();
206
+ } finally {
207
+ unregister();
208
+ }
209
+ });
210
+
211
+ it("rejects invalid bearer before attempting to read the body", async () => {
212
+ vi.mocked(verifyGoogleChatRequest).mockResolvedValue({ ok: false, reason: "invalid" });
213
+ const { unregister } = registerTwoTargets();
214
+
215
+ try {
216
+ const req = createHeaderOnlyWebhookRequest({
217
+ authorization: "Bearer invalid-token",
218
+ });
219
+ const onSpy = vi.spyOn(req, "on");
220
+ const res = await dispatchWebhookRequest(req);
221
+ expect(res.statusCode).toBe(401);
222
+ expect(onSpy.mock.calls.map(([event]) => event)).not.toContain("data");
223
+ } finally {
224
+ unregister();
225
+ }
226
+ });
227
+
228
+ it("supports add-on requests that provide systemIdToken in the body", async () => {
229
+ mockSecondVerifierSuccess();
230
+ const { sinkA, sinkB, unregister } = registerTwoTargets();
231
+
232
+ try {
233
+ await expectVerifiedRoute({
234
+ request: createWebhookRequest({
235
+ payload: {
236
+ commonEventObject: { hostApp: "CHAT" },
237
+ authorizationEventObject: { systemIdToken: "addon-token" },
238
+ chat: {
239
+ eventTime: "2026-03-02T00:00:00.000Z",
240
+ user: { name: "users/12345", displayName: "Test User" },
241
+ messagePayload: {
242
+ space: { name: "spaces/AAA" },
243
+ message: { text: "Hello from add-on" },
244
+ },
245
+ },
246
+ },
247
+ }),
248
+ expectedStatus: 200,
249
+ sinkA,
250
+ sinkB,
251
+ expectedSink: "B",
252
+ });
253
+ } finally {
254
+ unregister();
255
+ }
256
+ });
257
+ });
package/src/runtime.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { createPluginRuntimeStore } from "klaw/plugin-sdk/runtime-store";
2
+ import type { PluginRuntime } from "klaw/plugin-sdk/runtime-store";
3
+
4
+ const { setRuntime: setGoogleChatRuntime, getRuntime: getGoogleChatRuntime } =
5
+ createPluginRuntimeStore<PluginRuntime>({
6
+ pluginId: "googlechat",
7
+ errorMessage: "Google Chat runtime not initialized",
8
+ });
9
+ export { getGoogleChatRuntime, setGoogleChatRuntime };
@@ -0,0 +1,60 @@
1
+ import type { KlawConfig } from "klaw/plugin-sdk/config-contracts";
2
+ import {
3
+ applyResolvedAssignments,
4
+ createResolverContext,
5
+ resolveSecretRefValues,
6
+ } from "klaw/plugin-sdk/secret-ref-runtime";
7
+ import { describe, expect, it } from "vitest";
8
+ import { collectRuntimeConfigAssignments } from "./secret-contract.js";
9
+
10
+ describe("googlechat secret contract", () => {
11
+ it("resolves account serviceAccount SecretRefs for enabled accounts", async () => {
12
+ const sourceConfig = {
13
+ channels: {
14
+ googlechat: {
15
+ enabled: true,
16
+ accounts: {
17
+ work: {
18
+ enabled: true,
19
+ serviceAccountRef: {
20
+ source: "env",
21
+ provider: "default",
22
+ id: "GOOGLECHAT_SERVICE_ACCOUNT",
23
+ },
24
+ },
25
+ },
26
+ },
27
+ },
28
+ } satisfies KlawConfig;
29
+ const resolvedConfig: KlawConfig = structuredClone(sourceConfig);
30
+ const context = createResolverContext({
31
+ sourceConfig,
32
+ env: {
33
+ GOOGLECHAT_SERVICE_ACCOUNT: '{"client_email":"bot@example.com"}',
34
+ },
35
+ });
36
+
37
+ collectRuntimeConfigAssignments({
38
+ config: resolvedConfig,
39
+ defaults: undefined,
40
+ context,
41
+ });
42
+
43
+ const resolved = await resolveSecretRefValues(
44
+ context.assignments.map((assignment) => assignment.ref),
45
+ {
46
+ config: sourceConfig,
47
+ env: context.env,
48
+ cache: context.cache,
49
+ },
50
+ );
51
+ applyResolvedAssignments({
52
+ assignments: context.assignments,
53
+ resolved,
54
+ });
55
+
56
+ const workAccount = resolvedConfig.channels?.googlechat?.accounts?.work;
57
+ expect(workAccount?.serviceAccount).toBe('{"client_email":"bot@example.com"}');
58
+ expect(context.warnings).toStrictEqual([]);
59
+ });
60
+ });
@@ -0,0 +1,161 @@
1
+ import {
2
+ getChannelSurface,
3
+ hasOwnProperty,
4
+ pushAssignment,
5
+ pushInactiveSurfaceWarning,
6
+ pushWarning,
7
+ resolveChannelAccountSurface,
8
+ type ResolverContext,
9
+ type SecretDefaults,
10
+ } from "klaw/plugin-sdk/channel-secret-basic-runtime";
11
+ import { coerceSecretRef } from "klaw/plugin-sdk/secret-ref-runtime";
12
+
13
+ type GoogleChatAccountLike = {
14
+ serviceAccount?: unknown;
15
+ serviceAccountRef?: unknown;
16
+ accounts?: Record<string, unknown>;
17
+ };
18
+
19
+ export const secretTargetRegistryEntries: import("klaw/plugin-sdk/channel-secret-basic-runtime").SecretTargetRegistryEntry[] =
20
+ [
21
+ {
22
+ id: "channels.googlechat.accounts.*.serviceAccount",
23
+ targetType: "channels.googlechat.serviceAccount",
24
+ targetTypeAliases: ["channels.googlechat.accounts.*.serviceAccount"],
25
+ configFile: "klaw.json",
26
+ pathPattern: "channels.googlechat.accounts.*.serviceAccount",
27
+ refPathPattern: "channels.googlechat.accounts.*.serviceAccountRef",
28
+ secretShape: "sibling_ref",
29
+ expectedResolvedValue: "string-or-object",
30
+ includeInPlan: true,
31
+ includeInConfigure: true,
32
+ includeInAudit: true,
33
+ accountIdPathSegmentIndex: 3,
34
+ },
35
+ {
36
+ id: "channels.googlechat.serviceAccount",
37
+ targetType: "channels.googlechat.serviceAccount",
38
+ configFile: "klaw.json",
39
+ pathPattern: "channels.googlechat.serviceAccount",
40
+ refPathPattern: "channels.googlechat.serviceAccountRef",
41
+ secretShape: "sibling_ref",
42
+ expectedResolvedValue: "string-or-object",
43
+ includeInPlan: true,
44
+ includeInConfigure: true,
45
+ includeInAudit: true,
46
+ },
47
+ ];
48
+
49
+ function resolveSecretInputRef(params: {
50
+ value: unknown;
51
+ refValue?: unknown;
52
+ defaults?: SecretDefaults;
53
+ }) {
54
+ const explicitRef = coerceSecretRef(params.refValue, params.defaults);
55
+ const inlineRef = explicitRef ? null : coerceSecretRef(params.value, params.defaults);
56
+ return {
57
+ explicitRef,
58
+ inlineRef,
59
+ ref: explicitRef ?? inlineRef,
60
+ };
61
+ }
62
+
63
+ function collectGoogleChatAccountAssignment(params: {
64
+ target: GoogleChatAccountLike;
65
+ path: string;
66
+ defaults?: SecretDefaults;
67
+ context: ResolverContext;
68
+ active?: boolean;
69
+ inactiveReason?: string;
70
+ }): void {
71
+ const { explicitRef, ref } = resolveSecretInputRef({
72
+ value: params.target.serviceAccount,
73
+ refValue: params.target.serviceAccountRef,
74
+ defaults: params.defaults,
75
+ });
76
+ if (!ref) {
77
+ return;
78
+ }
79
+ if (params.active === false) {
80
+ pushInactiveSurfaceWarning({
81
+ context: params.context,
82
+ path: `${params.path}.serviceAccount`,
83
+ details: params.inactiveReason,
84
+ });
85
+ return;
86
+ }
87
+ if (
88
+ explicitRef &&
89
+ params.target.serviceAccount !== undefined &&
90
+ !coerceSecretRef(params.target.serviceAccount, params.defaults)
91
+ ) {
92
+ pushWarning(params.context, {
93
+ code: "SECRETS_REF_OVERRIDES_PLAINTEXT",
94
+ path: params.path,
95
+ message: `${params.path}: serviceAccountRef is set; runtime will ignore plaintext serviceAccount.`,
96
+ });
97
+ }
98
+ pushAssignment(params.context, {
99
+ ref,
100
+ path: `${params.path}.serviceAccount`,
101
+ expected: "string-or-object",
102
+ apply: (value) => {
103
+ params.target.serviceAccount = value;
104
+ },
105
+ });
106
+ }
107
+
108
+ export function collectRuntimeConfigAssignments(params: {
109
+ config: { channels?: Record<string, unknown> };
110
+ defaults?: SecretDefaults;
111
+ context: ResolverContext;
112
+ }): void {
113
+ const resolved = getChannelSurface(params.config, "googlechat");
114
+ if (!resolved) {
115
+ return;
116
+ }
117
+ const googleChat = resolved.channel as GoogleChatAccountLike;
118
+ const surface = resolveChannelAccountSurface(googleChat as Record<string, unknown>);
119
+ const topLevelServiceAccountActive = !surface.channelEnabled
120
+ ? false
121
+ : !surface.hasExplicitAccounts
122
+ ? true
123
+ : surface.accounts.some(
124
+ ({ account, enabled }) =>
125
+ enabled &&
126
+ !hasOwnProperty(account, "serviceAccount") &&
127
+ !hasOwnProperty(account, "serviceAccountRef"),
128
+ );
129
+ collectGoogleChatAccountAssignment({
130
+ target: googleChat,
131
+ path: "channels.googlechat",
132
+ defaults: params.defaults,
133
+ context: params.context,
134
+ active: topLevelServiceAccountActive,
135
+ inactiveReason: "no enabled account inherits this top-level Google Chat serviceAccount.",
136
+ });
137
+ if (!surface.hasExplicitAccounts) {
138
+ return;
139
+ }
140
+ for (const { accountId, account, enabled } of surface.accounts) {
141
+ if (
142
+ !hasOwnProperty(account, "serviceAccount") &&
143
+ !hasOwnProperty(account, "serviceAccountRef")
144
+ ) {
145
+ continue;
146
+ }
147
+ collectGoogleChatAccountAssignment({
148
+ target: account as GoogleChatAccountLike,
149
+ path: `channels.googlechat.accounts.${accountId}`,
150
+ defaults: params.defaults,
151
+ context: params.context,
152
+ active: enabled,
153
+ inactiveReason: "Google Chat account is disabled.",
154
+ });
155
+ }
156
+ }
157
+
158
+ export const channelSecrets = {
159
+ secretTargetRegistryEntries,
160
+ collectRuntimeConfigAssignments,
161
+ };
@@ -0,0 +1,40 @@
1
+ import {
2
+ createPatchedAccountSetupAdapter,
3
+ createSetupInputPresenceValidator,
4
+ } from "klaw/plugin-sdk/setup-runtime";
5
+
6
+ const channel = "googlechat" as const;
7
+
8
+ export const googlechatSetupAdapter = createPatchedAccountSetupAdapter({
9
+ channelKey: channel,
10
+ validateInput: createSetupInputPresenceValidator({
11
+ defaultAccountOnlyEnvError:
12
+ "GOOGLE_CHAT_SERVICE_ACCOUNT env vars can only be used for the default account.",
13
+ whenNotUseEnv: [
14
+ {
15
+ someOf: ["token", "tokenFile"],
16
+ message: "Google Chat requires --token (service account JSON) or --token-file.",
17
+ },
18
+ ],
19
+ }),
20
+ buildPatch: (input) => {
21
+ const patch = input.useEnv
22
+ ? {}
23
+ : input.tokenFile
24
+ ? { serviceAccountFile: input.tokenFile }
25
+ : input.token
26
+ ? { serviceAccount: input.token }
27
+ : {};
28
+ const audienceType = input.audienceType?.trim();
29
+ const audience = input.audience?.trim();
30
+ const webhookPath = input.webhookPath?.trim();
31
+ const webhookUrl = input.webhookUrl?.trim();
32
+ return {
33
+ ...patch,
34
+ ...(audienceType ? { audienceType } : {}),
35
+ ...(audience ? { audience } : {}),
36
+ ...(webhookPath ? { webhookPath } : {}),
37
+ ...(webhookUrl ? { webhookUrl } : {}),
38
+ };
39
+ },
40
+ });