piece-signal-cli-rest-api 0.2.13 → 0.2.15

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/package.json CHANGED
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "name": "piece-signal-cli-rest-api",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
+ "scripts": {
5
+ "build": "echo \"piece-signal-cli-rest-api: kein zusätzlicher Build-Schritt erforderlich (bereits als JS ausgeliefert)\"",
6
+ "publish:npm": "npm publish --access public"
7
+ },
4
8
  "dependencies": {
5
9
  "@sinclair/typebox": "0.34.11",
6
10
  "ai": "5.0.104",
@@ -26,4 +30,4 @@
26
30
  "types": "./src/index.d.ts",
27
31
  "main": "./src/index.js",
28
32
  "type": "commonjs"
29
- }
33
+ }
@@ -28,11 +28,11 @@ exports.requestApprovalMessage = (0, pieces_framework_1.createAction)({
28
28
  options: {
29
29
  options: [
30
30
  { label: 'Emoji Reactions', value: 'emoji' },
31
- { label: 'Text Replies', value: 'text' },
32
- { label: 'Both', value: 'both' }
31
+ { label: 'Text Replies (Quote)', value: 'text' },
32
+ { label: 'Direct Messages (1:1 only)', value: 'direct' }
33
33
  ]
34
34
  },
35
- defaultValue: ['both']
35
+ defaultValue: ['text']
36
36
  }),
