@canonry/canonry 7.45.2 → 7.46.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/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -0
- package/assets/assets/{AuditHistoryPanel-BJDLPf4D.js → AuditHistoryPanel-u65pF2eT.js} +1 -1
- package/assets/assets/{BacklinksPage-BDbYMh4s.js → BacklinksPage-CmYpSNGB.js} +1 -1
- package/assets/assets/{HistoryPage-B9c7cjV4.js → HistoryPage-CqzQSAil.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-B2wDvzLz.js → MeasurementPropertyPage-KfpmUisb.js} +1 -1
- package/assets/assets/{ProjectPage-Cgj5ZmRt.js → ProjectPage-BCiospUp.js} +1 -1
- package/assets/assets/{RunsPage-DNJpOT2C.js → RunsPage-CzPvTnwG.js} +1 -1
- package/assets/assets/{SettingsPage-BJABQP0W.js → SettingsPage-CZYckyPO.js} +1 -1
- package/assets/assets/SiteHealthSection-Bm5nWAoZ.js +4 -0
- package/assets/assets/{TrafficPage-DGks1U43.js → TrafficPage-DZw_GvaO.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-B9EC-koW.js → TrafficSourceDetailPage-CD20PJ4A.js} +1 -1
- package/assets/assets/{extract-error-message-BnQj6MRg.js → extract-error-message-Cpzj_AMZ.js} +1 -1
- package/assets/assets/{index-CQ2txiby.js → index-BHLYJHa-.js} +18 -18
- package/assets/assets/{index-aBS1f2Bj.css → index-Bll2LqWk.css} +1 -1
- package/assets/assets/{react-sigma_core.esm.min-DMBVhOEe.js → react-sigma_core.esm.min-CK2U3T7S.js} +1 -1
- package/assets/assets/{v2-overview-adapter-DUYXYO7p.js → v2-overview-adapter-DL1IsAM2.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-MBOOXUPV.js → chunk-7R2HFAG4.js} +20 -13
- package/dist/{chunk-55QNMM7V.js → chunk-GWRIZSIL.js} +3 -3
- package/dist/{chunk-ZJUP7CHK.js → chunk-IBRZ23VQ.js} +233 -109
- package/dist/{chunk-KVUBP25C.js → chunk-OO64BWNB.js} +11 -7
- package/dist/{chunk-E7BG7QGP.js → chunk-PCZXWOYX.js} +8 -3
- package/dist/{chunk-KZS5N3JH.js → chunk-QBZPKXFW.js} +1 -1
- package/dist/{chunk-T2MBKWUP.js → chunk-QWLKYIQ4.js} +2 -2
- package/dist/{chunk-6333IHPS.js → chunk-V4TITINC.js} +12 -4
- package/dist/{chunk-5A57TIOK.js → chunk-WMG365JI.js} +2 -2
- package/dist/{chunk-KMMOYVAO.js → chunk-Y2QMWZT7.js} +3 -3
- package/dist/{cli-catalog-E45QWSSR.js → cli-catalog-TFCCKIE4.js} +3 -3
- package/dist/cli-commands-JEN27DWN.js +15 -0
- package/dist/cli.js +10 -10
- package/dist/{demo-server-M45RUD4A.js → demo-server-5TX4MJIS.js} +4 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-FWOKBYOM.js → intelligence-service-5BYJSFLW.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +12 -12
- package/assets/assets/SiteHealthSection-Co-YnYL1.js +0 -4
- package/dist/cli-commands-E2TXR3OQ.js +0 -15
|
@@ -888,6 +888,7 @@ import {
|
|
|
888
888
|
unsupportedKind,
|
|
889
889
|
upgradeCaveatFor,
|
|
890
890
|
usableBrandAliases,
|
|
891
|
+
usageSurfaceSchema,
|
|
891
892
|
userDtoSchema,
|
|
892
893
|
userListDtoSchema,
|
|
893
894
|
validationError,
|
|
@@ -913,7 +914,7 @@ import {
|
|
|
913
914
|
wordpressSchemaDeployResultDtoSchema,
|
|
914
915
|
wordpressSchemaStatusResultDtoSchema,
|
|
915
916
|
wordpressStatusDtoSchema
|
|
916
|
-
} from "./chunk-
|
|
917
|
+
} from "./chunk-V4TITINC.js";
|
|
917
918
|
|
|
918
919
|
// src/intelligence-service.ts
|
|
919
920
|
import { eq as eq99, desc as desc45, asc as asc20, and as and82, ne as ne17, or as or24, inArray as inArray40, gte as gte26, lte as lte23, isNull as isNull19, sql as sql37, exists as exists2 } from "drizzle-orm";
|
|
@@ -46517,6 +46518,7 @@ var routeCatalog = [
|
|
|
46517
46518
|
// TODO: Add `GaConnectResponse` Zod schema in contracts.
|
|
46518
46519
|
200: rawJsonResponse("GA4 connection established.", looseObjectSchema),
|
|
46519
46520
|
400: errorResponse("Invalid GA4 connection request."),
|
|
46521
|
+
403: errorResponse("Google refused the property (`details.reason` is `ga4-permission-missing`). Retrying will not help until access is granted; repeated refusals are answered without asking Google again until `details.nextCheckAt`."),
|
|
46520
46522
|
404: errorResponse("Project not found.")
|
|
46521
46523
|
}
|
|
46522
46524
|
},
|
|
@@ -48253,7 +48255,7 @@ var routeCatalog = [
|
|
|
48253
48255
|
method: "post",
|
|
48254
48256
|
path: "/api/v1/projects/{name}/technical-aeo/runs",
|
|
48255
48257
|
summary: "Trigger a Technical AEO site-audit run",
|
|
48256
|
-
description: "Queues a `site-audit` run. The run discovers pages from the root, recursive sitemaps, and internal links. An active run is reused only when its persisted effective sitemap, budgets, depth,
|
|
48258
|
+
description: "Queues a `site-audit` run. The run discovers pages from the root, recursive sitemaps, and internal links. An active run is reused only when its persisted effective sitemap, budgets, depth, dead-link option, and optional factor selections match exactly; a semantic mismatch returns 409.",
|
|
48257
48259
|
tags: ["technical-aeo"],
|
|
48258
48260
|
parameters: [nameParameter],
|
|
48259
48261
|
requestBody: {
|
|
@@ -48268,7 +48270,9 @@ var routeCatalog = [
|
|
|
48268
48270
|
maxPages: { ...integerSchema, minimum: 1, maximum: 5e4, description: "Crawl page budget. When omitted, the project's saved budget (siteAuditMaxPages) applies, else the full site; max 50000." },
|
|
48269
48271
|
maxEdges: { ...integerSchema, minimum: 1, maximum: 1e6, description: "Internal-link observation budget. When omitted the crawl engine derives it from the page count; max 1000000." },
|
|
48270
48272
|
maxDepth: { ...integerSchema, minimum: 0, maximum: 100, description: "Maximum crawl depth from the root. Max 100." },
|
|
48271
|
-
checkDeadLinks: { ...booleanSchema, default: false, description: "Enable dead-link checking. Defaults to false." }
|
|
48273
|
+
checkDeadLinks: { ...booleanSchema, default: false, description: "Enable dead-link checking. Defaults to false." },
|
|
48274
|
+
includeGeo: { ...booleanSchema, default: false, description: "Opt in to geographic signals for location and service-area businesses. Part of scan identity." },
|
|
48275
|
+
includeAgentSkills: { ...booleanSchema, default: true, description: "Check discoverable actions, forms, and agent integrations by default. Set false to disable. Part of scan identity." }
|
|
48272
48276
|
}
|
|
48273
48277
|
}
|
|
48274
48278
|
}
|
|
@@ -60098,11 +60102,76 @@ async function adsRoutes(app, opts) {
|
|
|
60098
60102
|
}
|
|
60099
60103
|
|
|
60100
60104
|
// ../api-routes/src/bing.ts
|
|
60101
|
-
import
|
|
60105
|
+
import crypto43 from "crypto";
|
|
60102
60106
|
import { eq as eq66, and as and54, desc as desc32 } from "drizzle-orm";
|
|
60103
60107
|
|
|
60104
|
-
// ../
|
|
60108
|
+
// ../api-routes/src/connection-verify-cache.ts
|
|
60105
60109
|
import crypto41 from "crypto";
|
|
60110
|
+
var CONNECTION_VERIFY_TTL_MS = 15 * 60 * 1e3;
|
|
60111
|
+
var REFUSALS_BEFORE_BACKOFF = 3;
|
|
60112
|
+
var FIRST_BACKOFF_MS = 5 * 60 * 1e3;
|
|
60113
|
+
var MAX_BACKOFF_MS = 60 * 60 * 1e3;
|
|
60114
|
+
var MAX_ENTRIES = 500;
|
|
60115
|
+
function remember(entries, key, value) {
|
|
60116
|
+
entries.delete(key);
|
|
60117
|
+
entries.set(key, value);
|
|
60118
|
+
if (entries.size > MAX_ENTRIES) entries.delete(entries.keys().next().value);
|
|
60119
|
+
}
|
|
60120
|
+
function isInteractiveCaller() {
|
|
60121
|
+
const attribution = currentOutcomeAttribution();
|
|
60122
|
+
const label = usageSurfaceSchema.safeParse(attribution?.surfaceLabel);
|
|
60123
|
+
if (label.success) return label.data === "cli" || label.data === "dashboard";
|
|
60124
|
+
const ua = attribution?.userAgent ?? "";
|
|
60125
|
+
return ua.startsWith("canonry-cli") || ua.startsWith("Mozilla/");
|
|
60126
|
+
}
|
|
60127
|
+
function verifyCacheKey(...parts) {
|
|
60128
|
+
return crypto41.createHash("sha256").update(parts.join("\0")).digest("hex");
|
|
60129
|
+
}
|
|
60130
|
+
function createVerifyCache(ttlMs = CONNECTION_VERIFY_TTL_MS) {
|
|
60131
|
+
const entries = /* @__PURE__ */ new Map();
|
|
60132
|
+
return {
|
|
60133
|
+
get(key) {
|
|
60134
|
+
const entry = entries.get(key);
|
|
60135
|
+
if (!entry) return void 0;
|
|
60136
|
+
if (Date.now() - entry.at < ttlMs) return entry.value;
|
|
60137
|
+
entries.delete(key);
|
|
60138
|
+
return void 0;
|
|
60139
|
+
},
|
|
60140
|
+
set(key, value) {
|
|
60141
|
+
remember(entries, key, { value, at: Date.now() });
|
|
60142
|
+
}
|
|
60143
|
+
};
|
|
60144
|
+
}
|
|
60145
|
+
function createRefusalBackoff() {
|
|
60146
|
+
const entries = /* @__PURE__ */ new Map();
|
|
60147
|
+
return {
|
|
60148
|
+
/** The refusal to answer with instead of asking the upstream, while this key is backing off. */
|
|
60149
|
+
replay(key) {
|
|
60150
|
+
const entry = entries.get(key);
|
|
60151
|
+
if (!entry || entry.count < REFUSALS_BEFORE_BACKOFF) return void 0;
|
|
60152
|
+
const wait = Math.min(FIRST_BACKOFF_MS * 2 ** (entry.count - REFUSALS_BEFORE_BACKOFF), MAX_BACKOFF_MS);
|
|
60153
|
+
const nextCheckAt = entry.at + wait;
|
|
60154
|
+
if (Date.now() >= nextCheckAt) return void 0;
|
|
60155
|
+
const { error } = entry;
|
|
60156
|
+
return {
|
|
60157
|
+
error: new AppError(error.code, error.message, error.statusCode, {
|
|
60158
|
+
...error.details,
|
|
60159
|
+
nextCheckAt: new Date(nextCheckAt).toISOString()
|
|
60160
|
+
}),
|
|
60161
|
+
cause: entry.cause
|
|
60162
|
+
};
|
|
60163
|
+
},
|
|
60164
|
+
refused(key, error, cause) {
|
|
60165
|
+
remember(entries, key, { count: (entries.get(key)?.count ?? 0) + 1, at: Date.now(), error, cause });
|
|
60166
|
+
},
|
|
60167
|
+
cleared(key) {
|
|
60168
|
+
entries.delete(key);
|
|
60169
|
+
}
|
|
60170
|
+
};
|
|
60171
|
+
}
|
|
60172
|
+
|
|
60173
|
+
// ../integration-bing/src/bing-client.ts
|
|
60174
|
+
import crypto42 from "crypto";
|
|
60106
60175
|
|
|
60107
60176
|
// ../integration-bing/src/constants.ts
|
|
60108
60177
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -60257,7 +60326,7 @@ async function bingFetchOnce(apiKey, endpoint, opts) {
|
|
|
60257
60326
|
var throttleCooldownUntil = /* @__PURE__ */ new Map();
|
|
60258
60327
|
var cooldownNow = () => Date.now();
|
|
60259
60328
|
function cooldownKey(apiKey) {
|
|
60260
|
-
return
|
|
60329
|
+
return crypto42.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
|
|
60261
60330
|
}
|
|
60262
60331
|
function throttleCooldownRemainingMs(apiKey) {
|
|
60263
60332
|
const until = throttleCooldownUntil.get(cooldownKey(apiKey));
|
|
@@ -60401,6 +60470,7 @@ async function loadBlockingCrawlIssues(apiKey, siteUrl, domain) {
|
|
|
60401
60470
|
return blockedUrls;
|
|
60402
60471
|
}
|
|
60403
60472
|
async function bingRoutes(app, opts) {
|
|
60473
|
+
const keyChecks = createVerifyCache();
|
|
60404
60474
|
function requireConnectionStore() {
|
|
60405
60475
|
if (opts.bingConnectionStore) return opts.bingConnectionStore;
|
|
60406
60476
|
throw validationError("Bing connection storage is not configured for this deployment");
|
|
@@ -60419,16 +60489,21 @@ async function bingRoutes(app, opts) {
|
|
|
60419
60489
|
throw validationError("apiKey is required");
|
|
60420
60490
|
}
|
|
60421
60491
|
const project = resolveProject(app.db, request.params.name);
|
|
60422
|
-
|
|
60423
|
-
|
|
60424
|
-
|
|
60425
|
-
|
|
60426
|
-
|
|
60427
|
-
|
|
60428
|
-
|
|
60429
|
-
|
|
60430
|
-
|
|
60492
|
+
const keyCheck = verifyCacheKey("bing-key", apiKey);
|
|
60493
|
+
let sites = isInteractiveCaller() ? void 0 : keyChecks.get(keyCheck);
|
|
60494
|
+
if (!sites) {
|
|
60495
|
+
try {
|
|
60496
|
+
sites = await getSites(apiKey);
|
|
60497
|
+
keyChecks.set(keyCheck, sites);
|
|
60498
|
+
bingLog("info", "connect.verify-key", { domain: project.canonicalDomain, siteCount: sites.length });
|
|
60499
|
+
} catch (e) {
|
|
60500
|
+
const msg = describeError(e);
|
|
60501
|
+
bingLog("error", "connect.verify-key-failed", { domain: project.canonicalDomain, error: msg });
|
|
60502
|
+
attempt.failed(e);
|
|
60503
|
+
throw validationError(`Failed to verify Bing API key: ${msg}`);
|
|
60504
|
+
}
|
|
60431
60505
|
}
|
|
60506
|
+
const availableSites = sites.map((s) => ({ url: s.Url, verified: s.IsVerified ?? false }));
|
|
60432
60507
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60433
60508
|
const existing = store.getConnection(project.canonicalDomain);
|
|
60434
60509
|
if (existing) attempt.update({ action: "reauth" });
|
|
@@ -60438,6 +60513,9 @@ async function bingRoutes(app, opts) {
|
|
|
60438
60513
|
`This domain already has a Bing connection owned by another project. Disconnect it from that project first before re-connecting from "${project.name}".`
|
|
60439
60514
|
);
|
|
60440
60515
|
}
|
|
60516
|
+
if (existing?.apiKey === apiKey && existing.createdByProjectId) {
|
|
60517
|
+
return { connected: true, domain: project.canonicalDomain, siteUrl: existing.siteUrl ?? null, availableSites };
|
|
60518
|
+
}
|
|
60441
60519
|
store.upsertConnection({
|
|
60442
60520
|
domain: project.canonicalDomain,
|
|
60443
60521
|
apiKey,
|
|
@@ -60457,7 +60535,7 @@ async function bingRoutes(app, opts) {
|
|
|
60457
60535
|
connected: true,
|
|
60458
60536
|
domain: project.canonicalDomain,
|
|
60459
60537
|
siteUrl: existing?.siteUrl ?? null,
|
|
60460
|
-
availableSites
|
|
60538
|
+
availableSites
|
|
60461
60539
|
};
|
|
60462
60540
|
}));
|
|
60463
60541
|
app.delete("/projects/:name/bing/disconnect", connectionRoute(app, () => ({ integration: "bing", action: "disconnect" }), async (request, reply, attempt) => {
|
|
@@ -60587,7 +60665,7 @@ async function bingRoutes(app, opts) {
|
|
|
60587
60665
|
const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
60588
60666
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60589
60667
|
app.db.insert(bingCoverageSnapshots).values({
|
|
60590
|
-
id:
|
|
60668
|
+
id: crypto43.randomUUID(),
|
|
60591
60669
|
projectId: project.id,
|
|
60592
60670
|
syncRunId: snapshotRunId,
|
|
60593
60671
|
date: snapshotDate,
|
|
@@ -60662,7 +60740,7 @@ async function bingRoutes(app, opts) {
|
|
|
60662
60740
|
throw validationError("url is required");
|
|
60663
60741
|
}
|
|
60664
60742
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
60665
|
-
const runId =
|
|
60743
|
+
const runId = crypto43.randomUUID();
|
|
60666
60744
|
app.db.insert(runs).values({
|
|
60667
60745
|
id: runId,
|
|
60668
60746
|
projectId: project.id,
|
|
@@ -60683,7 +60761,7 @@ async function bingRoutes(app, opts) {
|
|
|
60683
60761
|
discoveryDate: result.DiscoveryDate ?? null
|
|
60684
60762
|
});
|
|
60685
60763
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60686
|
-
const id =
|
|
60764
|
+
const id = crypto43.randomUUID();
|
|
60687
60765
|
const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
|
|
60688
60766
|
const lastCrawledDate = parseBingDate(result.LastCrawledDate);
|
|
60689
60767
|
const inIndexDate = parseBingDate(result.InIndexDate);
|
|
@@ -60760,7 +60838,7 @@ async function bingRoutes(app, opts) {
|
|
|
60760
60838
|
throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
|
|
60761
60839
|
}
|
|
60762
60840
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60763
|
-
const runId =
|
|
60841
|
+
const runId = crypto43.randomUUID();
|
|
60764
60842
|
app.db.insert(runs).values({
|
|
60765
60843
|
id: runId,
|
|
60766
60844
|
projectId: project.id,
|
|
@@ -61058,7 +61136,7 @@ async function cdpRoutes(app, opts) {
|
|
|
61058
61136
|
}
|
|
61059
61137
|
|
|
61060
61138
|
// ../api-routes/src/ga.ts
|
|
61061
|
-
import
|
|
61139
|
+
import crypto45 from "crypto";
|
|
61062
61140
|
import { eq as eq69, desc as desc34, and as and57, sql as sql27 } from "drizzle-orm";
|
|
61063
61141
|
|
|
61064
61142
|
// ../api-routes/src/ga-session-history.ts
|
|
@@ -61118,14 +61196,14 @@ function findBiggestMover(current, prior) {
|
|
|
61118
61196
|
}
|
|
61119
61197
|
|
|
61120
61198
|
// ../api-routes/src/ga-search-landing-pages.ts
|
|
61121
|
-
import
|
|
61199
|
+
import crypto44 from "crypto";
|
|
61122
61200
|
import { and as and56, asc as asc13, count as count4, desc as desc33, eq as eq68 } from "drizzle-orm";
|
|
61123
61201
|
function persistSearchLandingSnapshot(db, input) {
|
|
61124
61202
|
db.transaction((tx) => {
|
|
61125
61203
|
deleteSearchLandingRows(tx, input.projectId);
|
|
61126
61204
|
for (const window of input.windows) {
|
|
61127
61205
|
tx.insert(gaSearchLandingWindows).values({
|
|
61128
|
-
id:
|
|
61206
|
+
id: crypto44.randomUUID(),
|
|
61129
61207
|
projectId: input.projectId,
|
|
61130
61208
|
propertyId: input.propertyId,
|
|
61131
61209
|
windowKey: window.window,
|
|
@@ -61147,7 +61225,7 @@ function persistSearchLandingSnapshot(db, input) {
|
|
|
61147
61225
|
}).run();
|
|
61148
61226
|
for (const row of window.rows) {
|
|
61149
61227
|
tx.insert(gaSearchLandingPages).values({
|
|
61150
|
-
id:
|
|
61228
|
+
id: crypto44.randomUUID(),
|
|
61151
61229
|
projectId: input.projectId,
|
|
61152
61230
|
windowKey: window.window,
|
|
61153
61231
|
landingPage: row.landingPage,
|
|
@@ -61379,7 +61457,7 @@ function persistAcquisitionMeasurement(db, input) {
|
|
|
61379
61457
|
)).run();
|
|
61380
61458
|
for (const row of input.report.rows) {
|
|
61381
61459
|
tx.insert(gaAcquisitionDaily).values({
|
|
61382
|
-
id:
|
|
61460
|
+
id: crypto45.randomUUID(),
|
|
61383
61461
|
projectId: input.projectId,
|
|
61384
61462
|
date: row.date,
|
|
61385
61463
|
channelGroup: row.channelGroup,
|
|
@@ -61421,7 +61499,7 @@ function persistLeadMeasurement(db, input) {
|
|
|
61421
61499
|
)).run();
|
|
61422
61500
|
for (const row of input.report.rows) {
|
|
61423
61501
|
tx.insert(gaLeadEventsDaily).values({
|
|
61424
|
-
id:
|
|
61502
|
+
id: crypto45.randomUUID(),
|
|
61425
61503
|
projectId: input.projectId,
|
|
61426
61504
|
date: row.date,
|
|
61427
61505
|
eventName: row.eventName,
|
|
@@ -61531,6 +61609,18 @@ function ga4ErrorToAppError(err, context) {
|
|
|
61531
61609
|
}
|
|
61532
61610
|
return providerError(`${context}: ${describeError(err)}`);
|
|
61533
61611
|
}
|
|
61612
|
+
function ga4ConnectError(err, propertyId, principal, context) {
|
|
61613
|
+
if (err instanceof GA4ApiError && err.status === 403) {
|
|
61614
|
+
return forbidden(
|
|
61615
|
+
`Google refused access to GA4 property ${propertyId} for ${principal}. Add ${principal} as a Viewer in Google Analytics (Admin > Property access management), then connect again. Retrying will not help until access is granted.`,
|
|
61616
|
+
{ reason: "ga4-permission-missing", propertyId, retryable: false }
|
|
61617
|
+
);
|
|
61618
|
+
}
|
|
61619
|
+
return validationError(`${context}: ${describeError(err)}`);
|
|
61620
|
+
}
|
|
61621
|
+
function isPermissionRefusal(error) {
|
|
61622
|
+
return error.details?.reason === "ga4-permission-missing";
|
|
61623
|
+
}
|
|
61534
61624
|
async function resolveGa4AccessToken(opts, projectName, canonicalDomain) {
|
|
61535
61625
|
const saConn = opts.ga4CredentialStore?.getConnection(projectName);
|
|
61536
61626
|
if (saConn?.clientEmail && saConn?.privateKey && saConn?.propertyId) {
|
|
@@ -61581,6 +61671,25 @@ function requireGa4Connection(opts, projectName, canonicalDomain) {
|
|
|
61581
61671
|
}
|
|
61582
61672
|
}
|
|
61583
61673
|
async function ga4Routes(app, opts) {
|
|
61674
|
+
const connectPasses = createVerifyCache();
|
|
61675
|
+
const connectRefusals = createRefusalBackoff();
|
|
61676
|
+
async function checkGa4Access(key, verify, toError) {
|
|
61677
|
+
if (!isInteractiveCaller()) {
|
|
61678
|
+
if (connectPasses.get(key)) return { ok: true };
|
|
61679
|
+
const replay = connectRefusals.replay(key);
|
|
61680
|
+
if (replay) return { ok: false, ...replay };
|
|
61681
|
+
}
|
|
61682
|
+
try {
|
|
61683
|
+
await verify();
|
|
61684
|
+
connectPasses.set(key, true);
|
|
61685
|
+
connectRefusals.cleared(key);
|
|
61686
|
+
return { ok: true };
|
|
61687
|
+
} catch (cause) {
|
|
61688
|
+
const error = toError(cause);
|
|
61689
|
+
if (isPermissionRefusal(error)) connectRefusals.refused(key, error, cause);
|
|
61690
|
+
return { ok: false, error, cause };
|
|
61691
|
+
}
|
|
61692
|
+
}
|
|
61584
61693
|
app.post("/projects/:name/ga/connect", connectionRoute(app, (request) => {
|
|
61585
61694
|
const body = request.body;
|
|
61586
61695
|
return { integration: "ga4", action: typeof body?.keyJson === "string" && body.keyJson ? "connect" : "select" };
|
|
@@ -61605,18 +61714,23 @@ async function ga4Routes(app, opts) {
|
|
|
61605
61714
|
}
|
|
61606
61715
|
const clientEmail = parsed.client_email;
|
|
61607
61716
|
const privateKey = parsed.private_key;
|
|
61608
|
-
|
|
61609
|
-
|
|
61610
|
-
|
|
61611
|
-
|
|
61612
|
-
|
|
61613
|
-
|
|
61614
|
-
|
|
61615
|
-
|
|
61717
|
+
const check3 = await checkGa4Access(
|
|
61718
|
+
verifyCacheKey("ga4-service-account", project.id, propertyId, clientEmail, privateKey),
|
|
61719
|
+
() => verifyConnection(clientEmail, privateKey, propertyId),
|
|
61720
|
+
(err) => ga4ConnectError(err, propertyId, clientEmail, "Failed to verify GA4 credentials")
|
|
61721
|
+
);
|
|
61722
|
+
if (!check3.ok) {
|
|
61723
|
+
gaLog("error", "connect.verify-failed", { projectId: project.id, propertyId, error: check3.error.message });
|
|
61724
|
+
attempt.failed(check3.cause);
|
|
61725
|
+
throw check3.error;
|
|
61616
61726
|
}
|
|
61727
|
+
gaLog("info", "connect.verified.service-account", { projectId: project.id, propertyId });
|
|
61617
61728
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
61618
61729
|
const existing = opts.ga4CredentialStore.getConnection(project.name);
|
|
61619
61730
|
if (existing) attempt.update({ action: "reauth" });
|
|
61731
|
+
if (existing?.clientEmail === clientEmail && existing.privateKey === privateKey && existing.propertyId === propertyId) {
|
|
61732
|
+
return { connected: true, propertyId, authMethod: "service-account", clientEmail };
|
|
61733
|
+
}
|
|
61620
61734
|
const previousPropertyId2 = resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain);
|
|
61621
61735
|
opts.ga4CredentialStore.upsertConnection({
|
|
61622
61736
|
projectName: project.name,
|
|
@@ -61653,19 +61767,27 @@ async function ga4Routes(app, opts) {
|
|
|
61653
61767
|
'No GA4 OAuth token found. Run "canonry google connect <project> --type ga4" first, or pass --key-file to use a service account.'
|
|
61654
61768
|
);
|
|
61655
61769
|
}
|
|
61656
|
-
const accessToken
|
|
61657
|
-
|
|
61658
|
-
|
|
61659
|
-
|
|
61660
|
-
|
|
61661
|
-
|
|
61662
|
-
|
|
61663
|
-
|
|
61664
|
-
|
|
61665
|
-
|
|
61666
|
-
|
|
61667
|
-
|
|
61668
|
-
|
|
61770
|
+
const { accessToken: storedAccessToken, refreshToken } = oauthConn;
|
|
61771
|
+
const check2 = await checkGa4Access(
|
|
61772
|
+
verifyCacheKey("ga4-oauth", project.id, propertyId, refreshToken),
|
|
61773
|
+
async () => {
|
|
61774
|
+
const accessToken = await refreshOAuthTokenIfNeeded(googleStore, authConfig, project.canonicalDomain, {
|
|
61775
|
+
accessToken: storedAccessToken,
|
|
61776
|
+
refreshToken,
|
|
61777
|
+
tokenExpiresAt: oauthConn.tokenExpiresAt
|
|
61778
|
+
});
|
|
61779
|
+
return verifyConnectionWithToken(accessToken, propertyId);
|
|
61780
|
+
},
|
|
61781
|
+
(err) => err instanceof AppError ? err : ga4ConnectError(err, propertyId, "the connected Google account", "Failed to verify GA4 access")
|
|
61782
|
+
);
|
|
61783
|
+
if (!check2.ok) {
|
|
61784
|
+
gaLog("error", "connect.verify-failed.oauth", { projectId: project.id, propertyId, error: check2.error.message });
|
|
61785
|
+
attempt.failed(check2.cause);
|
|
61786
|
+
throw check2.error;
|
|
61787
|
+
}
|
|
61788
|
+
gaLog("info", "connect.verified.oauth", { projectId: project.id, propertyId });
|
|
61789
|
+
if (oauthConn.propertyId === propertyId) {
|
|
61790
|
+
return { connected: true, propertyId, authMethod: "oauth" };
|
|
61669
61791
|
}
|
|
61670
61792
|
const previousPropertyId = resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain);
|
|
61671
61793
|
const connectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -61775,7 +61897,7 @@ async function ga4Routes(app, opts) {
|
|
|
61775
61897
|
const leadDays = measurementState?.leadSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
|
|
61776
61898
|
const leadEventNames = project.measurement.leadEventNames;
|
|
61777
61899
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
61778
|
-
const runId =
|
|
61900
|
+
const runId = crypto45.randomUUID();
|
|
61779
61901
|
app.db.insert(runs).values({
|
|
61780
61902
|
id: runId,
|
|
61781
61903
|
projectId: project.id,
|
|
@@ -61835,7 +61957,7 @@ async function ga4Routes(app, opts) {
|
|
|
61835
61957
|
).run();
|
|
61836
61958
|
for (const row of rows) {
|
|
61837
61959
|
tx.insert(gaTrafficSnapshots).values({
|
|
61838
|
-
id:
|
|
61960
|
+
id: crypto45.randomUUID(),
|
|
61839
61961
|
projectId: project.id,
|
|
61840
61962
|
date: row.date,
|
|
61841
61963
|
landingPage: row.landingPage,
|
|
@@ -61858,7 +61980,7 @@ async function ga4Routes(app, opts) {
|
|
|
61858
61980
|
).run();
|
|
61859
61981
|
for (const row of dailyTotals) {
|
|
61860
61982
|
tx.insert(gaDailyTotals).values({
|
|
61861
|
-
id:
|
|
61983
|
+
id: crypto45.randomUUID(),
|
|
61862
61984
|
projectId: project.id,
|
|
61863
61985
|
date: row.date,
|
|
61864
61986
|
sessions: row.sessions,
|
|
@@ -61884,7 +62006,7 @@ async function ga4Routes(app, opts) {
|
|
|
61884
62006
|
).run();
|
|
61885
62007
|
for (const row of aiReferrals) {
|
|
61886
62008
|
tx.insert(gaAiReferrals).values({
|
|
61887
|
-
id:
|
|
62009
|
+
id: crypto45.randomUUID(),
|
|
61888
62010
|
projectId: project.id,
|
|
61889
62011
|
date: row.date,
|
|
61890
62012
|
source: row.source,
|
|
@@ -61916,7 +62038,7 @@ async function ga4Routes(app, opts) {
|
|
|
61916
62038
|
).run();
|
|
61917
62039
|
for (const row of socialReferrals) {
|
|
61918
62040
|
tx.insert(gaSocialReferrals).values({
|
|
61919
|
-
id:
|
|
62041
|
+
id: crypto45.randomUUID(),
|
|
61920
62042
|
projectId: project.id,
|
|
61921
62043
|
date: row.date,
|
|
61922
62044
|
source: row.source,
|
|
@@ -61932,7 +62054,7 @@ async function ga4Routes(app, opts) {
|
|
|
61932
62054
|
if (syncSummary) {
|
|
61933
62055
|
tx.delete(gaTrafficSummaries).where(eq69(gaTrafficSummaries.projectId, project.id)).run();
|
|
61934
62056
|
tx.insert(gaTrafficSummaries).values({
|
|
61935
|
-
id:
|
|
62057
|
+
id: crypto45.randomUUID(),
|
|
61936
62058
|
projectId: project.id,
|
|
61937
62059
|
periodStart: summary3.periodStart,
|
|
61938
62060
|
periodEnd: summary3.periodEnd,
|
|
@@ -61945,7 +62067,7 @@ async function ga4Routes(app, opts) {
|
|
|
61945
62067
|
tx.delete(gaTrafficWindowSummaries).where(eq69(gaTrafficWindowSummaries.projectId, project.id)).run();
|
|
61946
62068
|
for (const ws of windowSummaries) {
|
|
61947
62069
|
tx.insert(gaTrafficWindowSummaries).values({
|
|
61948
|
-
id:
|
|
62070
|
+
id: crypto45.randomUUID(),
|
|
61949
62071
|
projectId: project.id,
|
|
61950
62072
|
windowKey: ws.windowKey,
|
|
61951
62073
|
periodStart: ws.periodStart,
|
|
@@ -62742,7 +62864,7 @@ function parseSchemaPageEntry(entry) {
|
|
|
62742
62864
|
}
|
|
62743
62865
|
|
|
62744
62866
|
// ../integration-wordpress/src/wordpress-client.ts
|
|
62745
|
-
import
|
|
62867
|
+
import crypto46 from "crypto";
|
|
62746
62868
|
function validateUsername(username) {
|
|
62747
62869
|
if (!username || typeof username !== "string" || username.trim().length === 0) {
|
|
62748
62870
|
throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
|
|
@@ -62962,7 +63084,7 @@ function buildSnippet(content) {
|
|
|
62962
63084
|
return `${text2.slice(0, 157)}...`;
|
|
62963
63085
|
}
|
|
62964
63086
|
function contentHash(content) {
|
|
62965
|
-
return
|
|
63087
|
+
return crypto46.createHash("sha256").update(content).digest("hex");
|
|
62966
63088
|
}
|
|
62967
63089
|
function buildAmbiguousSlugMessage(slug, pages) {
|
|
62968
63090
|
const candidates = pages.map((page3) => {
|
|
@@ -64455,7 +64577,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
64455
64577
|
}
|
|
64456
64578
|
|
|
64457
64579
|
// ../api-routes/src/backlinks.ts
|
|
64458
|
-
import
|
|
64580
|
+
import crypto47 from "crypto";
|
|
64459
64581
|
import { and as and61, asc as asc15, desc as desc36, eq as eq72, sql as sql29 } from "drizzle-orm";
|
|
64460
64582
|
|
|
64461
64583
|
// ../integration-commoncrawl/src/constants.ts
|
|
@@ -65108,7 +65230,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
65108
65230
|
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq72(ccReleaseSyncs.id, existing.id)).get();
|
|
65109
65231
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
65110
65232
|
}
|
|
65111
|
-
const id =
|
|
65233
|
+
const id = crypto47.randomUUID();
|
|
65112
65234
|
app.db.insert(ccReleaseSyncs).values({
|
|
65113
65235
|
id,
|
|
65114
65236
|
release,
|
|
@@ -65165,7 +65287,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
65165
65287
|
throw validationError("Invalid release id");
|
|
65166
65288
|
}
|
|
65167
65289
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
65168
|
-
const runId =
|
|
65290
|
+
const runId = crypto47.randomUUID();
|
|
65169
65291
|
app.db.insert(runs).values({
|
|
65170
65292
|
id: runId,
|
|
65171
65293
|
projectId: project.id,
|
|
@@ -65254,7 +65376,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
65254
65376
|
}
|
|
65255
65377
|
|
|
65256
65378
|
// ../api-routes/src/traffic.ts
|
|
65257
|
-
import
|
|
65379
|
+
import crypto50 from "crypto";
|
|
65258
65380
|
import { isIP } from "net";
|
|
65259
65381
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
65260
65382
|
import { and as and65, desc as desc37, eq as eq76, gte as gte18, inArray as inArray31, lte as lte20, sql as sql32 } from "drizzle-orm";
|
|
@@ -65282,7 +65404,7 @@ function resolveVercelSyncDeadlineMs(env = process.env) {
|
|
|
65282
65404
|
}
|
|
65283
65405
|
|
|
65284
65406
|
// ../integration-cloud-run/src/auth.ts
|
|
65285
|
-
import
|
|
65407
|
+
import crypto48 from "crypto";
|
|
65286
65408
|
var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
|
|
65287
65409
|
var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
|
|
65288
65410
|
var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -65313,7 +65435,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
|
|
|
65313
65435
|
const headerB64 = encode(header2);
|
|
65314
65436
|
const payloadB64 = encode(payload);
|
|
65315
65437
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
65316
|
-
const sign =
|
|
65438
|
+
const sign = crypto48.createSign("RSA-SHA256");
|
|
65317
65439
|
sign.update(signingInput);
|
|
65318
65440
|
const signature = sign.sign(privateKey, "base64url");
|
|
65319
65441
|
return `${signingInput}.${signature}`;
|
|
@@ -81922,7 +82044,7 @@ async function ackCloudflareQueueMessages(client, options) {
|
|
|
81922
82044
|
}
|
|
81923
82045
|
|
|
81924
82046
|
// ../api-routes/src/traffic-event-ingest.ts
|
|
81925
|
-
import
|
|
82047
|
+
import crypto49 from "crypto";
|
|
81926
82048
|
import { and as and63, eq as eq74, gte as gte17, lt as lt12, lte as lte18, sql as sql31 } from "drizzle-orm";
|
|
81927
82049
|
|
|
81928
82050
|
// ../api-routes/src/raw-event-sample-retention.ts
|
|
@@ -82221,7 +82343,7 @@ function writeTrafficEventBatch(opts) {
|
|
|
82221
82343
|
}
|
|
82222
82344
|
})();
|
|
82223
82345
|
tx.insert(rawEventSamples).values({
|
|
82224
|
-
id:
|
|
82346
|
+
id: crypto49.randomUUID(),
|
|
82225
82347
|
projectId: opts.projectId,
|
|
82226
82348
|
sourceId: opts.sourceId,
|
|
82227
82349
|
ts: sampleTimestamp,
|
|
@@ -82322,7 +82444,7 @@ var WORDPRESS_SYNC_LEASE_TTL_MS = 5 * 6e4;
|
|
|
82322
82444
|
var DEFAULT_CLOUDFLARE_INGEST_RATE_LIMIT_MAX = 6e3;
|
|
82323
82445
|
var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
|
|
82324
82446
|
function sha256Hex3(value) {
|
|
82325
|
-
return
|
|
82447
|
+
return crypto50.createHash("sha256").update(value).digest("hex");
|
|
82326
82448
|
}
|
|
82327
82449
|
function hashCloudflareBearerToken(value) {
|
|
82328
82450
|
return sha256Hex3(value);
|
|
@@ -82332,7 +82454,7 @@ function timingSafeEqualHex(a, b) {
|
|
|
82332
82454
|
if (!SHA256_HEX_PATTERN.test(a) || !SHA256_HEX_PATTERN.test(b)) return false;
|
|
82333
82455
|
const left = Buffer.from(a, "hex");
|
|
82334
82456
|
const right = Buffer.from(b, "hex");
|
|
82335
|
-
return left.length === right.length &&
|
|
82457
|
+
return left.length === right.length && crypto50.timingSafeEqual(left, right);
|
|
82336
82458
|
}
|
|
82337
82459
|
function parseDirectPushCloudflareSourceConfig(config) {
|
|
82338
82460
|
const parsed = cloudflareTrafficSourceConfigSchema.safeParse(config);
|
|
@@ -82650,7 +82772,7 @@ function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing =
|
|
|
82650
82772
|
}
|
|
82651
82773
|
if (!createIfMissing) return { changed: false, created: false };
|
|
82652
82774
|
tx.insert(schedules).values({
|
|
82653
|
-
id:
|
|
82775
|
+
id: crypto50.randomUUID(),
|
|
82654
82776
|
projectId,
|
|
82655
82777
|
kind: SchedulableRunKinds["traffic-sync"],
|
|
82656
82778
|
cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
|
|
@@ -82971,7 +83093,7 @@ async function runBackfillTask(options) {
|
|
|
82971
83093
|
}
|
|
82972
83094
|
})();
|
|
82973
83095
|
tx.insert(rawEventSamples).values({
|
|
82974
|
-
id:
|
|
83096
|
+
id: crypto50.randomUUID(),
|
|
82975
83097
|
projectId: project.id,
|
|
82976
83098
|
sourceId: sourceRow2.id,
|
|
82977
83099
|
ts: sampleTimestamp,
|
|
@@ -83162,7 +83284,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83162
83284
|
const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
|
|
83163
83285
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
83164
83286
|
const activeSource = tx.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
|
|
83165
|
-
const sourceId = activeSource?.id ??
|
|
83287
|
+
const sourceId = activeSource?.id ?? crypto50.randomUUID();
|
|
83166
83288
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
83167
83289
|
if (activeSource) {
|
|
83168
83290
|
tx.update(trafficSources).set({
|
|
@@ -83254,7 +83376,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83254
83376
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
83255
83377
|
const activeSource = tx.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
83256
83378
|
const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
|
|
83257
|
-
const sourceId = endpointChanged ?
|
|
83379
|
+
const sourceId = endpointChanged ? crypto50.randomUUID() : activeSource?.id ?? crypto50.randomUUID();
|
|
83258
83380
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
83259
83381
|
if (activeSource && !endpointChanged) {
|
|
83260
83382
|
tx.update(trafficSources).set({
|
|
@@ -83366,7 +83488,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83366
83488
|
}).where(eq76(trafficSources.id, activeSource.id)).run();
|
|
83367
83489
|
row = tx.select().from(trafficSources).where(eq76(trafficSources.id, activeSource.id)).get();
|
|
83368
83490
|
} else {
|
|
83369
|
-
const newId =
|
|
83491
|
+
const newId = crypto50.randomUUID();
|
|
83370
83492
|
tx.insert(trafficSources).values({
|
|
83371
83493
|
id: newId,
|
|
83372
83494
|
projectId: project.id,
|
|
@@ -83436,7 +83558,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83436
83558
|
const cloudflareSources = app.db.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
|
|
83437
83559
|
const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
|
|
83438
83560
|
if (sameModeSource) attempt.update({ action: "reauth" });
|
|
83439
|
-
const sourceId2 = sameModeSource?.id ??
|
|
83561
|
+
const sourceId2 = sameModeSource?.id ?? crypto50.randomUUID();
|
|
83440
83562
|
const previousCredential2 = credentialStore.getConnectionBySourceId(sourceId2);
|
|
83441
83563
|
const workerVersion2 = CURRENT_CLOUDFLARE_WORKER_VERSION;
|
|
83442
83564
|
const workerScript2 = generateWorkerScript({
|
|
@@ -83572,7 +83694,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83572
83694
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83573
83695
|
const activeSource = app.db.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
|
|
83574
83696
|
if (activeSource) attempt.update({ action: "reauth" });
|
|
83575
|
-
const sourceId = activeSource?.id ??
|
|
83697
|
+
const sourceId = activeSource?.id ?? crypto50.randomUUID();
|
|
83576
83698
|
const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
|
|
83577
83699
|
if (activeSource && !parsedActiveConfig) {
|
|
83578
83700
|
throw validationError("Existing Cloudflare source is not configured for direct-push delivery");
|
|
@@ -83588,8 +83710,8 @@ async function trafficRoutes(app, opts) {
|
|
|
83588
83710
|
const previousAccountId = reusableCredential?.accountId ?? parsedActiveConfig?.accountId ?? null;
|
|
83589
83711
|
const effectiveZoneId = zoneId ?? previousZoneId;
|
|
83590
83712
|
const effectiveAccountId = accountId ?? previousAccountId;
|
|
83591
|
-
const bearerToken = reusableCredential?.bearerToken ?? `cnry_cfw_${
|
|
83592
|
-
const hmacSecret = reusableCredential?.hmacSecret ??
|
|
83713
|
+
const bearerToken = reusableCredential?.bearerToken ?? `cnry_cfw_${crypto50.randomBytes(32).toString("hex")}`;
|
|
83714
|
+
const hmacSecret = reusableCredential?.hmacSecret ?? crypto50.randomBytes(32).toString("hex");
|
|
83593
83715
|
const ingestTokenHash = sha256Hex3(bearerToken);
|
|
83594
83716
|
const ingestUrl = opts.cloudflareTrafficIngestUrl.replace("{name}", encodeURIComponent(project.name));
|
|
83595
83717
|
assertCloudflareIngestUrlIsPublicHttps(ingestUrl);
|
|
@@ -83935,7 +84057,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83935
84057
|
apiToken: credential.apiToken
|
|
83936
84058
|
};
|
|
83937
84059
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83938
|
-
const leaseOwner =
|
|
84060
|
+
const leaseOwner = crypto50.randomUUID();
|
|
83939
84061
|
if (!tryClaimTrafficSyncLease({
|
|
83940
84062
|
db: app.db,
|
|
83941
84063
|
sourceId: sourceRow2.id,
|
|
@@ -83945,7 +84067,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83945
84067
|
})) {
|
|
83946
84068
|
throw operationInProgress("Cloudflare Queue source sync is already in progress", { sourceId: sourceRow2.id });
|
|
83947
84069
|
}
|
|
83948
|
-
const runId =
|
|
84070
|
+
const runId = crypto50.randomUUID();
|
|
83949
84071
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
83950
84072
|
const startedMs = Date.now();
|
|
83951
84073
|
try {
|
|
@@ -84185,7 +84307,7 @@ async function trafficRoutes(app, opts) {
|
|
|
84185
84307
|
if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
|
|
84186
84308
|
throw validationError("Traffic source must be active before it can sync");
|
|
84187
84309
|
}
|
|
84188
|
-
const wordpressLeaseOwner = sourceRow2.sourceType === TrafficSourceTypes.wordpress ?
|
|
84310
|
+
const wordpressLeaseOwner = sourceRow2.sourceType === TrafficSourceTypes.wordpress ? crypto50.randomUUID() : void 0;
|
|
84189
84311
|
if (wordpressLeaseOwner && !tryClaimTrafficSyncLease({
|
|
84190
84312
|
db: app.db,
|
|
84191
84313
|
sourceId: sourceRow2.id,
|
|
@@ -84200,7 +84322,7 @@ async function trafficRoutes(app, opts) {
|
|
|
84200
84322
|
let windowEnd = syncWindowEnd;
|
|
84201
84323
|
const startedAt = syncWindowEnd.toISOString();
|
|
84202
84324
|
const syncStartedAtMs = syncWindowEnd.getTime();
|
|
84203
|
-
const runId =
|
|
84325
|
+
const runId = crypto50.randomUUID();
|
|
84204
84326
|
app.db.insert(runs).values({
|
|
84205
84327
|
id: runId,
|
|
84206
84328
|
projectId: project.id,
|
|
@@ -84749,7 +84871,7 @@ async function trafficRoutes(app, opts) {
|
|
|
84749
84871
|
}
|
|
84750
84872
|
})();
|
|
84751
84873
|
tx.insert(rawEventSamples).values({
|
|
84752
|
-
id:
|
|
84874
|
+
id: crypto50.randomUUID(),
|
|
84753
84875
|
projectId: project.id,
|
|
84754
84876
|
sourceId: sourceRow2.id,
|
|
84755
84877
|
ts: sampleTimestamp,
|
|
@@ -84981,7 +85103,7 @@ async function trafficRoutes(app, opts) {
|
|
|
84981
85103
|
};
|
|
84982
85104
|
}
|
|
84983
85105
|
const startedAt = windowEnd.toISOString();
|
|
84984
|
-
const runId =
|
|
85106
|
+
const runId = crypto50.randomUUID();
|
|
84985
85107
|
app.db.transaction((tx) => {
|
|
84986
85108
|
const latestSource = tx.select().from(trafficSources).where(eq76(trafficSources.id, sourceRow2.id)).get();
|
|
84987
85109
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
@@ -85862,7 +85984,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
|
85862
85984
|
}];
|
|
85863
85985
|
|
|
85864
85986
|
// ../api-routes/src/doctor/checks/agent.ts
|
|
85865
|
-
import
|
|
85987
|
+
import crypto51 from "crypto";
|
|
85866
85988
|
import fs6 from "fs";
|
|
85867
85989
|
import path7 from "path";
|
|
85868
85990
|
var REQUIRED_SKILLS = ["canonry", "aero"];
|
|
@@ -86096,7 +86218,7 @@ function isInstalled(dir) {
|
|
|
86096
86218
|
}
|
|
86097
86219
|
function hashInstalledFile(filePath) {
|
|
86098
86220
|
try {
|
|
86099
|
-
return
|
|
86221
|
+
return crypto51.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
|
|
86100
86222
|
} catch {
|
|
86101
86223
|
return void 0;
|
|
86102
86224
|
}
|
|
@@ -89979,7 +90101,7 @@ async function statusRoutes(app, opts) {
|
|
|
89979
90101
|
}
|
|
89980
90102
|
|
|
89981
90103
|
// ../api-routes/src/discovery/routes.ts
|
|
89982
|
-
import
|
|
90104
|
+
import crypto52 from "crypto";
|
|
89983
90105
|
import { and as and76, desc as desc42, eq as eq92, gte as gte23, inArray as inArray37, isNull as isNull17, or as or21 } from "drizzle-orm";
|
|
89984
90106
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
89985
90107
|
async function discoveryRoutes(app, opts) {
|
|
@@ -90045,8 +90167,8 @@ async function discoveryRoutes(app, opts) {
|
|
|
90045
90167
|
if (existing && existing.runId) {
|
|
90046
90168
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
90047
90169
|
}
|
|
90048
|
-
const sessionId =
|
|
90049
|
-
const runId =
|
|
90170
|
+
const sessionId = crypto52.randomUUID();
|
|
90171
|
+
const runId = crypto52.randomUUID();
|
|
90050
90172
|
tx.insert(discoverySessions).values({
|
|
90051
90173
|
id: sessionId,
|
|
90052
90174
|
projectId: project.id,
|
|
@@ -90301,7 +90423,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
90301
90423
|
const marketNames = beginMarketNameWrite(tx, project.id);
|
|
90302
90424
|
for (const query of promotedQueries) {
|
|
90303
90425
|
tx.insert(queries).values({
|
|
90304
|
-
id:
|
|
90426
|
+
id: crypto52.randomUUID(),
|
|
90305
90427
|
projectId: project.id,
|
|
90306
90428
|
query,
|
|
90307
90429
|
provenance,
|
|
@@ -90482,7 +90604,7 @@ function planCompetitorPromotion(input) {
|
|
|
90482
90604
|
}
|
|
90483
90605
|
|
|
90484
90606
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
90485
|
-
import
|
|
90607
|
+
import crypto53 from "crypto";
|
|
90486
90608
|
import { eq as eq93 } from "drizzle-orm";
|
|
90487
90609
|
var DEFAULT_MAX_PROBES = 100;
|
|
90488
90610
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
@@ -90648,7 +90770,7 @@ async function executeDiscovery(opts) {
|
|
|
90648
90770
|
probeRows.push({ citedDomains: probe.citedDomains, bucket });
|
|
90649
90771
|
buckets[bucket]++;
|
|
90650
90772
|
return {
|
|
90651
|
-
id:
|
|
90773
|
+
id: crypto53.randomUUID(),
|
|
90652
90774
|
sessionId: opts.sessionId,
|
|
90653
90775
|
projectId: opts.project.id,
|
|
90654
90776
|
query: probedCanonicals[index2],
|
|
@@ -90700,7 +90822,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
|
|
|
90700
90822
|
const domain = hostOf(entry.domain) ?? "";
|
|
90701
90823
|
if (!domain) continue;
|
|
90702
90824
|
db.insert(domainClassifications).values({
|
|
90703
|
-
id:
|
|
90825
|
+
id: crypto53.randomUUID(),
|
|
90704
90826
|
projectId,
|
|
90705
90827
|
domain,
|
|
90706
90828
|
competitorType: entry.competitorType,
|
|
@@ -90740,7 +90862,7 @@ function dedupeStrings(input) {
|
|
|
90740
90862
|
}
|
|
90741
90863
|
|
|
90742
90864
|
// ../api-routes/src/technical-aeo.ts
|
|
90743
|
-
import
|
|
90865
|
+
import crypto54 from "crypto";
|
|
90744
90866
|
import { and as and78, asc as asc18, count as count7, desc as desc43, eq as eq95, gte as gte24, inArray as inArray38, isNotNull as isNotNull9, isNull as isNull18, lt as lt13, or as or22, sql as sql35 } from "drizzle-orm";
|
|
90745
90867
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
90746
90868
|
|
|
@@ -92733,7 +92855,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92733
92855
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
92734
92856
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
92735
92857
|
const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
|
|
92736
|
-
const selectionKey =
|
|
92858
|
+
const selectionKey = crypto54.createHash("sha256").update(JSON.stringify({
|
|
92737
92859
|
projectId: project.id,
|
|
92738
92860
|
runId: snapshot.runId,
|
|
92739
92861
|
attemptId: snapshot.attemptId,
|
|
@@ -93233,7 +93355,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
93233
93355
|
);
|
|
93234
93356
|
}
|
|
93235
93357
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
93236
|
-
const runId =
|
|
93358
|
+
const runId = crypto54.randomUUID();
|
|
93237
93359
|
tx.insert(runs).values({
|
|
93238
93360
|
id: runId,
|
|
93239
93361
|
projectId: project.id,
|
|
@@ -93260,7 +93382,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
93260
93382
|
// from the page count instead of inheriting a flat ceiling here.
|
|
93261
93383
|
maxEdges: effectiveRequest.maxEdges ?? void 0,
|
|
93262
93384
|
maxDepth: effectiveRequest.maxDepth ?? void 0,
|
|
93263
|
-
checkDeadLinks: effectiveRequest.checkDeadLinks
|
|
93385
|
+
checkDeadLinks: effectiveRequest.checkDeadLinks,
|
|
93386
|
+
includeGeo: effectiveRequest.includeGeo,
|
|
93387
|
+
includeAgentSkills: effectiveRequest.includeAgentSkills
|
|
93264
93388
|
});
|
|
93265
93389
|
}
|
|
93266
93390
|
return { runId: result.runId, status: result.status };
|
|
@@ -93268,7 +93392,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
93268
93392
|
}
|
|
93269
93393
|
|
|
93270
93394
|
// ../api-routes/src/research.ts
|
|
93271
|
-
import
|
|
93395
|
+
import crypto55 from "crypto";
|
|
93272
93396
|
import { z as z5 } from "zod";
|
|
93273
93397
|
import { and as and79, count as count8, desc as desc44, eq as eq96, gte as gte25, lt as lt14, or as or23, sql as sql36 } from "drizzle-orm";
|
|
93274
93398
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
@@ -93338,9 +93462,9 @@ async function researchRoutes(app, opts) {
|
|
|
93338
93462
|
throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
93339
93463
|
}
|
|
93340
93464
|
}
|
|
93341
|
-
const id =
|
|
93465
|
+
const id = crypto55.randomUUID();
|
|
93342
93466
|
tx.insert(researchRuns).values({ id, projectId: project.id, status: ResearchRunStatuses.queued, provider: providerName, requestedModel: requestedModel2, resolvedModel, location: location ?? null, totalQueries: input.queries.length, scope, template, idempotencyKey: input.idempotencyKey ?? null, requestHash: input.idempotencyKey ? requestHash2 : null, initiatedBy, createdAt: now }).run();
|
|
93343
|
-
for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id:
|
|
93467
|
+
for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id: crypto55.randomUUID(), researchRunId: id, position, queryText: query, queryClass: queryClasses[position] ?? null, status: ResearchQueryStatuses.queued, requestedModel: requestedModel2, resolvedModel, groundingSources: [], citedDomains: [], searchQueries: [], createdAt: now }).run();
|
|
93344
93468
|
writeAuditLog(tx, { projectId: project.id, actor: "api", action: "research.created", entityType: "research_run", entityId: id });
|
|
93345
93469
|
return { reused: false, id, shouldDispatch: true };
|
|
93346
93470
|
});
|
|
@@ -93393,7 +93517,7 @@ async function researchRoutes(app, opts) {
|
|
|
93393
93517
|
}
|
|
93394
93518
|
const ids = [];
|
|
93395
93519
|
for (const [index2, run] of prepared.entries()) {
|
|
93396
|
-
const id =
|
|
93520
|
+
const id = crypto55.randomUUID();
|
|
93397
93521
|
ids.push(id);
|
|
93398
93522
|
tx.insert(researchRuns).values({
|
|
93399
93523
|
id,
|
|
@@ -93412,7 +93536,7 @@ async function researchRoutes(app, opts) {
|
|
|
93412
93536
|
createdAt: now
|
|
93413
93537
|
}).run();
|
|
93414
93538
|
for (const [position, query] of run.input.queries.entries()) tx.insert(researchRunQueries).values({
|
|
93415
|
-
id:
|
|
93539
|
+
id: crypto55.randomUUID(),
|
|
93416
93540
|
researchRunId: id,
|
|
93417
93541
|
position,
|
|
93418
93542
|
queryText: query,
|
|
@@ -93471,7 +93595,7 @@ async function researchRoutes(app, opts) {
|
|
|
93471
93595
|
var DIRECT_REQUEST_HASH_VERSION = "direct-v2:";
|
|
93472
93596
|
function directResearchRequestHash(input) {
|
|
93473
93597
|
const { idempotencyKey: _key, ...request } = input;
|
|
93474
|
-
return DIRECT_REQUEST_HASH_VERSION +
|
|
93598
|
+
return DIRECT_REQUEST_HASH_VERSION + crypto55.createHash("sha256").update(JSON.stringify(request)).digest("hex");
|
|
93475
93599
|
}
|
|
93476
93600
|
function assertSameDirectResearchRequest(existing, input, hash2) {
|
|
93477
93601
|
if (existing.requestHash?.startsWith(DIRECT_REQUEST_HASH_VERSION)) {
|
|
@@ -93489,7 +93613,7 @@ function assertSameDirectResearchRequest(existing, input, hash2) {
|
|
|
93489
93613
|
...scope ? { scope } : {},
|
|
93490
93614
|
...template ? { template } : {}
|
|
93491
93615
|
};
|
|
93492
|
-
if (
|
|
93616
|
+
if (crypto55.createHash("sha256").update(JSON.stringify(normalized)).digest("hex") !== existing.requestHash) throw alreadyExists("Research idempotency key", input.idempotencyKey);
|
|
93493
93617
|
}
|
|
93494
93618
|
var researchCursorSchema = z5.object({ projectId: z5.string().min(1), createdAt: z5.string().datetime({ offset: true }), id: z5.string().min(1).max(256) }).strict();
|
|
93495
93619
|
function parseResearchCursor(value, projectId) {
|
|
@@ -93509,11 +93633,11 @@ function resolveStoredResearchScope(stored, selection, idempotencyKey) {
|
|
|
93509
93633
|
return stored;
|
|
93510
93634
|
}
|
|
93511
93635
|
function batchChildReceiptKey(rootKey, index2) {
|
|
93512
|
-
const derived =
|
|
93636
|
+
const derived = crypto55.createHash("sha256").update(`${rootKey}\0${index2}`).digest("hex");
|
|
93513
93637
|
return `${BATCH_RECEIPT_PREFIX}${derived}:${index2}`;
|
|
93514
93638
|
}
|
|
93515
93639
|
function researchBatchRequestHash(input) {
|
|
93516
|
-
return
|
|
93640
|
+
return crypto55.createHash("sha256").update(JSON.stringify({ runs: input.runs })).digest("hex");
|
|
93517
93641
|
}
|
|
93518
93642
|
function prepareBatchRun(app, opts, project, active, input) {
|
|
93519
93643
|
const adapters = opts.providerAdapters ?? [];
|
|
@@ -93785,7 +93909,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
93785
93909
|
}
|
|
93786
93910
|
|
|
93787
93911
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
93788
|
-
import
|
|
93912
|
+
import crypto56 from "crypto";
|
|
93789
93913
|
import { and as and81, eq as eq98 } from "drizzle-orm";
|
|
93790
93914
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
93791
93915
|
return db.transaction((tx) => {
|
|
@@ -93827,11 +93951,11 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
93827
93951
|
...frozen.identity.qualifiedAliases?.length ? { qualifiedAliases: frozen.identity.qualifiedAliases } : {}
|
|
93828
93952
|
}
|
|
93829
93953
|
};
|
|
93830
|
-
const replayChecksum =
|
|
93954
|
+
const replayChecksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(replayDefinition)).digest("hex");
|
|
93831
93955
|
if (existing.checksum !== replayChecksum) {
|
|
93832
93956
|
if (frozen.competitors === void 0) {
|
|
93833
93957
|
const { competitors: _currentCompetitors, ...legacyReplayDefinition } = replayDefinition;
|
|
93834
|
-
const legacyReplayChecksum =
|
|
93958
|
+
const legacyReplayChecksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(legacyReplayDefinition)).digest("hex");
|
|
93835
93959
|
if (existing.checksum === legacyReplayChecksum) return frozen;
|
|
93836
93960
|
}
|
|
93837
93961
|
throw validationError("This run already has a captured measurement definition. Start a new run for changed inputs.");
|
|
@@ -93855,7 +93979,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
93855
93979
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
93856
93980
|
}
|
|
93857
93981
|
}
|
|
93858
|
-
const checksum =
|
|
93982
|
+
const checksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(definition2)).digest("hex");
|
|
93859
93983
|
tx.insert(simpleMeasurementDefinitions).values({
|
|
93860
93984
|
projectId: input.projectId,
|
|
93861
93985
|
runId: input.runId,
|
|
@@ -94470,7 +94594,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
94470
94594
|
}
|
|
94471
94595
|
|
|
94472
94596
|
// src/intelligence-service.ts
|
|
94473
|
-
import
|
|
94597
|
+
import crypto57 from "crypto";
|
|
94474
94598
|
|
|
94475
94599
|
// ../api-routes/src/runtime-logger.ts
|
|
94476
94600
|
import { format } from "util";
|
|
@@ -95272,7 +95396,7 @@ var IntelligenceService = class {
|
|
|
95272
95396
|
}).run();
|
|
95273
95397
|
}
|
|
95274
95398
|
tx.insert(healthSnapshots).values({
|
|
95275
|
-
id:
|
|
95399
|
+
id: crypto57.randomUUID(),
|
|
95276
95400
|
projectId,
|
|
95277
95401
|
runId,
|
|
95278
95402
|
overallCitedRate: String(result.health.overallCitedRate),
|