@ainyc/canonry 4.51.0 → 4.51.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/dist/{chunk-FDR3G6SB.js → chunk-JOKPYAEL.js} +148 -67
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +10 -10
|
@@ -12540,6 +12540,9 @@ var GA4_DEFAULT_SYNC_DAYS = 30;
|
|
|
12540
12540
|
var GA4_MAX_SYNC_DAYS = 90;
|
|
12541
12541
|
var GA4_REQUEST_TIMEOUT_MS = 3e4;
|
|
12542
12542
|
var GA4_MAX_PAGES = 50;
|
|
12543
|
+
var GA4_MAX_CONCURRENT_REQUESTS = 4;
|
|
12544
|
+
var GA4_MAX_RETRIES = 3;
|
|
12545
|
+
var GA4_INITIAL_RETRY_DELAY_MS = 1e3;
|
|
12543
12546
|
var GA4_DIMENSIONS = {
|
|
12544
12547
|
date: "date",
|
|
12545
12548
|
landingPagePlusQueryString: "landingPagePlusQueryString",
|
|
@@ -12560,10 +12563,14 @@ var GA4_METRICS = {
|
|
|
12560
12563
|
// ../integration-google-analytics/src/types.ts
|
|
12561
12564
|
var GA4ApiError = class extends Error {
|
|
12562
12565
|
status;
|
|
12563
|
-
|
|
12566
|
+
/** Seconds the GA4 API asked us to wait before retrying. Populated from the
|
|
12567
|
+
* `Retry-After` response header on 429 and 5xx responses when present. */
|
|
12568
|
+
retryAfterSeconds;
|
|
12569
|
+
constructor(message, status, retryAfterSeconds) {
|
|
12564
12570
|
super(message);
|
|
12565
12571
|
this.name = "GA4ApiError";
|
|
12566
12572
|
this.status = status;
|
|
12573
|
+
if (retryAfterSeconds !== void 0) this.retryAfterSeconds = retryAfterSeconds;
|
|
12567
12574
|
}
|
|
12568
12575
|
};
|
|
12569
12576
|
|
|
@@ -12659,81 +12666,155 @@ async function getAccessToken(clientEmail, privateKey) {
|
|
|
12659
12666
|
function escapeRegExp2(str) {
|
|
12660
12667
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12661
12668
|
}
|
|
12669
|
+
var gaInFlight = 0;
|
|
12670
|
+
var gaWaitQueue = [];
|
|
12671
|
+
async function acquireGa4Slot() {
|
|
12672
|
+
if (gaInFlight < GA4_MAX_CONCURRENT_REQUESTS) {
|
|
12673
|
+
gaInFlight++;
|
|
12674
|
+
return;
|
|
12675
|
+
}
|
|
12676
|
+
await new Promise((resolve) => gaWaitQueue.push(resolve));
|
|
12677
|
+
}
|
|
12678
|
+
function releaseGa4Slot() {
|
|
12679
|
+
const next = gaWaitQueue.shift();
|
|
12680
|
+
if (next) {
|
|
12681
|
+
next();
|
|
12682
|
+
} else {
|
|
12683
|
+
gaInFlight--;
|
|
12684
|
+
}
|
|
12685
|
+
}
|
|
12686
|
+
async function withGa4Limit(fn) {
|
|
12687
|
+
await acquireGa4Slot();
|
|
12688
|
+
try {
|
|
12689
|
+
return await fn();
|
|
12690
|
+
} finally {
|
|
12691
|
+
releaseGa4Slot();
|
|
12692
|
+
}
|
|
12693
|
+
}
|
|
12694
|
+
function parseRetryAfter(headerValue) {
|
|
12695
|
+
if (!headerValue) return void 0;
|
|
12696
|
+
const trimmed = headerValue.trim();
|
|
12697
|
+
const asNum = Number(trimmed);
|
|
12698
|
+
if (Number.isFinite(asNum) && asNum >= 0) return asNum;
|
|
12699
|
+
const asDate = Date.parse(trimmed);
|
|
12700
|
+
if (!Number.isNaN(asDate)) {
|
|
12701
|
+
const seconds = Math.max(0, (asDate - Date.now()) / 1e3);
|
|
12702
|
+
return seconds;
|
|
12703
|
+
}
|
|
12704
|
+
return void 0;
|
|
12705
|
+
}
|
|
12706
|
+
function isRetryableGa4Error(err) {
|
|
12707
|
+
if (err instanceof GA4ApiError) {
|
|
12708
|
+
return err.status === 429 || err.status >= 500;
|
|
12709
|
+
}
|
|
12710
|
+
return false;
|
|
12711
|
+
}
|
|
12712
|
+
async function withGa4Retry(fn, errLabel) {
|
|
12713
|
+
let lastError;
|
|
12714
|
+
for (let attempt = 0; attempt <= GA4_MAX_RETRIES; attempt++) {
|
|
12715
|
+
try {
|
|
12716
|
+
return await fn();
|
|
12717
|
+
} catch (err) {
|
|
12718
|
+
lastError = err;
|
|
12719
|
+
if (attempt >= GA4_MAX_RETRIES || !isRetryableGa4Error(err)) throw err;
|
|
12720
|
+
const ga4Err = err;
|
|
12721
|
+
const computedDelayMs = GA4_INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
12722
|
+
const delayMs = ga4Err.retryAfterSeconds !== void 0 ? Math.max(0, ga4Err.retryAfterSeconds * 1e3) : computedDelayMs;
|
|
12723
|
+
ga4Log("info", `${errLabel}.retry`, {
|
|
12724
|
+
attempt: attempt + 1,
|
|
12725
|
+
maxAttempts: GA4_MAX_RETRIES + 1,
|
|
12726
|
+
status: ga4Err.status,
|
|
12727
|
+
delayMs,
|
|
12728
|
+
usedRetryAfter: ga4Err.retryAfterSeconds !== void 0
|
|
12729
|
+
});
|
|
12730
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
12731
|
+
}
|
|
12732
|
+
}
|
|
12733
|
+
throw lastError;
|
|
12734
|
+
}
|
|
12662
12735
|
async function runReport(accessToken, propertyId, request) {
|
|
12663
12736
|
validatePropertyId(propertyId);
|
|
12664
12737
|
const safePropertyId = encodeURIComponent(propertyId);
|
|
12665
12738
|
const url = `${GA4_DATA_API_BASE}/properties/${safePropertyId}:runReport`;
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12739
|
+
return withGa4Limit(() => withGa4Retry(async () => {
|
|
12740
|
+
const res = await fetch(url, {
|
|
12741
|
+
method: "POST",
|
|
12742
|
+
headers: {
|
|
12743
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
12744
|
+
"Content-Type": "application/json"
|
|
12745
|
+
},
|
|
12746
|
+
body: JSON.stringify(request),
|
|
12747
|
+
signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
|
|
12748
|
+
});
|
|
12749
|
+
if (res.status === 401 || res.status === 403) {
|
|
12750
|
+
const body = await res.text().catch(() => "");
|
|
12751
|
+
let detail = "";
|
|
12752
|
+
try {
|
|
12753
|
+
const parsed = JSON.parse(body);
|
|
12754
|
+
if (parsed.error?.status === "SERVICE_DISABLED") {
|
|
12755
|
+
detail = " The Google Analytics Data API is not enabled for this GCP project. Enable it at: https://console.developers.google.com/apis/api/analyticsdata.googleapis.com/overview";
|
|
12756
|
+
} else if (parsed.error?.message) {
|
|
12757
|
+
detail = ` ${parsed.error.message}`;
|
|
12758
|
+
}
|
|
12759
|
+
} catch {
|
|
12760
|
+
if (body.length < 200) detail = ` ${body}`;
|
|
12684
12761
|
}
|
|
12685
|
-
|
|
12686
|
-
|
|
12762
|
+
ga4Log("error", "report.auth-failed", { propertyId, httpStatus: res.status });
|
|
12763
|
+
throw new GA4ApiError(
|
|
12764
|
+
`GA4 API authentication failed \u2014 check service account permissions.${detail}`,
|
|
12765
|
+
res.status
|
|
12766
|
+
);
|
|
12687
12767
|
}
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
throw new GA4ApiError(`GA4 API error (${res.status}): ${detail}`, res.status);
|
|
12703
|
-
}
|
|
12704
|
-
return await res.json();
|
|
12768
|
+
if (res.status === 429) {
|
|
12769
|
+
const retryAfterSeconds = parseRetryAfter(res.headers.get("retry-after"));
|
|
12770
|
+
ga4Log("error", "report.rate-limited", { propertyId, retryAfterSeconds });
|
|
12771
|
+
throw new GA4ApiError("GA4 API rate limit exceeded", 429, retryAfterSeconds);
|
|
12772
|
+
}
|
|
12773
|
+
if (!res.ok) {
|
|
12774
|
+
const body = await res.text();
|
|
12775
|
+
const retryAfterSeconds = res.status >= 500 ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
|
|
12776
|
+
ga4Log("error", "report.error", { propertyId, httpStatus: res.status, retryAfterSeconds });
|
|
12777
|
+
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
12778
|
+
throw new GA4ApiError(`GA4 API error (${res.status}): ${detail}`, res.status, retryAfterSeconds);
|
|
12779
|
+
}
|
|
12780
|
+
return await res.json();
|
|
12781
|
+
}, "report"));
|
|
12705
12782
|
}
|
|
12706
12783
|
async function batchRunReports(accessToken, propertyId, requests) {
|
|
12707
12784
|
const url = `${GA4_DATA_API_BASE}/properties/${propertyId}:batchRunReports`;
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
|
|
12785
|
+
return withGa4Limit(() => withGa4Retry(async () => {
|
|
12786
|
+
const res = await fetch(url, {
|
|
12787
|
+
method: "POST",
|
|
12788
|
+
headers: {
|
|
12789
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
12790
|
+
"Content-Type": "application/json"
|
|
12791
|
+
},
|
|
12792
|
+
body: JSON.stringify({ requests }),
|
|
12793
|
+
signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
|
|
12794
|
+
});
|
|
12795
|
+
if (res.status === 401 || res.status === 403) {
|
|
12796
|
+
const body = await res.text().catch(() => "");
|
|
12797
|
+
ga4Log("error", "batch-report.auth-failed", { propertyId, httpStatus: res.status });
|
|
12798
|
+
throw new GA4ApiError(
|
|
12799
|
+
`GA4 API authentication failed \u2014 check service account permissions. ${body}`,
|
|
12800
|
+
res.status
|
|
12801
|
+
);
|
|
12802
|
+
}
|
|
12803
|
+
if (res.status === 429) {
|
|
12804
|
+
const retryAfterSeconds = parseRetryAfter(res.headers.get("retry-after"));
|
|
12805
|
+
ga4Log("error", "batch-report.rate-limited", { propertyId, retryAfterSeconds });
|
|
12806
|
+
throw new GA4ApiError("GA4 API rate limit exceeded", 429, retryAfterSeconds);
|
|
12807
|
+
}
|
|
12808
|
+
if (!res.ok) {
|
|
12809
|
+
const body = await res.text();
|
|
12810
|
+
const retryAfterSeconds = res.status >= 500 ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
|
|
12811
|
+
ga4Log("error", "batch-report.error", { propertyId, httpStatus: res.status, retryAfterSeconds });
|
|
12812
|
+
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
12813
|
+
throw new GA4ApiError(`GA4 API error (${res.status}): ${detail}`, res.status, retryAfterSeconds);
|
|
12814
|
+
}
|
|
12815
|
+
const data = await res.json();
|
|
12816
|
+
return data.reports;
|
|
12817
|
+
}, "batch-report"));
|
|
12737
12818
|
}
|
|
12738
12819
|
function formatDate2(d) {
|
|
12739
12820
|
return d.toISOString().slice(0, 10);
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "4.51.
|
|
3
|
+
"version": "4.51.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -63,21 +63,21 @@
|
|
|
63
63
|
"tsx": "^4.19.0",
|
|
64
64
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
65
65
|
"@ainyc/canonry-config": "0.0.0",
|
|
66
|
-
"@ainyc/canonry-intelligence": "0.0.0",
|
|
67
|
-
"@ainyc/canonry-contracts": "0.0.0",
|
|
68
66
|
"@ainyc/canonry-db": "0.0.0",
|
|
69
|
-
"@ainyc/canonry-
|
|
70
|
-
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
67
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
71
68
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
72
|
-
"@ainyc/canonry-integration-
|
|
69
|
+
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
73
70
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
74
|
-
"@ainyc/canonry-
|
|
71
|
+
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
72
|
+
"@ainyc/canonry-integration-google": "0.0.0",
|
|
75
73
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
76
74
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
77
|
-
"@ainyc/canonry-provider-
|
|
78
|
-
"@ainyc/canonry-
|
|
75
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
76
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
79
77
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
80
|
-
"@ainyc/canonry-provider-
|
|
78
|
+
"@ainyc/canonry-provider-local": "0.0.0",
|
|
79
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
80
|
+
"@ainyc/canonry-provider-openai": "0.0.0"
|
|
81
81
|
},
|
|
82
82
|
"scripts": {
|
|
83
83
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|