@squadbase/vite-server 0.1.12-dev.a9ac647 → 0.1.17-dev.3b633bb
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/cli/index.js +14375 -1652
- package/dist/connectors/airtable-oauth.js +282 -46
- package/dist/connectors/airtable.js +319 -51
- package/dist/connectors/amplitude.js +322 -47
- package/dist/connectors/anthropic.js +135 -47
- package/dist/connectors/asana.js +327 -49
- package/dist/connectors/attio.js +302 -49
- package/dist/connectors/aws-billing.js +287 -46
- package/dist/connectors/azure-sql.js +421 -102
- package/dist/connectors/backlog-api-key.js +317 -47
- package/dist/connectors/clickup.js +338 -49
- package/dist/connectors/cosmosdb.js +305 -50
- package/dist/connectors/customerio.js +319 -47
- package/dist/connectors/dbt.js +340 -47
- package/dist/connectors/freshdesk.js +342 -53
- package/dist/connectors/freshsales.js +333 -52
- package/dist/connectors/freshservice.js +361 -53
- package/dist/connectors/gamma.js +327 -52
- package/dist/connectors/gemini.js +134 -47
- package/dist/connectors/github.js +386 -49
- package/dist/connectors/gmail-oauth.js +204 -7
- package/dist/connectors/gmail.js +350 -47
- package/dist/connectors/google-ads.js +288 -46
- package/dist/connectors/google-analytics-oauth.js +310 -46
- package/dist/connectors/google-analytics.js +547 -87
- package/dist/connectors/google-audit-log.js +438 -47
- package/dist/connectors/google-calendar-oauth.js +259 -46
- package/dist/connectors/google-calendar.js +359 -47
- package/dist/connectors/google-docs.js +220 -6
- package/dist/connectors/google-drive.js +262 -5
- package/dist/connectors/google-search-console-oauth.js +256 -46
- package/dist/connectors/google-sheets.js +272 -47
- package/dist/connectors/google-slides.js +205 -6
- package/dist/connectors/grafana.js +332 -49
- package/dist/connectors/hubspot-oauth.js +208 -5
- package/dist/connectors/hubspot.js +306 -49
- package/dist/connectors/influxdb.js +416 -51
- package/dist/connectors/intercom-oauth.js +210 -5
- package/dist/connectors/intercom.js +302 -49
- package/dist/connectors/jdbc.js +762 -110
- package/dist/connectors/jira-api-key.js +326 -47
- package/dist/connectors/kintone-api-token.js +281 -47
- package/dist/connectors/kintone.js +328 -47
- package/dist/connectors/linear.js +330 -49
- package/dist/connectors/linkedin-ads.js +268 -50
- package/dist/connectors/mailchimp-oauth.js +268 -46
- package/dist/connectors/mailchimp.js +320 -49
- package/dist/connectors/meta-ads-oauth.js +273 -48
- package/dist/connectors/meta-ads.js +285 -50
- package/dist/connectors/mixpanel.js +338 -47
- package/dist/connectors/monday.js +360 -49
- package/dist/connectors/mongodb.js +319 -57
- package/dist/connectors/notion-oauth.js +231 -5
- package/dist/connectors/notion.js +323 -51
- package/dist/connectors/openai.js +134 -47
- package/dist/connectors/oracle.js +454 -103
- package/dist/connectors/outlook-oauth.js +204 -5
- package/dist/connectors/powerbi-oauth.js +498 -5
- package/dist/connectors/salesforce.js +384 -49
- package/dist/connectors/semrush.js +609 -49
- package/dist/connectors/sentry.js +289 -50
- package/dist/connectors/shopify-oauth.js +187 -5
- package/dist/connectors/shopify.js +357 -47
- package/dist/connectors/sqlserver.js +415 -102
- package/dist/connectors/stripe-api-key.js +269 -46
- package/dist/connectors/stripe-oauth.js +202 -5
- package/dist/connectors/supabase.js +303 -48
- package/dist/connectors/tableau.js +536 -163
- package/dist/connectors/tiktok-ads.js +279 -48
- package/dist/connectors/wix-store.js +320 -49
- package/dist/connectors/zendesk-oauth.js +239 -5
- package/dist/connectors/zendesk.js +358 -47
- package/dist/index.d.ts +149 -1
- package/dist/index.js +15057 -2117
- package/dist/main.js +15005 -2073
- package/dist/vite-plugin.js +14752 -2019
- package/package.json +1 -1
|
@@ -1,51 +1,63 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
1
6
|
// ../connectors/src/parameter-definition.ts
|
|
2
|
-
var ParameterDefinition
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
var ParameterDefinition;
|
|
8
|
+
var init_parameter_definition = __esm({
|
|
9
|
+
"../connectors/src/parameter-definition.ts"() {
|
|
10
|
+
"use strict";
|
|
11
|
+
ParameterDefinition = class {
|
|
12
|
+
slug;
|
|
13
|
+
name;
|
|
14
|
+
description;
|
|
15
|
+
envVarBaseKey;
|
|
16
|
+
type;
|
|
17
|
+
secret;
|
|
18
|
+
required;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.slug = config.slug;
|
|
21
|
+
this.name = config.name;
|
|
22
|
+
this.description = config.description;
|
|
23
|
+
this.envVarBaseKey = config.envVarBaseKey;
|
|
24
|
+
this.type = config.type;
|
|
25
|
+
this.secret = config.secret;
|
|
26
|
+
this.required = config.required;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the parameter value from a ConnectorConnectionObject.
|
|
30
|
+
*/
|
|
31
|
+
getValue(connection2) {
|
|
32
|
+
const param = connection2.parameters.find(
|
|
33
|
+
(p) => p.parameterSlug === this.slug
|
|
34
|
+
);
|
|
35
|
+
if (!param || param.value == null) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return param.value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Try to get the parameter value. Returns undefined if not found (for optional params).
|
|
44
|
+
*/
|
|
45
|
+
tryGetValue(connection2) {
|
|
46
|
+
const param = connection2.parameters.find(
|
|
47
|
+
(p) => p.parameterSlug === this.slug
|
|
48
|
+
);
|
|
49
|
+
if (!param || param.value == null) return void 0;
|
|
50
|
+
return param.value;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
42
53
|
}
|
|
43
|
-
};
|
|
54
|
+
});
|
|
44
55
|
|
|
45
56
|
// ../connectors/src/connectors/google-analytics/sdk/index.ts
|
|
46
57
|
import * as crypto from "crypto";
|
|
47
58
|
|
|
48
59
|
// ../connectors/src/connectors/google-analytics/parameters.ts
|
|
60
|
+
init_parameter_definition();
|
|
49
61
|
var parameters = {
|
|
50
62
|
serviceAccountKeyJsonBase64: new ParameterDefinition({
|
|
51
63
|
slug: "service-account-key-json-base64",
|
|
@@ -55,15 +67,6 @@ var parameters = {
|
|
|
55
67
|
type: "base64EncodedJson",
|
|
56
68
|
secret: true,
|
|
57
69
|
required: true
|
|
58
|
-
}),
|
|
59
|
-
propertyId: new ParameterDefinition({
|
|
60
|
-
slug: "property-id",
|
|
61
|
-
name: "Google Analytics Property ID",
|
|
62
|
-
description: "The Google Analytics 4 property ID (e.g., 123456789).",
|
|
63
|
-
envVarBaseKey: "GA_PROPERTY_ID",
|
|
64
|
-
type: "text",
|
|
65
|
-
secret: false,
|
|
66
|
-
required: true
|
|
67
70
|
})
|
|
68
71
|
};
|
|
69
72
|
|
|
@@ -95,15 +98,9 @@ function buildJwt(clientEmail, privateKey, nowSec) {
|
|
|
95
98
|
}
|
|
96
99
|
function createClient(params) {
|
|
97
100
|
const serviceAccountKeyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
98
|
-
|
|
99
|
-
if (!serviceAccountKeyJsonBase64 || !propertyId) {
|
|
100
|
-
const required = [
|
|
101
|
-
parameters.serviceAccountKeyJsonBase64.slug,
|
|
102
|
-
parameters.propertyId.slug
|
|
103
|
-
];
|
|
104
|
-
const missing = required.filter((s) => !params[s]);
|
|
101
|
+
if (!serviceAccountKeyJsonBase64) {
|
|
105
102
|
throw new Error(
|
|
106
|
-
`google-analytics: missing required parameters: ${
|
|
103
|
+
`google-analytics: missing required parameters: ${parameters.serviceAccountKeyJsonBase64.slug}`
|
|
107
104
|
);
|
|
108
105
|
}
|
|
109
106
|
let serviceAccountKey;
|
|
@@ -125,7 +122,7 @@ function createClient(params) {
|
|
|
125
122
|
}
|
|
126
123
|
let cachedToken = null;
|
|
127
124
|
let tokenExpiresAt = 0;
|
|
128
|
-
async function
|
|
125
|
+
async function getAccessToken2() {
|
|
129
126
|
const nowSec = Math.floor(Date.now() / 1e3);
|
|
130
127
|
if (cachedToken && nowSec < tokenExpiresAt - 60) {
|
|
131
128
|
return cachedToken;
|
|
@@ -156,14 +153,13 @@ function createClient(params) {
|
|
|
156
153
|
}
|
|
157
154
|
return {
|
|
158
155
|
async request(path2, init) {
|
|
159
|
-
const accessToken = await
|
|
160
|
-
const
|
|
161
|
-
const url = `${BASE_URL.replace(/\/+$/, "")}/${resolvedPath.replace(/^\/+/, "")}`;
|
|
156
|
+
const accessToken = await getAccessToken2();
|
|
157
|
+
const url = `${BASE_URL.replace(/\/+$/, "")}/${path2.replace(/^\/+/, "")}`;
|
|
162
158
|
const headers = new Headers(init?.headers);
|
|
163
159
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
164
160
|
return fetch(url, { ...init, headers });
|
|
165
161
|
},
|
|
166
|
-
async runReport(request) {
|
|
162
|
+
async runReport(propertyId, request) {
|
|
167
163
|
const response = await this.request(
|
|
168
164
|
`properties/${propertyId}:runReport`,
|
|
169
165
|
{
|
|
@@ -184,7 +180,7 @@ function createClient(params) {
|
|
|
184
180
|
rowCount: data.rowCount ?? 0
|
|
185
181
|
};
|
|
186
182
|
},
|
|
187
|
-
async getMetadata() {
|
|
183
|
+
async getMetadata(propertyId) {
|
|
188
184
|
const response = await this.request(
|
|
189
185
|
`properties/${propertyId}/metadata`,
|
|
190
186
|
{ method: "GET" }
|
|
@@ -197,7 +193,7 @@ function createClient(params) {
|
|
|
197
193
|
}
|
|
198
194
|
return await response.json();
|
|
199
195
|
},
|
|
200
|
-
async runRealtimeReport(request) {
|
|
196
|
+
async runRealtimeReport(propertyId, request) {
|
|
201
197
|
const response = await this.request(
|
|
202
198
|
`properties/${propertyId}:runRealtimeReport`,
|
|
203
199
|
{
|
|
@@ -280,6 +276,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
280
276
|
tools;
|
|
281
277
|
query;
|
|
282
278
|
checkConnection;
|
|
279
|
+
/**
|
|
280
|
+
* SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
|
|
281
|
+
* implement this expose a step-by-step exploration flow (database/schema/
|
|
282
|
+
* table/etc. discovery) that the dashboard backend drives via the
|
|
283
|
+
* `/connections/:connectionId/setup` endpoint. Implement by delegating to
|
|
284
|
+
* `runSetupFlow` from `setup-flow.ts`.
|
|
285
|
+
*/
|
|
286
|
+
setup;
|
|
287
|
+
/**
|
|
288
|
+
* Opt-out of the default "verify before save" behavior on connection
|
|
289
|
+
* creation. The backend invokes `checkConnection` synchronously while
|
|
290
|
+
* creating the connection and aborts (no row inserted) if it fails — this
|
|
291
|
+
* flag disables that for connectors where the check cannot succeed pre-save:
|
|
292
|
+
*
|
|
293
|
+
* - `squadbase-db` populates `connection-url` only after Neon provisioning
|
|
294
|
+
* - OAuth connectors require an OAuth-aware proxyFetch keyed by the
|
|
295
|
+
* connectionId, which doesn't exist until the row is saved
|
|
296
|
+
*
|
|
297
|
+
* Exceptions are the explicit position; new credential-input connectors get
|
|
298
|
+
* the default verify-on-create behavior without opt-in.
|
|
299
|
+
*/
|
|
300
|
+
skipConnectionCheckOnCreate;
|
|
283
301
|
constructor(config) {
|
|
284
302
|
this.slug = config.slug;
|
|
285
303
|
this.authType = config.authType;
|
|
@@ -296,6 +314,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
296
314
|
this.tools = config.tools;
|
|
297
315
|
this.query = config.query;
|
|
298
316
|
this.checkConnection = config.checkConnection;
|
|
317
|
+
this.setup = config.setup;
|
|
318
|
+
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
299
319
|
}
|
|
300
320
|
get connectorKey() {
|
|
301
321
|
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
@@ -360,6 +380,76 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
360
380
|
}
|
|
361
381
|
};
|
|
362
382
|
|
|
383
|
+
// ../connectors/src/setup-flow.ts
|
|
384
|
+
async function runSetupFlow(flow, params, ctx, config) {
|
|
385
|
+
const runtime = {
|
|
386
|
+
params,
|
|
387
|
+
language: ctx.language,
|
|
388
|
+
config
|
|
389
|
+
};
|
|
390
|
+
let state = flow.initialState();
|
|
391
|
+
let answerIdx = 0;
|
|
392
|
+
const pendingParameterUpdates = [];
|
|
393
|
+
for (const step of flow.steps) {
|
|
394
|
+
const ans = ctx.answers[answerIdx];
|
|
395
|
+
if (ans && ans.questionSlug === step.slug) {
|
|
396
|
+
state = step.applyAnswer(state, ans.answer);
|
|
397
|
+
if (step.toParameterUpdates) {
|
|
398
|
+
pendingParameterUpdates.push(...step.toParameterUpdates(state));
|
|
399
|
+
}
|
|
400
|
+
answerIdx += 1;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
|
|
404
|
+
if (step.type === "text") {
|
|
405
|
+
if (step.fetchOptions) {
|
|
406
|
+
const options2 = await step.fetchOptions(state, runtime);
|
|
407
|
+
if (options2.length === 0) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
type: "nextQuestion",
|
|
413
|
+
questionSlug: step.slug,
|
|
414
|
+
question: step.question[ctx.language],
|
|
415
|
+
questionType: "text",
|
|
416
|
+
allowFreeText: resolvedAllowFreeText,
|
|
417
|
+
...pendingParameterUpdates.length > 0 && {
|
|
418
|
+
parameterUpdates: pendingParameterUpdates
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
423
|
+
if (options.length === 0) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
type: "nextQuestion",
|
|
428
|
+
questionSlug: step.slug,
|
|
429
|
+
question: step.question[ctx.language],
|
|
430
|
+
questionType: step.type,
|
|
431
|
+
options,
|
|
432
|
+
allowFreeText: resolvedAllowFreeText,
|
|
433
|
+
...pendingParameterUpdates.length > 0 && {
|
|
434
|
+
parameterUpdates: pendingParameterUpdates
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
439
|
+
return {
|
|
440
|
+
type: "fulfilled",
|
|
441
|
+
dataInvestigationResult,
|
|
442
|
+
...pendingParameterUpdates.length > 0 && {
|
|
443
|
+
parameterUpdates: pendingParameterUpdates
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
async function resolveSetupSelection(params) {
|
|
448
|
+
const { selected, allSentinel, fetchAll, limit } = params;
|
|
449
|
+
const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
|
|
450
|
+
return resolved.slice(0, limit);
|
|
451
|
+
}
|
|
452
|
+
|
|
363
453
|
// ../connectors/src/auth-types.ts
|
|
364
454
|
var AUTH_TYPES = {
|
|
365
455
|
OAUTH: "oauth",
|
|
@@ -380,6 +470,330 @@ var googleAnalyticsOnboarding = new ConnectorOnboarding({
|
|
|
380
470
|
}
|
|
381
471
|
});
|
|
382
472
|
|
|
473
|
+
// ../connectors/src/connectors/google-analytics/utils.ts
|
|
474
|
+
import * as crypto2 from "crypto";
|
|
475
|
+
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
476
|
+
var ADMIN_BASE_URL = "https://analyticsadmin.googleapis.com/v1beta";
|
|
477
|
+
var DATA_BASE_URL = "https://analyticsdata.googleapis.com/v1beta";
|
|
478
|
+
var SCOPE2 = "https://www.googleapis.com/auth/analytics.readonly";
|
|
479
|
+
function base64url2(input) {
|
|
480
|
+
const buf = typeof input === "string" ? Buffer.from(input) : input;
|
|
481
|
+
return buf.toString("base64url");
|
|
482
|
+
}
|
|
483
|
+
function buildJwt2(clientEmail, privateKey, nowSec) {
|
|
484
|
+
const header = base64url2(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
485
|
+
const payload = base64url2(
|
|
486
|
+
JSON.stringify({
|
|
487
|
+
iss: clientEmail,
|
|
488
|
+
scope: SCOPE2,
|
|
489
|
+
aud: TOKEN_URL2,
|
|
490
|
+
iat: nowSec,
|
|
491
|
+
exp: nowSec + 3600
|
|
492
|
+
})
|
|
493
|
+
);
|
|
494
|
+
const signingInput = `${header}.${payload}`;
|
|
495
|
+
const sign = crypto2.createSign("RSA-SHA256");
|
|
496
|
+
sign.update(signingInput);
|
|
497
|
+
sign.end();
|
|
498
|
+
const signature = base64url2(sign.sign(privateKey));
|
|
499
|
+
return `${signingInput}.${signature}`;
|
|
500
|
+
}
|
|
501
|
+
function decodeServiceAccount(serviceAccountKeyJsonBase64) {
|
|
502
|
+
let serviceAccountKey;
|
|
503
|
+
try {
|
|
504
|
+
const decoded = Buffer.from(
|
|
505
|
+
serviceAccountKeyJsonBase64,
|
|
506
|
+
"base64"
|
|
507
|
+
).toString("utf-8");
|
|
508
|
+
serviceAccountKey = JSON.parse(decoded);
|
|
509
|
+
} catch {
|
|
510
|
+
throw new Error(
|
|
511
|
+
"google-analytics: failed to decode service account key JSON from base64"
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
if (!serviceAccountKey.client_email || !serviceAccountKey.private_key) {
|
|
515
|
+
throw new Error(
|
|
516
|
+
"google-analytics: service account key JSON must contain client_email and private_key"
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
return serviceAccountKey;
|
|
520
|
+
}
|
|
521
|
+
async function getAccessToken(serviceAccountKey) {
|
|
522
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
523
|
+
const jwt = buildJwt2(
|
|
524
|
+
serviceAccountKey.client_email,
|
|
525
|
+
serviceAccountKey.private_key,
|
|
526
|
+
nowSec
|
|
527
|
+
);
|
|
528
|
+
const response = await fetch(TOKEN_URL2, {
|
|
529
|
+
method: "POST",
|
|
530
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
531
|
+
body: new URLSearchParams({
|
|
532
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
533
|
+
assertion: jwt
|
|
534
|
+
})
|
|
535
|
+
});
|
|
536
|
+
if (!response.ok) {
|
|
537
|
+
const text = await response.text();
|
|
538
|
+
throw new Error(
|
|
539
|
+
`google-analytics: token exchange failed (${response.status}): ${text}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
const data = await response.json();
|
|
543
|
+
return data.access_token;
|
|
544
|
+
}
|
|
545
|
+
async function adminFetch(params, path2, init) {
|
|
546
|
+
const keyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
547
|
+
if (!keyJsonBase64) {
|
|
548
|
+
throw new Error(
|
|
549
|
+
"google-analytics: missing required parameter: service-account-key-json-base64"
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
const serviceAccountKey = decodeServiceAccount(keyJsonBase64);
|
|
553
|
+
const accessToken = await getAccessToken(serviceAccountKey);
|
|
554
|
+
const url = `${ADMIN_BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
555
|
+
const headers = new Headers(init?.headers);
|
|
556
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
557
|
+
return fetch(url, { ...init, headers });
|
|
558
|
+
}
|
|
559
|
+
async function dataFetch(params, path2, init) {
|
|
560
|
+
const keyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
561
|
+
if (!keyJsonBase64) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
"google-analytics: missing required parameter: service-account-key-json-base64"
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
const serviceAccountKey = decodeServiceAccount(keyJsonBase64);
|
|
567
|
+
const accessToken = await getAccessToken(serviceAccountKey);
|
|
568
|
+
const url = `${DATA_BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
569
|
+
const headers = new Headers(init?.headers);
|
|
570
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
571
|
+
return fetch(url, { ...init, headers });
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// ../connectors/src/connectors/google-analytics/setup-flow.ts
|
|
575
|
+
var ALL_PROPERTIES = "__ALL_PROPERTIES__";
|
|
576
|
+
var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
|
|
577
|
+
var METADATA_DISPLAY_LIMIT = 30;
|
|
578
|
+
async function listAccountSummaries(params) {
|
|
579
|
+
const summaries = [];
|
|
580
|
+
let pageToken;
|
|
581
|
+
do {
|
|
582
|
+
const path2 = pageToken ? `/accountSummaries?pageToken=${encodeURIComponent(pageToken)}` : `/accountSummaries`;
|
|
583
|
+
const res = await adminFetch(params, path2);
|
|
584
|
+
if (!res.ok) {
|
|
585
|
+
const body = await res.text().catch(() => res.statusText);
|
|
586
|
+
throw new Error(
|
|
587
|
+
`google-analytics: accountSummaries failed (${res.status}): ${body}`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const data = await res.json();
|
|
591
|
+
summaries.push(...data.accountSummaries ?? []);
|
|
592
|
+
pageToken = data.nextPageToken;
|
|
593
|
+
} while (pageToken);
|
|
594
|
+
return summaries;
|
|
595
|
+
}
|
|
596
|
+
function propertyIdFromResource(resource) {
|
|
597
|
+
return (resource ?? "").replace(/^properties\//, "");
|
|
598
|
+
}
|
|
599
|
+
async function getProperty(params, propertyId) {
|
|
600
|
+
const res = await adminFetch(params, `/properties/${propertyId}`);
|
|
601
|
+
if (!res.ok) return null;
|
|
602
|
+
return await res.json();
|
|
603
|
+
}
|
|
604
|
+
async function getMetadata(params, propertyId) {
|
|
605
|
+
try {
|
|
606
|
+
const res = await dataFetch(
|
|
607
|
+
params,
|
|
608
|
+
`/properties/${propertyId}/metadata`
|
|
609
|
+
);
|
|
610
|
+
if (!res.ok) return { dimensions: [], metrics: [] };
|
|
611
|
+
const data = await res.json();
|
|
612
|
+
return {
|
|
613
|
+
dimensions: data.dimensions ?? [],
|
|
614
|
+
metrics: data.metrics ?? []
|
|
615
|
+
};
|
|
616
|
+
} catch {
|
|
617
|
+
return { dimensions: [], metrics: [] };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function appendMetadataSection(sections, dimensions, metrics) {
|
|
621
|
+
if (dimensions.length > 0) {
|
|
622
|
+
sections.push(`#### Dimensions (${dimensions.length})`, "");
|
|
623
|
+
for (const d of dimensions.slice(0, METADATA_DISPLAY_LIMIT)) {
|
|
624
|
+
sections.push(`- ${d.apiName ?? d.uiName ?? "(unknown)"}`);
|
|
625
|
+
}
|
|
626
|
+
if (dimensions.length > METADATA_DISPLAY_LIMIT) {
|
|
627
|
+
sections.push(
|
|
628
|
+
`- \u2026and ${dimensions.length - METADATA_DISPLAY_LIMIT} more`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
sections.push("");
|
|
632
|
+
}
|
|
633
|
+
if (metrics.length > 0) {
|
|
634
|
+
sections.push(`#### Metrics (${metrics.length})`, "");
|
|
635
|
+
for (const m of metrics.slice(0, METADATA_DISPLAY_LIMIT)) {
|
|
636
|
+
sections.push(`- ${m.apiName ?? m.uiName ?? "(unknown)"}`);
|
|
637
|
+
}
|
|
638
|
+
if (metrics.length > METADATA_DISPLAY_LIMIT) {
|
|
639
|
+
sections.push(
|
|
640
|
+
`- \u2026and ${metrics.length - METADATA_DISPLAY_LIMIT} more`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
sections.push("");
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
var googleAnalyticsSetupFlow = {
|
|
647
|
+
initialState: () => ({}),
|
|
648
|
+
steps: [
|
|
649
|
+
{
|
|
650
|
+
slug: "account",
|
|
651
|
+
type: "select",
|
|
652
|
+
question: {
|
|
653
|
+
ja: "Google Analytics \u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044",
|
|
654
|
+
en: "Select a Google Analytics account"
|
|
655
|
+
},
|
|
656
|
+
async fetchOptions(_state, rt) {
|
|
657
|
+
try {
|
|
658
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
659
|
+
return summaries.map((s) => {
|
|
660
|
+
const accountResource = s.account ?? s.name ?? "";
|
|
661
|
+
if (!accountResource) return null;
|
|
662
|
+
return {
|
|
663
|
+
value: accountResource,
|
|
664
|
+
label: s.displayName ?? accountResource
|
|
665
|
+
};
|
|
666
|
+
}).filter((v) => v != null);
|
|
667
|
+
} catch {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
applyAnswer: (state, answer) => ({ ...state, account: answer[0] })
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
slug: "properties",
|
|
675
|
+
type: "multiSelect",
|
|
676
|
+
question: {
|
|
677
|
+
ja: "\u5BFE\u8C61\u306E GA4 \u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
|
|
678
|
+
en: "Select target GA4 properties (multi-select allowed)"
|
|
679
|
+
},
|
|
680
|
+
async fetchOptions(state, rt) {
|
|
681
|
+
if (!state.account) return [];
|
|
682
|
+
try {
|
|
683
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
684
|
+
const account = summaries.find(
|
|
685
|
+
(s) => (s.account ?? s.name) === state.account
|
|
686
|
+
);
|
|
687
|
+
const props = (account?.propertySummaries ?? []).map((p) => {
|
|
688
|
+
const id = propertyIdFromResource(p.property);
|
|
689
|
+
if (!id) return null;
|
|
690
|
+
return {
|
|
691
|
+
value: id,
|
|
692
|
+
label: p.displayName ? `${p.displayName} (${id})` : id
|
|
693
|
+
};
|
|
694
|
+
}).filter((v) => v != null);
|
|
695
|
+
if (props.length === 0) return [];
|
|
696
|
+
return [
|
|
697
|
+
{
|
|
698
|
+
value: ALL_PROPERTIES,
|
|
699
|
+
label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30D1\u30C6\u30A3" : "All properties"
|
|
700
|
+
},
|
|
701
|
+
...props
|
|
702
|
+
];
|
|
703
|
+
} catch {
|
|
704
|
+
return [];
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
applyAnswer: (state, answer) => ({ ...state, properties: answer })
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
slug: "manualPropertyId",
|
|
711
|
+
type: "text",
|
|
712
|
+
question: {
|
|
713
|
+
ja: "GA4 \u30D7\u30ED\u30D1\u30C6\u30A3 ID \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: 123456789\uFF09\u3002GA4 \u7BA1\u7406\u753B\u9762 > \u30D7\u30ED\u30D1\u30C6\u30A3\u8A2D\u5B9A\u3067\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002",
|
|
714
|
+
en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings."
|
|
715
|
+
},
|
|
716
|
+
async fetchOptions(state) {
|
|
717
|
+
if (state.properties?.length) return [];
|
|
718
|
+
return [{ value: "_show", label: "" }];
|
|
719
|
+
},
|
|
720
|
+
applyAnswer: (state, answer) => ({
|
|
721
|
+
...state,
|
|
722
|
+
manualPropertyId: answer[0]
|
|
723
|
+
})
|
|
724
|
+
}
|
|
725
|
+
],
|
|
726
|
+
async finalize(state, rt) {
|
|
727
|
+
const sections = ["## Google Analytics", ""];
|
|
728
|
+
if (state.account && state.properties) {
|
|
729
|
+
let summaries = [];
|
|
730
|
+
try {
|
|
731
|
+
summaries = await listAccountSummaries(rt.params);
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
734
|
+
const account = summaries.find(
|
|
735
|
+
(s) => (s.account ?? s.name) === state.account
|
|
736
|
+
);
|
|
737
|
+
const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
|
|
738
|
+
const targetPropertyIds = await resolveSetupSelection({
|
|
739
|
+
selected: state.properties,
|
|
740
|
+
allSentinel: ALL_PROPERTIES,
|
|
741
|
+
fetchAll: async () => (account?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
|
|
742
|
+
limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
|
|
743
|
+
});
|
|
744
|
+
sections.push(`### Account: ${accountLabel}`, "");
|
|
745
|
+
if (targetPropertyIds.length === 0) {
|
|
746
|
+
sections.push("_No properties selected._", "");
|
|
747
|
+
return sections.join("\n");
|
|
748
|
+
}
|
|
749
|
+
sections.push(
|
|
750
|
+
"| Property ID | Display Name | Time Zone | Currency | Industry |"
|
|
751
|
+
);
|
|
752
|
+
sections.push(
|
|
753
|
+
"|-------------|--------------|-----------|----------|----------|"
|
|
754
|
+
);
|
|
755
|
+
for (const pid of targetPropertyIds) {
|
|
756
|
+
const prop = await getProperty(rt.params, pid).catch(() => null);
|
|
757
|
+
const displayName = (prop?.displayName ?? "-").replace(/\|/g, "\\|");
|
|
758
|
+
const timeZone = prop?.timeZone ?? "-";
|
|
759
|
+
const currency = prop?.currencyCode ?? "-";
|
|
760
|
+
const industry = (prop?.industryCategory ?? "-").replace(
|
|
761
|
+
/\|/g,
|
|
762
|
+
"\\|"
|
|
763
|
+
);
|
|
764
|
+
sections.push(
|
|
765
|
+
`| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
sections.push("");
|
|
769
|
+
const firstPropertyId = targetPropertyIds[0];
|
|
770
|
+
if (firstPropertyId) {
|
|
771
|
+
const { dimensions, metrics } = await getMetadata(
|
|
772
|
+
rt.params,
|
|
773
|
+
firstPropertyId
|
|
774
|
+
);
|
|
775
|
+
appendMetadataSection(sections, dimensions, metrics);
|
|
776
|
+
}
|
|
777
|
+
return sections.join("\n");
|
|
778
|
+
}
|
|
779
|
+
const propertyId = state.manualPropertyId;
|
|
780
|
+
if (propertyId) {
|
|
781
|
+
sections.push(`### Property: ${propertyId}`, "");
|
|
782
|
+
const { dimensions, metrics } = await getMetadata(
|
|
783
|
+
rt.params,
|
|
784
|
+
propertyId
|
|
785
|
+
);
|
|
786
|
+
appendMetadataSection(sections, dimensions, metrics);
|
|
787
|
+
return sections.join("\n");
|
|
788
|
+
}
|
|
789
|
+
sections.push(
|
|
790
|
+
"_Could not list GA4 accounts. Please enable the Google Analytics Admin API in your GCP project. Property ID can be specified per request at runtime._",
|
|
791
|
+
""
|
|
792
|
+
);
|
|
793
|
+
return sections.join("\n");
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
|
|
383
797
|
// ../connectors/src/connectors/google-analytics/tools/request.ts
|
|
384
798
|
import { z } from "zod";
|
|
385
799
|
var BASE_URL2 = "https://analyticsdata.googleapis.com/v1beta/";
|
|
@@ -387,8 +801,9 @@ var REQUEST_TIMEOUT_MS = 6e4;
|
|
|
387
801
|
var inputSchema = z.object({
|
|
388
802
|
toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
|
|
389
803
|
connectionId: z.string().describe("ID of the Google Analytics connection to use"),
|
|
804
|
+
propertyId: z.string().describe("GA4 property ID (e.g., '123456789')"),
|
|
390
805
|
method: z.enum(["GET", "POST"]).describe("HTTP method"),
|
|
391
|
-
path: z.string().describe("API path (e.g., 'properties/{propertyId}:runReport'). {propertyId} is
|
|
806
|
+
path: z.string().describe("API path (e.g., 'properties/{propertyId}:runReport'). {propertyId} is replaced with the propertyId parameter."),
|
|
392
807
|
body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
|
|
393
808
|
});
|
|
394
809
|
var outputSchema = z.discriminatedUnion("success", [
|
|
@@ -406,10 +821,10 @@ var requestTool = new ConnectorTool({
|
|
|
406
821
|
name: "request",
|
|
407
822
|
description: `Send authenticated requests to the Google Analytics Data API.
|
|
408
823
|
Authentication is handled automatically using a service account.
|
|
409
|
-
{propertyId} in the path is automatically replaced with the
|
|
824
|
+
{propertyId} in the path is automatically replaced with the propertyId parameter.`,
|
|
410
825
|
inputSchema,
|
|
411
826
|
outputSchema,
|
|
412
|
-
async execute({ connectionId, method, path: path2, body }, connections) {
|
|
827
|
+
async execute({ connectionId, propertyId, method, path: path2, body }, connections) {
|
|
413
828
|
const connection2 = connections.find((c) => c.id === connectionId);
|
|
414
829
|
if (!connection2) {
|
|
415
830
|
return { success: false, error: `Connection ${connectionId} not found` };
|
|
@@ -418,7 +833,6 @@ Authentication is handled automatically using a service account.
|
|
|
418
833
|
try {
|
|
419
834
|
const { GoogleAuth } = await import("google-auth-library");
|
|
420
835
|
const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
|
|
421
|
-
const propertyId = parameters.propertyId.getValue(connection2);
|
|
422
836
|
const credentials = JSON.parse(
|
|
423
837
|
Buffer.from(keyJsonBase64, "base64").toString("utf-8")
|
|
424
838
|
);
|
|
@@ -478,16 +892,16 @@ var googleAnalyticsConnector = new ConnectorPlugin({
|
|
|
478
892
|
systemPrompt: {
|
|
479
893
|
en: `### Tools
|
|
480
894
|
|
|
481
|
-
- \`google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. See the GA4 Data API Reference below for available endpoints and request bodies.
|
|
895
|
+
- \`google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. Requires a \`propertyId\` parameter. See the GA4 Data API Reference below for available endpoints and request bodies.
|
|
482
896
|
|
|
483
897
|
### Business Logic
|
|
484
898
|
|
|
485
899
|
The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
|
|
486
900
|
|
|
487
901
|
SDK methods (client created via \`connection(connectionId)\`):
|
|
488
|
-
- \`client.runReport(request)\` \u2014 run a GA4 report
|
|
489
|
-
- \`client.runRealtimeReport(request)\` \u2014 run a realtime report
|
|
490
|
-
- \`client.getMetadata()\` \u2014 fetch available dimensions/metrics
|
|
902
|
+
- \`client.runReport(propertyId, request)\` \u2014 run a GA4 report
|
|
903
|
+
- \`client.runRealtimeReport(propertyId, request)\` \u2014 run a realtime report
|
|
904
|
+
- \`client.getMetadata(propertyId)\` \u2014 fetch available dimensions/metrics
|
|
491
905
|
- \`client.request(path, init?)\` \u2014 low-level authenticated fetch
|
|
492
906
|
|
|
493
907
|
\`\`\`ts
|
|
@@ -497,12 +911,13 @@ import { connection } from "@squadbase/vite-server/connectors/google-analytics";
|
|
|
497
911
|
const ga = connection("<connectionId>");
|
|
498
912
|
|
|
499
913
|
export default async function handler(c: Context) {
|
|
500
|
-
const { startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
|
|
914
|
+
const { propertyId, startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
|
|
915
|
+
propertyId: string;
|
|
501
916
|
startDate?: string;
|
|
502
917
|
endDate?: string;
|
|
503
918
|
}>();
|
|
504
919
|
|
|
505
|
-
const { rows } = await ga.runReport({
|
|
920
|
+
const { rows } = await ga.runReport(propertyId, {
|
|
506
921
|
dateRanges: [{ startDate, endDate }],
|
|
507
922
|
dimensions: [{ name: "date" }],
|
|
508
923
|
metrics: [{ name: "activeUsers" }, { name: "sessions" }],
|
|
@@ -546,16 +961,16 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
546
961
|
- Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
|
|
547
962
|
ja: `### \u30C4\u30FC\u30EB
|
|
548
963
|
|
|
549
|
-
- \`google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
964
|
+
- \`google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\`propertyId\` \u30D1\u30E9\u30E1\u30FC\u30BF\u30FC\u304C\u5FC5\u8981\u3067\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
550
965
|
|
|
551
966
|
### Business Logic
|
|
552
967
|
|
|
553
968
|
\u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
|
|
554
969
|
|
|
555
970
|
SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
|
|
556
|
-
- \`client.runReport(request)\` \u2014 GA4\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
|
|
557
|
-
- \`client.runRealtimeReport(request)\` \u2014 \u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
|
|
558
|
-
- \`client.getMetadata()\` \u2014 \u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u3092\u53D6\u5F97
|
|
971
|
+
- \`client.runReport(propertyId, request)\` \u2014 GA4\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
|
|
972
|
+
- \`client.runRealtimeReport(propertyId, request)\` \u2014 \u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
|
|
973
|
+
- \`client.getMetadata(propertyId)\` \u2014 \u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u3092\u53D6\u5F97
|
|
559
974
|
- \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
|
|
560
975
|
|
|
561
976
|
\`\`\`ts
|
|
@@ -565,12 +980,13 @@ import { connection } from "@squadbase/vite-server/connectors/google-analytics";
|
|
|
565
980
|
const ga = connection("<connectionId>");
|
|
566
981
|
|
|
567
982
|
export default async function handler(c: Context) {
|
|
568
|
-
const { startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
|
|
983
|
+
const { propertyId, startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
|
|
984
|
+
propertyId: string;
|
|
569
985
|
startDate?: string;
|
|
570
986
|
endDate?: string;
|
|
571
987
|
}>();
|
|
572
988
|
|
|
573
|
-
const { rows } = await ga.runReport({
|
|
989
|
+
const { rows } = await ga.runReport(propertyId, {
|
|
574
990
|
dateRanges: [{ startDate, endDate }],
|
|
575
991
|
dimensions: [{ name: "date" }],
|
|
576
992
|
metrics: [{ name: "activeUsers" }, { name: "sessions" }],
|
|
@@ -613,7 +1029,31 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
613
1029
|
- \u7D76\u5BFE\u5024: \`"2024-01-01"\`
|
|
614
1030
|
- \u76F8\u5BFE\u5024: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``
|
|
615
1031
|
},
|
|
616
|
-
tools
|
|
1032
|
+
tools,
|
|
1033
|
+
setup: (params, ctx, config) => runSetupFlow(googleAnalyticsSetupFlow, params, ctx, config),
|
|
1034
|
+
async checkConnection(params, _config) {
|
|
1035
|
+
const keyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
1036
|
+
if (!keyJsonBase64) {
|
|
1037
|
+
return {
|
|
1038
|
+
success: false,
|
|
1039
|
+
error: "google-analytics: missing service account key"
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
try {
|
|
1043
|
+
const res = await dataFetch(params, `/metadata`);
|
|
1044
|
+
if (!res.ok) {
|
|
1045
|
+
const body = await res.text().catch(() => res.statusText);
|
|
1046
|
+
return {
|
|
1047
|
+
success: false,
|
|
1048
|
+
error: `Google Analytics API failed: HTTP ${res.status} ${body}`
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
return { success: true };
|
|
1052
|
+
} catch (err) {
|
|
1053
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1054
|
+
return { success: false, error: msg };
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
617
1057
|
});
|
|
618
1058
|
|
|
619
1059
|
// src/connectors/create-connector-sdk.ts
|
|
@@ -642,6 +1082,7 @@ function resolveEnvVarOptional(entry, key) {
|
|
|
642
1082
|
import { getContext } from "hono/context-storage";
|
|
643
1083
|
import { getCookie } from "hono/cookie";
|
|
644
1084
|
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
1085
|
+
var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
|
|
645
1086
|
function normalizeHeaders(input) {
|
|
646
1087
|
const out = {};
|
|
647
1088
|
if (!input) return out;
|
|
@@ -650,6 +1091,11 @@ function normalizeHeaders(input) {
|
|
|
650
1091
|
});
|
|
651
1092
|
return out;
|
|
652
1093
|
}
|
|
1094
|
+
function extractInputUrl(input) {
|
|
1095
|
+
if (typeof input === "string") return input;
|
|
1096
|
+
if (input instanceof URL) return input.href;
|
|
1097
|
+
return input.url;
|
|
1098
|
+
}
|
|
653
1099
|
function createSandboxProxyFetch(connectionId) {
|
|
654
1100
|
return async (input, init) => {
|
|
655
1101
|
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
@@ -659,10 +1105,17 @@ function createSandboxProxyFetch(connectionId) {
|
|
|
659
1105
|
"Connection proxy is not configured. Please check your deployment settings."
|
|
660
1106
|
);
|
|
661
1107
|
}
|
|
662
|
-
const originalUrl =
|
|
1108
|
+
const originalUrl = extractInputUrl(input);
|
|
1109
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
1110
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
1111
|
+
const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
1112
|
+
return fetch(sessionUrl, {
|
|
1113
|
+
method: "POST",
|
|
1114
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
663
1117
|
const originalMethod = init?.method ?? "GET";
|
|
664
1118
|
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
665
|
-
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
666
1119
|
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
667
1120
|
return fetch(proxyUrl, {
|
|
668
1121
|
method: "POST",
|
|
@@ -688,10 +1141,9 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
688
1141
|
}
|
|
689
1142
|
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
690
1143
|
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
1144
|
+
const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
691
1145
|
return async (input, init) => {
|
|
692
|
-
const originalUrl =
|
|
693
|
-
const originalMethod = init?.method ?? "GET";
|
|
694
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
1146
|
+
const originalUrl = extractInputUrl(input);
|
|
695
1147
|
const c = getContext();
|
|
696
1148
|
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
697
1149
|
if (!appSession) {
|
|
@@ -699,6 +1151,14 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
699
1151
|
"No authentication method available for connection proxy."
|
|
700
1152
|
);
|
|
701
1153
|
}
|
|
1154
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
1155
|
+
return fetch(sessionUrl, {
|
|
1156
|
+
method: "POST",
|
|
1157
|
+
headers: { Authorization: `Bearer ${appSession}` }
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
const originalMethod = init?.method ?? "GET";
|
|
1161
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
702
1162
|
return fetch(proxyUrl, {
|
|
703
1163
|
method: "POST",
|
|
704
1164
|
headers: {
|