@online5880/opensession 0.1.1 → 0.1.4

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.
@@ -0,0 +1,197 @@
1
+ const DEFAULT_TIMEOUT_MS = 5000;
2
+
3
+ function withTimeout(ms) {
4
+ return AbortSignal.timeout(Number.isInteger(ms) && ms > 0 ? ms : DEFAULT_TIMEOUT_MS);
5
+ }
6
+
7
+ function normalizeArray(value) {
8
+ if (!Array.isArray(value)) {
9
+ return [];
10
+ }
11
+ return value;
12
+ }
13
+
14
+ function normalizeHeaders(value) {
15
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
16
+ return {};
17
+ }
18
+
19
+ const output = {};
20
+ for (const [key, headerValue] of Object.entries(value)) {
21
+ if (typeof headerValue === 'string' && headerValue.trim().length > 0) {
22
+ output[key] = headerValue;
23
+ }
24
+ }
25
+ return output;
26
+ }
27
+
28
+ function normalizeWebhookTarget(target) {
29
+ if (!target || typeof target !== 'object') {
30
+ return null;
31
+ }
32
+
33
+ const url = typeof target.url === 'string' ? target.url.trim() : '';
34
+ if (!url) {
35
+ return null;
36
+ }
37
+
38
+ const eventTypes = normalizeArray(target.eventTypes).filter((item) => typeof item === 'string' && item.trim().length > 0);
39
+ const source = typeof target.source === 'string' && target.source.trim().length > 0 ? target.source.trim() : null;
40
+
41
+ return {
42
+ type: 'webhook',
43
+ url,
44
+ eventTypes,
45
+ source,
46
+ headers: normalizeHeaders(target.headers),
47
+ timeoutMs: Number.isInteger(target.timeoutMs) && target.timeoutMs > 0 ? target.timeoutMs : DEFAULT_TIMEOUT_MS
48
+ };
49
+ }
50
+
51
+ function normalizeRule(rule) {
52
+ if (!rule || typeof rule !== 'object') {
53
+ return null;
54
+ }
55
+
56
+ const name = typeof rule.name === 'string' && rule.name.trim().length > 0 ? rule.name.trim() : 'unnamed-rule';
57
+ const when = rule.when && typeof rule.when === 'object' && !Array.isArray(rule.when) ? rule.when : {};
58
+ const actions = normalizeArray(rule.actions)
59
+ .map((action) => normalizeWebhookTarget(action))
60
+ .filter(Boolean);
61
+
62
+ if (actions.length === 0) {
63
+ return null;
64
+ }
65
+
66
+ return {
67
+ name,
68
+ when: {
69
+ eventType: typeof when.eventType === 'string' ? when.eventType.trim() : null,
70
+ source: typeof when.source === 'string' ? when.source.trim() : null,
71
+ projectKey: typeof when.projectKey === 'string' ? when.projectKey.trim() : null
72
+ },
73
+ actions
74
+ };
75
+ }
76
+
77
+ function shouldApplyTarget(target, envelope) {
78
+ if (!target) {
79
+ return false;
80
+ }
81
+
82
+ if (target.source && target.source !== envelope.source) {
83
+ return false;
84
+ }
85
+
86
+ if (target.eventTypes.length > 0 && !target.eventTypes.includes(envelope.eventType)) {
87
+ return false;
88
+ }
89
+
90
+ return true;
91
+ }
92
+
93
+ function ruleMatches(rule, envelope) {
94
+ const { when } = rule;
95
+
96
+ if (when.eventType && when.eventType !== envelope.eventType) {
97
+ return false;
98
+ }
99
+
100
+ if (when.source && when.source !== envelope.source) {
101
+ return false;
102
+ }
103
+
104
+ if (when.projectKey && when.projectKey !== envelope.projectKey) {
105
+ return false;
106
+ }
107
+
108
+ return true;
109
+ }
110
+
111
+ async function postJson(url, body, options = {}) {
112
+ const headers = {
113
+ 'Content-Type': 'application/json',
114
+ ...(options.headers ?? {})
115
+ };
116
+
117
+ const response = await fetch(url, {
118
+ method: 'POST',
119
+ headers,
120
+ body: JSON.stringify(body),
121
+ signal: withTimeout(options.timeoutMs)
122
+ });
123
+
124
+ const text = await response.text();
125
+ return {
126
+ ok: response.ok,
127
+ status: response.status,
128
+ body: text.slice(0, 400)
129
+ };
130
+ }
131
+
132
+ export async function dispatchAutomation(envelope, automationConfig = {}) {
133
+ const rules = normalizeArray(automationConfig.rules)
134
+ .map((rule) => normalizeRule(rule))
135
+ .filter(Boolean);
136
+ const directTargets = normalizeArray(automationConfig.webhooks)
137
+ .map((target) => normalizeWebhookTarget(target))
138
+ .filter(Boolean)
139
+ .filter((target) => shouldApplyTarget(target, envelope));
140
+
141
+ const results = [];
142
+
143
+ for (const target of directTargets) {
144
+ try {
145
+ const response = await postJson(target.url, envelope, target);
146
+ results.push({
147
+ channel: 'webhook',
148
+ mode: 'direct',
149
+ destination: target.url,
150
+ ...response
151
+ });
152
+ } catch (error) {
153
+ results.push({
154
+ channel: 'webhook',
155
+ mode: 'direct',
156
+ destination: target.url,
157
+ ok: false,
158
+ status: 0,
159
+ body: error instanceof Error ? error.message : String(error)
160
+ });
161
+ }
162
+ }
163
+
164
+ for (const rule of rules) {
165
+ if (!ruleMatches(rule, envelope)) {
166
+ continue;
167
+ }
168
+
169
+ for (const action of rule.actions) {
170
+ try {
171
+ const response = await postJson(action.url, {
172
+ rule: rule.name,
173
+ event: envelope
174
+ }, action);
175
+ results.push({
176
+ channel: 'webhook',
177
+ mode: 'rule',
178
+ rule: rule.name,
179
+ destination: action.url,
180
+ ...response
181
+ });
182
+ } catch (error) {
183
+ results.push({
184
+ channel: 'webhook',
185
+ mode: 'rule',
186
+ rule: rule.name,
187
+ destination: action.url,
188
+ ok: false,
189
+ status: 0,
190
+ body: error instanceof Error ? error.message : String(error)
191
+ });
192
+ }
193
+ }
194
+ }
195
+
196
+ return results;
197
+ }