@empiricalrun/playwright-utils 0.47.4 → 0.48.1
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/CHANGELOG.md +17 -0
- package/dist/webhook.d.ts +3 -0
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +157 -37
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @empiricalrun/playwright-utils
|
|
2
2
|
|
|
3
|
+
## 0.48.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 376c2db: feat: prettier webhook urls
|
|
8
|
+
|
|
9
|
+
## 0.48.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 12b14e5: feat: webhook testing with inbox worker
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [12b14e5]
|
|
18
|
+
- @empiricalrun/dashboard-client@0.3.0
|
|
19
|
+
|
|
3
20
|
## 0.47.4
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/dist/webhook.d.ts
CHANGED
|
@@ -13,8 +13,11 @@ export type WebhookRequest = {
|
|
|
13
13
|
created_at: string;
|
|
14
14
|
updated_at: string;
|
|
15
15
|
};
|
|
16
|
+
type WebhookProvider = "inbox" | "webhook-site";
|
|
16
17
|
export declare function getWebhookUrl(options?: {
|
|
17
18
|
expiry?: number | null;
|
|
19
|
+
provider?: WebhookProvider;
|
|
18
20
|
}): Promise<string>;
|
|
19
21
|
export declare function queryWebhookRequests(webhookUrl: string, content: string | string[]): Promise<WebhookRequest[]>;
|
|
22
|
+
export {};
|
|
20
23
|
//# sourceMappingURL=webhook.d.ts.map
|
package/dist/webhook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,eAAe,GAAG,OAAO,GAAG,cAAc,CAAC;AA0ShD,wBAAsB,aAAa,CACjC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;CAAO,GACnE,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GACzB,OAAO,CAAC,cAAc,EAAE,CAAC,CAO3B"}
|
package/dist/webhook.js
CHANGED
|
@@ -3,18 +3,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getWebhookUrl = getWebhookUrl;
|
|
4
4
|
exports.queryWebhookRequests = queryWebhookRequests;
|
|
5
5
|
const dashboard_client_1 = require("@empiricalrun/dashboard-client");
|
|
6
|
+
// ── webhook.site provider ──
|
|
6
7
|
const WEBHOOK_SITE_BASE_URL = "https://webhook.site";
|
|
7
8
|
const DEFAULT_TOKEN_EXPIRY = 604800;
|
|
8
|
-
|
|
9
|
+
function escapeQueryTerm(term) {
|
|
10
|
+
return term.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
11
|
+
}
|
|
12
|
+
function buildContentQuery(content) {
|
|
13
|
+
const terms = Array.isArray(content) ? content : [content];
|
|
14
|
+
if (terms.length === 0 || terms.some((t) => t.length === 0)) {
|
|
15
|
+
throw new Error("content must be a non-empty string or string[]");
|
|
16
|
+
}
|
|
17
|
+
return terms.map((t) => `content:"${escapeQueryTerm(t)}"`).join(" AND ");
|
|
18
|
+
}
|
|
19
|
+
async function webhookSiteGetUrl(options) {
|
|
9
20
|
const expiry = options.expiry === undefined ? DEFAULT_TOKEN_EXPIRY : options.expiry;
|
|
10
21
|
if (process.env.EMPIRICALRUN_API_KEY) {
|
|
11
22
|
const apiClient = new dashboard_client_1.DashboardAPIClient({
|
|
12
23
|
authType: "project-api-key",
|
|
13
24
|
});
|
|
14
|
-
const result = await apiClient.
|
|
25
|
+
const result = await apiClient.request("/api/webhook-site/proxy", {
|
|
15
26
|
method: "POST",
|
|
16
|
-
path: "/token",
|
|
17
|
-
body: { expiry },
|
|
27
|
+
body: { method: "POST", path: "/token", body: { expiry } },
|
|
18
28
|
});
|
|
19
29
|
const tokenData = result.data;
|
|
20
30
|
return `${WEBHOOK_SITE_BASE_URL}/${tokenData.uuid}`;
|
|
@@ -38,32 +48,7 @@ async function getWebhookUrl(options = {}) {
|
|
|
38
48
|
const { uuid } = await response.json();
|
|
39
49
|
return `${WEBHOOK_SITE_BASE_URL}/${uuid}`;
|
|
40
50
|
}
|
|
41
|
-
function
|
|
42
|
-
let url;
|
|
43
|
-
try {
|
|
44
|
-
url = new URL(webhookUrl);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
throw new Error(`Invalid webhook URL: ${webhookUrl}`);
|
|
48
|
-
}
|
|
49
|
-
const parts = url.pathname.split("/").filter(Boolean);
|
|
50
|
-
const token = parts[parts.length - 1];
|
|
51
|
-
if (!token) {
|
|
52
|
-
throw new Error(`Invalid webhook URL (no token in path): ${webhookUrl}`);
|
|
53
|
-
}
|
|
54
|
-
return token;
|
|
55
|
-
}
|
|
56
|
-
function escapeQueryTerm(term) {
|
|
57
|
-
return term.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
58
|
-
}
|
|
59
|
-
function buildContentQuery(content) {
|
|
60
|
-
const terms = Array.isArray(content) ? content : [content];
|
|
61
|
-
if (terms.length === 0 || terms.some((t) => t.length === 0)) {
|
|
62
|
-
throw new Error("content must be a non-empty string or string[]");
|
|
63
|
-
}
|
|
64
|
-
return terms.map((t) => `content:"${escapeQueryTerm(t)}"`).join(" AND ");
|
|
65
|
-
}
|
|
66
|
-
async function queryViaProxy(token, content) {
|
|
51
|
+
async function webhookSiteQueryViaProxy(token, content) {
|
|
67
52
|
const apiClient = new dashboard_client_1.DashboardAPIClient({
|
|
68
53
|
authType: "project-api-key",
|
|
69
54
|
});
|
|
@@ -72,9 +57,12 @@ async function queryViaProxy(token, content) {
|
|
|
72
57
|
sorting: "newest",
|
|
73
58
|
per_page: "20",
|
|
74
59
|
});
|
|
75
|
-
const result = await apiClient.
|
|
76
|
-
method: "
|
|
77
|
-
|
|
60
|
+
const result = await apiClient.request("/api/webhook-site/proxy", {
|
|
61
|
+
method: "POST",
|
|
62
|
+
body: {
|
|
63
|
+
method: "GET",
|
|
64
|
+
path: `/token/${token}/requests?${params.toString()}`,
|
|
65
|
+
},
|
|
78
66
|
});
|
|
79
67
|
const webhookResponse = result.data;
|
|
80
68
|
if (!webhookResponse ||
|
|
@@ -84,7 +72,7 @@ async function queryViaProxy(token, content) {
|
|
|
84
72
|
}
|
|
85
73
|
return webhookResponse.data;
|
|
86
74
|
}
|
|
87
|
-
async function
|
|
75
|
+
async function webhookSiteQueryDirect(token, content) {
|
|
88
76
|
const apiKey = process.env.WEBHOOK_SITE_API_KEY;
|
|
89
77
|
if (!apiKey) {
|
|
90
78
|
throw new Error("Either EMPIRICALRUN_API_KEY or WEBHOOK_SITE_API_KEY must be set");
|
|
@@ -111,10 +99,142 @@ async function queryDirect(token, content) {
|
|
|
111
99
|
}
|
|
112
100
|
return json.data;
|
|
113
101
|
}
|
|
102
|
+
async function webhookSiteQuery(token, content) {
|
|
103
|
+
if (process.env.EMPIRICALRUN_API_KEY) {
|
|
104
|
+
return webhookSiteQueryViaProxy(token, content);
|
|
105
|
+
}
|
|
106
|
+
return webhookSiteQueryDirect(token, content);
|
|
107
|
+
}
|
|
108
|
+
// ── inbox provider ──
|
|
109
|
+
const INBOX_WORKER_URL = "https://inbox.empirical.run";
|
|
110
|
+
function inboxRowToWebhookRequest(row) {
|
|
111
|
+
let headers = {};
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse(row.headers || "{}");
|
|
114
|
+
headers = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [
|
|
115
|
+
k,
|
|
116
|
+
Array.isArray(v) ? v : [String(v)],
|
|
117
|
+
]));
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
let query = null;
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(row.query_params || "{}");
|
|
123
|
+
if (Object.keys(parsed).length > 0)
|
|
124
|
+
query = parsed;
|
|
125
|
+
}
|
|
126
|
+
catch { }
|
|
127
|
+
return {
|
|
128
|
+
uuid: row.id,
|
|
129
|
+
token_id: row.path_id,
|
|
130
|
+
method: row.method,
|
|
131
|
+
content: row.body || "",
|
|
132
|
+
headers,
|
|
133
|
+
url: "",
|
|
134
|
+
query,
|
|
135
|
+
ip: "",
|
|
136
|
+
hostname: "",
|
|
137
|
+
user_agent: headers["user-agent"]?.[0] || "",
|
|
138
|
+
size: (row.body || "").length,
|
|
139
|
+
created_at: row.received_at,
|
|
140
|
+
updated_at: row.received_at,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function inboxGetUrl() {
|
|
144
|
+
if (process.env.EMPIRICALRUN_API_KEY) {
|
|
145
|
+
const apiClient = new dashboard_client_1.DashboardAPIClient({
|
|
146
|
+
authType: "project-api-key",
|
|
147
|
+
});
|
|
148
|
+
const result = await apiClient.request("/api/inbox/proxy", {
|
|
149
|
+
method: "POST",
|
|
150
|
+
body: { method: "POST", path: "/api/hooks/token" },
|
|
151
|
+
});
|
|
152
|
+
const tokenData = result.data;
|
|
153
|
+
return `${INBOX_WORKER_URL}/hooks/${tokenData.uuid}`;
|
|
154
|
+
}
|
|
155
|
+
const response = await fetch(`${INBOX_WORKER_URL}/api/hooks/token`, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
const body = await response.text().catch(() => "");
|
|
160
|
+
throw new Error(`inbox webhook error [${response.status} ${response.statusText}]: ${body}`);
|
|
161
|
+
}
|
|
162
|
+
const { uuid } = await response.json();
|
|
163
|
+
return `${INBOX_WORKER_URL}/hooks/${uuid}`;
|
|
164
|
+
}
|
|
165
|
+
async function inboxQuery(token, content) {
|
|
166
|
+
const terms = Array.isArray(content) ? content : [content];
|
|
167
|
+
const params = new URLSearchParams({
|
|
168
|
+
path_id: token,
|
|
169
|
+
sorting: "newest",
|
|
170
|
+
per_page: "20",
|
|
171
|
+
});
|
|
172
|
+
for (const t of terms) {
|
|
173
|
+
params.append("content", t);
|
|
174
|
+
}
|
|
175
|
+
if (process.env.EMPIRICALRUN_API_KEY) {
|
|
176
|
+
const apiClient = new dashboard_client_1.DashboardAPIClient({
|
|
177
|
+
authType: "project-api-key",
|
|
178
|
+
});
|
|
179
|
+
const result = await apiClient.request("/api/inbox/proxy", {
|
|
180
|
+
method: "POST",
|
|
181
|
+
body: {
|
|
182
|
+
method: "GET",
|
|
183
|
+
path: `/api/hooks?${params.toString()}`,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
const response = result.data;
|
|
187
|
+
if (!response || !Array.isArray(response.data)) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
return response.data.map(inboxRowToWebhookRequest);
|
|
191
|
+
}
|
|
192
|
+
const rawResponse = await fetch(`${INBOX_WORKER_URL}/api/hooks?${params.toString()}`);
|
|
193
|
+
if (!rawResponse.ok) {
|
|
194
|
+
const body = await rawResponse.text().catch(() => "");
|
|
195
|
+
throw new Error(`inbox webhook error [${rawResponse.status} ${rawResponse.statusText}]: ${body}`);
|
|
196
|
+
}
|
|
197
|
+
const json = await rawResponse.json().catch((e) => {
|
|
198
|
+
throw new Error(`inbox webhook returned invalid JSON: ${String(e)}`);
|
|
199
|
+
});
|
|
200
|
+
if (!json || !Array.isArray(json.data)) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
return json.data.map(inboxRowToWebhookRequest);
|
|
204
|
+
}
|
|
205
|
+
// ── public API ──
|
|
206
|
+
function extractToken(webhookUrl) {
|
|
207
|
+
let url;
|
|
208
|
+
try {
|
|
209
|
+
url = new URL(webhookUrl);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
throw new Error(`Invalid webhook URL: ${webhookUrl}`);
|
|
213
|
+
}
|
|
214
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
215
|
+
const token = parts[parts.length - 1];
|
|
216
|
+
if (!token) {
|
|
217
|
+
throw new Error(`Invalid webhook URL (no token in path): ${webhookUrl}`);
|
|
218
|
+
}
|
|
219
|
+
return token;
|
|
220
|
+
}
|
|
221
|
+
function detectProvider(webhookUrl) {
|
|
222
|
+
if (webhookUrl.includes("webhook.site"))
|
|
223
|
+
return "webhook-site";
|
|
224
|
+
return "inbox";
|
|
225
|
+
}
|
|
226
|
+
async function getWebhookUrl(options = {}) {
|
|
227
|
+
const provider = options.provider || "webhook-site";
|
|
228
|
+
if (provider === "webhook-site") {
|
|
229
|
+
return webhookSiteGetUrl(options);
|
|
230
|
+
}
|
|
231
|
+
return inboxGetUrl();
|
|
232
|
+
}
|
|
114
233
|
async function queryWebhookRequests(webhookUrl, content) {
|
|
115
234
|
const token = extractToken(webhookUrl);
|
|
116
|
-
|
|
117
|
-
|
|
235
|
+
const provider = detectProvider(webhookUrl);
|
|
236
|
+
if (provider === "webhook-site") {
|
|
237
|
+
return webhookSiteQuery(token, content);
|
|
118
238
|
}
|
|
119
|
-
return
|
|
239
|
+
return inboxQuery(token, content);
|
|
120
240
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/playwright-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"rimraf": "^6.0.1",
|
|
45
45
|
"ts-morph": "^23.0.0",
|
|
46
46
|
"@empiricalrun/cua": "^0.3.0",
|
|
47
|
-
"@empiricalrun/dashboard-client": "^0.
|
|
47
|
+
"@empiricalrun/dashboard-client": "^0.3.0",
|
|
48
48
|
"@empiricalrun/llm": "^0.26.0",
|
|
49
49
|
"@empiricalrun/r2-uploader": "^0.9.1",
|
|
50
50
|
"@empiricalrun/reporter": "^0.28.1"
|