37
37
  timeout_seconds: pieces_framework_1.Property.Number({
38
38
  displayName: 'Timeout (seconds)',
@@ -44,7 +44,7 @@ exports.requestApprovalMessage = (0, pieces_framework_1.createAction)({
44
44
  run(context) {
45
45
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
46
46
  if (context.executionType === shared_1.ExecutionType.BEGIN) {
47
- const { number, recipients, message, accept_reaction_modes = ['both'], timeout_seconds = 86400 } = context.propsValue;
47
+ const { number, recipients, message, accept_reaction_modes = ['text'], timeout_seconds = 86400 } = context.propsValue;
48
48
  (0, shared_2.assertNotNullOrUndefined)(message, 'message');
49
49
  (0, shared_2.assertNotNullOrUndefined)(recipients, 'recipients');
50
50
  const apiClient = new api_client_1.SignalCliApiClient(context.auth.props);
@@ -83,7 +83,9 @@ exports.requestApprovalMessage = (0, pieces_framework_1.createAction)({
83
83
  const targetAuthor = formattedNumber;
84
84
  const createdAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
85
85
  // Create store key using milliseconds for precise matching
86
- const storeKey = `approval:${messageTimestampMs}:${targetAuthor}`;
86
+ // Store-Key mit Timestamp + eindeutiger Flow-Run-ID
87
+ // flowRunId ist pro Flow-Ausführung eindeutig, verhindert Kollisionen
88
+ const storeKey = `approval:${messageTimestampMs}:${context.run.id}`;
87
89
  // DEBUG: Log storing approval mapping
88
90
  console.log('[RequestApprovalMessage] DEBUG - Storing approval mapping:', {
89
91
  storeKey,
@@ -105,6 +107,7 @@ exports.requestApprovalMessage = (0, pieces_framework_1.createAction)({
105
107
  messageTimestampMs: messageTimestampMs,
106
108
  messageTimestamp: messageTimestamp,
107
109
  targetAuthor: targetAuthor,
110
+ recipients: recipients,
108
111
  acceptReactionModes: accept_reaction_modes,
109
112
  timeoutSeconds: timeout_seconds,
110
113
  createdAt: createdAt,
@@ -9,6 +9,8 @@ const pieces_framework_1 = require("@activepieces/pieces-framework");
9
9
  const pieces_common_1 = require("@activepieces/pieces-common");
10
10
  const ws_1 = tslib_1.__importDefault(require("ws"));
11
11
  // Function to cleanup expired approval mappings
12
+ // Diese Funktion ist die EINZIGE Stelle, an der entschieden wird, ob ein Approval abgelaufen ist.
13
+ // Alle abgelaufenen Approvals werden hier gelöscht, bevor das Matching stattfindet.
12
14
  function cleanupExpiredApprovals(store) {
13
15
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
14
16
  const keysListKey = 'approval:keys';
@@ -50,17 +52,25 @@ function tryResumeApprovalFlow(message, store, apiUrl) {
50
52
  if (!dataMessage) {
51
53
  return { resumed: false };
52
54
  }
55
+ // Check if it's a group message
56
+ const isGroupMessage = !!dataMessage.groupInfo;
57
+
53
58
  // Check if it's a reaction or a quote (text reply)
54
59
  const isReaction = !!dataMessage.reaction;
55
60
  const isTextReply = !!dataMessage.quote;
56
- if (!isReaction && !isTextReply) {
61
+
62
+ // If it's neither a reaction nor a quote, and it's a group message, ignore it
63
+ if (!isReaction && !isTextReply && isGroupMessage) {
57
64
  return { resumed: false };
58
65
  }
59
66
  console.log('[ReceiveMessages] DEBUG - Processing message:', {
60
67
  hasReaction: isReaction,
61
68
  hasQuote: isTextReply,
69
+ isGroupMessage: isGroupMessage,
62
70
  });
63
71
  // Cleanup expired approvals
72
+ // WICHTIG: cleanupExpiredApprovals ist die EINZIGE Stelle für Timeout-Entscheidungen.
73
+ // Alle nachfolgenden Matching-Schritte arbeiten nur auf bereits gefilterten (nicht abgelaufenen) Approvals.
64
74
  yield cleanupExpiredApprovals(store);
65
75
  const keysListKey = 'approval:keys';
66
76
  const existingKeys = (yield store.get(keysListKey, pieces_framework_1.StoreScope.PROJECT)) || [];
@@ -77,19 +87,72 @@ function tryResumeApprovalFlow(message, store, apiUrl) {
77
87
  // signal-cli returns timestamps in SECONDS, convert to milliseconds for matching
78
88
  const targetTimestampSeconds = reaction.targetSentTimestamp || reaction.targetTimestamp;
79
89
  const targetTimestampMs = targetTimestampSeconds * 1000;
80
- const targetAuthor = reaction.targetAuthor;
81
90
  const reactionEmoji = reaction.emoji;
82
- storeKey = `approval:${targetTimestampMs}:${targetAuthor}`;
83
- mapping = yield store.get(storeKey, pieces_framework_1.StoreScope.PROJECT);
84
- if (mapping) {
85
- const acceptModes = mapping.acceptReactionModes || ['both'];
86
- if (acceptModes.includes('emoji') || acceptModes.includes('both')) {
87
- responseContent = reactionEmoji; // Return the emoji directly
88
- reactionType = 'emoji';
91
+ const messageSource = message.envelope.source;
92
+
93
+ console.log('[ReceiveMessages] DEBUG - Processing emoji reaction:', {
94
+ targetTimestampMs,
95
+ targetTimestampSeconds,
96
+ reactionEmoji,
97
+ messageSource,
98
+ isGroupMessage,
99
+ });
100
+
101
+ // Alle Approvals durchsuchen: Exakter Timestamp-Match + Empfänger-Validierung
102
+ for (const key of existingKeys) {
103
+ const candidateMapping = yield store.get(key, pieces_framework_1.StoreScope.PROJECT);
104
+ if (!candidateMapping) continue;
105
+
106
+ // Exakter Timestamp-Match (beide vom Signal-Netzwerk, müssen identisch sein)
107
+ if (candidateMapping.messageTimestampMs !== targetTimestampMs) continue;
108
+
109
+ console.log('[ReceiveMessages] DEBUG - Timestamp match found, checking recipient:', {
110
+ key,
111
+ candidateTimestampMs: candidateMapping.messageTimestampMs,
112
+ recipients: candidateMapping.recipients,
113
+ });
114
+
115
+ // Empfänger-Validierung (1:1 vs. Gruppe)
116
+ const recipients = candidateMapping.recipients || [];
117
+ let isValidRecipient = false;
118
+
119
+ if (isGroupMessage) {
120
+ // Bei Gruppen: Prüfe Gruppen-ID
121
+ const groupId = dataMessage.groupInfo.groupId;
122
+ isValidRecipient = Array.isArray(recipients) && recipients.includes(groupId);
123
+ console.log('[ReceiveMessages] DEBUG - Group recipient check:', {
124
+ groupId,
125
+ isValidRecipient,
126
+ });
127
+ } else {
128
+ // Bei 1:1: Prüfe, ob Absender in recipients-Liste steht
129
+ isValidRecipient = Array.isArray(recipients) && recipients.includes(messageSource);
130
+ console.log('[ReceiveMessages] DEBUG - 1:1 recipient check:', {
131
+ messageSource,
132
+ isValidRecipient,
133
+ });
89
134
  }
90
- else {
91
- // Mapping found but emoji mode not allowed
92
- mapping = null;
135
+
136
+ if (!isValidRecipient) continue;
137
+
138
+ // Prüfe, ob Emoji-Modus erlaubt ist
139
+ const acceptModes = candidateMapping.acceptReactionModes || [];
140
+ if (acceptModes.includes('emoji')) {
141
+ mapping = candidateMapping;
142
+ storeKey = key;
143
+ responseContent = reactionEmoji;
144
+ reactionType = 'emoji';
145
+ console.log('[ReceiveMessages] DEBUG - Emoji reaction match found:', {
146
+ key,
147
+ flowRunId: mapping.flowRunId,
148
+ reactionEmoji,
149
+ });
150
+ break; // Gefunden, Suche beenden
151
+ } else {
152
+ console.log('[ReceiveMessages] DEBUG - Emoji mode not allowed for this approval:', {
153
+ key,
154
+ acceptModes,
155
+ });
93
156
  }
94
157
  }
95
158
  }
@@ -99,20 +162,111 @@ function tryResumeApprovalFlow(message, store, apiUrl) {
99
162
  // signal-cli returns timestamps in SECONDS, convert to milliseconds for matching
100
163
  const quoteTimestampSeconds = quote.id || quote.timestamp;
101
164
  const quoteTimestampMs = quoteTimestampSeconds * 1000;
102
- const quoteAuthor = quote.author;
103
165
  const replyText = dataMessage.message || '';
104
- // Try to find mapping by quote timestamp
105
- storeKey = `approval:${quoteTimestampMs}:${quoteAuthor}`;
106
- mapping = yield store.get(storeKey, pieces_framework_1.StoreScope.PROJECT);
107
- if (mapping) {
108
- const acceptModes = mapping.acceptReactionModes || ['both'];
109
- if (acceptModes.includes('text') || acceptModes.includes('both')) {
110
- responseContent = replyText; // Return the text directly
166
+ const messageSource = message.envelope.source;
167
+
168
+ console.log('[ReceiveMessages] DEBUG - Processing text reply (quote):', {
169
+ quoteTimestampMs,
170
+ quoteTimestampSeconds,
171
+ replyText: replyText.substring(0, 50), // First 50 chars for logging
172
+ messageSource,
173
+ isGroupMessage,
174
+ });
175
+
176
+ // Alle Approvals durchsuchen: Exakter Timestamp-Match + Empfänger-Validierung
177
+ for (const key of existingKeys) {
178
+ const candidateMapping = yield store.get(key, pieces_framework_1.StoreScope.PROJECT);
179
+ if (!candidateMapping) continue;
180
+
181
+ // Exakter Timestamp-Match (beide vom Signal-Netzwerk, müssen identisch sein)
182
+ if (candidateMapping.messageTimestampMs !== quoteTimestampMs) continue;
183
+
184
+ console.log('[ReceiveMessages] DEBUG - Timestamp match found, checking recipient:', {
185
+ key,
186
+ candidateTimestampMs: candidateMapping.messageTimestampMs,
187
+ recipients: candidateMapping.recipients,
188
+ });
189
+
190
+ // Empfänger-Validierung (1:1 vs. Gruppe)
191
+ const recipients = candidateMapping.recipients || [];
192
+ let isValidRecipient = false;
193
+
194
+ if (isGroupMessage) {
195
+ // Bei Gruppen: Prüfe Gruppen-ID
196
+ const groupId = dataMessage.groupInfo.groupId;
197
+ isValidRecipient = Array.isArray(recipients) && recipients.includes(groupId);
198
+ console.log('[ReceiveMessages] DEBUG - Group recipient check:', {
199
+ groupId,
200
+ isValidRecipient,
201
+ });
202
+ } else {
203
+ // Bei 1:1: Prüfe, ob Absender in recipients-Liste steht
204
+ isValidRecipient = Array.isArray(recipients) && recipients.includes(messageSource);
205
+ console.log('[ReceiveMessages] DEBUG - 1:1 recipient check:', {
206
+ messageSource,
207
+ isValidRecipient,
208
+ });
209
+ }
210
+
211
+ if (!isValidRecipient) continue;
212
+
213
+ // Prüfe, ob Text-Modus erlaubt ist
214
+ const acceptModes = candidateMapping.acceptReactionModes || [];
215
+ if (acceptModes.includes('text')) {
216
+ mapping = candidateMapping;
217
+ storeKey = key;
218
+ responseContent = replyText;
111
219
  reactionType = 'text';
220
+ console.log('[ReceiveMessages] DEBUG - Text reply match found:', {
221
+ key,
222
+ flowRunId: mapping.flowRunId,
223
+ replyText: replyText.substring(0, 50),
224
+ });
225
+ break; // Gefunden, Suche beenden
226
+ } else {
227
+ console.log('[ReceiveMessages] DEBUG - Text mode not allowed for this approval:', {
228
+ key,
229
+ acceptModes,
230
+ });
112
231
  }
113
- else {
114
- // Mapping found but text mode not allowed
115
- mapping = null;
232
+ }
233
+ }
234
+ // Handle Direct Messages (1:1 only) - if no reaction/quote found
235
+ if (!mapping && !isGroupMessage) {
236
+ const messageText = dataMessage.message || '';
237
+ const messageSource = message.envelope.source;
238
+
239
+ console.log('[ReceiveMessages] DEBUG - Processing direct message:', {
240
+ messageText: messageText.substring(0, 50), // First 50 chars for logging
241
+ messageSource,
242
+ });
243
+
244
+ // Iterate through all approval keys to find a match
245
+ // Keine Timeout-Prüfung mehr - cleanupExpiredApprovals hat bereits alle abgelaufenen entfernt
246
+ for (const key of existingKeys) {
247
+ const candidateMapping = yield store.get(key, pieces_framework_1.StoreScope.PROJECT);
248
+ if (!candidateMapping) continue;
249
+
250
+ // Check if direct mode is enabled
251
+ const acceptModes = candidateMapping.acceptReactionModes || [];
252
+ if (!acceptModes.includes('direct')) {
253
+ continue; // Direct mode not enabled for this approval
254
+ }
255
+
256
+ // Check if the message source is one of the recipients
257
+ const recipients = candidateMapping.recipients || [];
258
+ if (Array.isArray(recipients) && recipients.includes(messageSource)) {
259
+ // Found a match! This is a 1:1 conversation response
260
+ mapping = candidateMapping;
261
+ storeKey = key;
262
+ responseContent = messageText; // Use the message text as response
263
+ reactionType = 'direct';
264
+ console.log('[ReceiveMessages] DEBUG - Direct message match found:', {
265
+ key,
266
+ flowRunId: mapping.flowRunId,
267
+ messageText: messageText.substring(0, 50),
268
+ });
269
+ break; // Found a match, stop searching
116
270
  }
117
271
  }
118
272
  }
@@ -128,19 +282,7 @@ function tryResumeApprovalFlow(message, store, apiUrl) {
128
282
  reactionType,
129
283
  responseContent,
130
284
  });
131
- // Check if expired
132
- const ageInSeconds = currentTimestamp - mapping.createdAt;
133
- const isExpired = ageInSeconds > mapping.timeoutSeconds;
134
- if (isExpired) {
135
- // Expired - delete and return
136
- yield store.delete(storeKey, pieces_framework_1.StoreScope.PROJECT);
137
- const flowRunMappingKey = `approval:flowRun:${mapping.flowRunId}`;
138
- yield store.delete(flowRunMappingKey, pieces_framework_1.StoreScope.PROJECT);
139
- const updatedKeys = existingKeys.filter(key => key !== storeKey);
140
- yield store.put(keysListKey, updatedKeys, pieces_framework_1.StoreScope.PROJECT);
141
- console.log('[ReceiveMessages] DEBUG - Approval expired');
142
- return { resumed: false };
143
- }
285
+ // Keine Timeout-Prüfung mehr - cleanupExpiredApprovals hat bereits alle abgelaufenen entfernt
144
286
  // Resume the flow with responseContent
145
287
  const resumeUrl = `${apiUrl}v1/flow-runs/${mapping.flowRunId}/requests/${mapping.requestId}?responseContent=${encodeURIComponent(responseContent)}&reactionType=${reactionType}&responder=${encodeURIComponent(responder || '')}`;
146
288
  console.log('[ReceiveMessages] DEBUG - Attempting resume:', {