@squadbase/vite-server 0.1.12-dev.a9ac647 → 0.1.17-dev.24af54e
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 +12374 -883
- package/dist/connectors/airtable-oauth.js +257 -46
- package/dist/connectors/airtable.js +294 -51
- package/dist/connectors/amplitude.js +297 -47
- package/dist/connectors/anthropic.js +135 -47
- package/dist/connectors/asana.js +302 -49
- package/dist/connectors/attio.js +277 -49
- package/dist/connectors/aws-billing.js +262 -46
- package/dist/connectors/azure-sql.js +396 -102
- package/dist/connectors/backlog-api-key.js +292 -47
- package/dist/connectors/clickup.js +313 -49
- package/dist/connectors/cosmosdb.js +280 -50
- package/dist/connectors/customerio.js +294 -47
- package/dist/connectors/dbt.js +315 -47
- package/dist/connectors/freshdesk.js +317 -53
- package/dist/connectors/freshsales.js +308 -52
- package/dist/connectors/freshservice.js +336 -53
- package/dist/connectors/gamma.js +302 -52
- package/dist/connectors/gemini.js +134 -47
- package/dist/connectors/github.js +361 -49
- package/dist/connectors/gmail-oauth.js +179 -7
- package/dist/connectors/gmail.js +325 -47
- package/dist/connectors/google-ads.js +263 -46
- package/dist/connectors/google-analytics-oauth.js +285 -46
- package/dist/connectors/google-analytics.js +387 -49
- package/dist/connectors/google-audit-log.js +413 -47
- package/dist/connectors/google-calendar-oauth.js +234 -46
- package/dist/connectors/google-calendar.js +334 -47
- package/dist/connectors/google-docs.js +195 -6
- package/dist/connectors/google-drive.js +237 -5
- package/dist/connectors/google-search-console-oauth.js +231 -46
- package/dist/connectors/google-sheets.js +247 -47
- package/dist/connectors/google-slides.js +180 -6
- package/dist/connectors/grafana.js +307 -49
- package/dist/connectors/hubspot-oauth.js +183 -5
- package/dist/connectors/hubspot.js +281 -49
- package/dist/connectors/influxdb.js +391 -51
- package/dist/connectors/intercom-oauth.js +185 -5
- package/dist/connectors/intercom.js +277 -49
- package/dist/connectors/jdbc.js +737 -110
- package/dist/connectors/jira-api-key.js +301 -47
- package/dist/connectors/kintone-api-token.js +256 -47
- package/dist/connectors/kintone.js +303 -47
- package/dist/connectors/linear.js +305 -49
- package/dist/connectors/linkedin-ads.js +243 -50
- package/dist/connectors/mailchimp-oauth.js +243 -46
- package/dist/connectors/mailchimp.js +295 -49
- package/dist/connectors/meta-ads-oauth.js +248 -48
- package/dist/connectors/meta-ads.js +260 -50
- package/dist/connectors/mixpanel.js +313 -47
- package/dist/connectors/monday.js +335 -49
- package/dist/connectors/mongodb.js +294 -57
- package/dist/connectors/notion-oauth.js +206 -5
- package/dist/connectors/notion.js +298 -51
- package/dist/connectors/openai.js +134 -47
- package/dist/connectors/oracle.js +414 -103
- package/dist/connectors/outlook-oauth.js +179 -5
- package/dist/connectors/powerbi-oauth.js +226 -5
- package/dist/connectors/salesforce.js +359 -49
- package/dist/connectors/semrush.js +289 -49
- package/dist/connectors/sentry.js +264 -50
- package/dist/connectors/shopify-oauth.js +162 -5
- package/dist/connectors/shopify.js +332 -47
- package/dist/connectors/sqlserver.js +390 -102
- package/dist/connectors/stripe-api-key.js +244 -46
- package/dist/connectors/stripe-oauth.js +177 -5
- package/dist/connectors/supabase.js +278 -48
- package/dist/connectors/tableau.js +389 -184
- package/dist/connectors/tiktok-ads.js +254 -48
- package/dist/connectors/wix-store.js +295 -49
- package/dist/connectors/zendesk-oauth.js +214 -5
- package/dist/connectors/zendesk.js +333 -47
- package/dist/index.d.ts +149 -1
- package/dist/index.js +13677 -1969
- package/dist/main.js +13627 -1927
- package/dist/vite-plugin.js +12391 -890
- 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",
|
|
@@ -125,7 +137,7 @@ function createClient(params) {
|
|
|
125
137
|
}
|
|
126
138
|
let cachedToken = null;
|
|
127
139
|
let tokenExpiresAt = 0;
|
|
128
|
-
async function
|
|
140
|
+
async function getAccessToken2() {
|
|
129
141
|
const nowSec = Math.floor(Date.now() / 1e3);
|
|
130
142
|
if (cachedToken && nowSec < tokenExpiresAt - 60) {
|
|
131
143
|
return cachedToken;
|
|
@@ -156,7 +168,7 @@ function createClient(params) {
|
|
|
156
168
|
}
|
|
157
169
|
return {
|
|
158
170
|
async request(path2, init) {
|
|
159
|
-
const accessToken = await
|
|
171
|
+
const accessToken = await getAccessToken2();
|
|
160
172
|
const resolvedPath = path2.replace(/\{propertyId\}/g, propertyId);
|
|
161
173
|
const url = `${BASE_URL.replace(/\/+$/, "")}/${resolvedPath.replace(/^\/+/, "")}`;
|
|
162
174
|
const headers = new Headers(init?.headers);
|
|
@@ -280,6 +292,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
280
292
|
tools;
|
|
281
293
|
query;
|
|
282
294
|
checkConnection;
|
|
295
|
+
/**
|
|
296
|
+
* SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
|
|
297
|
+
* implement this expose a step-by-step exploration flow (database/schema/
|
|
298
|
+
* table/etc. discovery) that the dashboard backend drives via the
|
|
299
|
+
* `/connections/:connectionId/setup` endpoint. Implement by delegating to
|
|
300
|
+
* `runSetupFlow` from `setup-flow.ts`.
|
|
301
|
+
*/
|
|
302
|
+
setup;
|
|
303
|
+
/**
|
|
304
|
+
* Opt-out of the default "verify before save" behavior on connection
|
|
305
|
+
* creation. The backend invokes `checkConnection` synchronously while
|
|
306
|
+
* creating the connection and aborts (no row inserted) if it fails — this
|
|
307
|
+
* flag disables that for connectors where the check cannot succeed pre-save:
|
|
308
|
+
*
|
|
309
|
+
* - `squadbase-db` populates `connection-url` only after Neon provisioning
|
|
310
|
+
* - OAuth connectors require an OAuth-aware proxyFetch keyed by the
|
|
311
|
+
* connectionId, which doesn't exist until the row is saved
|
|
312
|
+
*
|
|
313
|
+
* Exceptions are the explicit position; new credential-input connectors get
|
|
314
|
+
* the default verify-on-create behavior without opt-in.
|
|
315
|
+
*/
|
|
316
|
+
skipConnectionCheckOnCreate;
|
|
283
317
|
constructor(config) {
|
|
284
318
|
this.slug = config.slug;
|
|
285
319
|
this.authType = config.authType;
|
|
@@ -296,6 +330,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
296
330
|
this.tools = config.tools;
|
|
297
331
|
this.query = config.query;
|
|
298
332
|
this.checkConnection = config.checkConnection;
|
|
333
|
+
this.setup = config.setup;
|
|
334
|
+
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
299
335
|
}
|
|
300
336
|
get connectorKey() {
|
|
301
337
|
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
@@ -360,6 +396,51 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
360
396
|
}
|
|
361
397
|
};
|
|
362
398
|
|
|
399
|
+
// ../connectors/src/setup-flow.ts
|
|
400
|
+
async function runSetupFlow(flow, params, ctx, config) {
|
|
401
|
+
const runtime = {
|
|
402
|
+
params,
|
|
403
|
+
language: ctx.language,
|
|
404
|
+
config
|
|
405
|
+
};
|
|
406
|
+
let state = flow.initialState();
|
|
407
|
+
let answerIdx = 0;
|
|
408
|
+
for (const step of flow.steps) {
|
|
409
|
+
const ans = ctx.answers[answerIdx];
|
|
410
|
+
if (ans && ans.questionSlug === step.slug) {
|
|
411
|
+
state = step.applyAnswer(state, ans.answer);
|
|
412
|
+
answerIdx += 1;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (step.type === "text") {
|
|
416
|
+
return {
|
|
417
|
+
type: "nextQuestion",
|
|
418
|
+
questionSlug: step.slug,
|
|
419
|
+
question: step.question[ctx.language],
|
|
420
|
+
questionType: "text"
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
424
|
+
if (options.length === 0) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
type: "nextQuestion",
|
|
429
|
+
questionSlug: step.slug,
|
|
430
|
+
question: step.question[ctx.language],
|
|
431
|
+
questionType: step.type,
|
|
432
|
+
options
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
436
|
+
return { type: "fulfilled", dataInvestigationResult };
|
|
437
|
+
}
|
|
438
|
+
async function resolveSetupSelection(params) {
|
|
439
|
+
const { selected, allSentinel, fetchAll, limit } = params;
|
|
440
|
+
const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
|
|
441
|
+
return resolved.slice(0, limit);
|
|
442
|
+
}
|
|
443
|
+
|
|
363
444
|
// ../connectors/src/auth-types.ts
|
|
364
445
|
var AUTH_TYPES = {
|
|
365
446
|
OAUTH: "oauth",
|
|
@@ -380,6 +461,219 @@ var googleAnalyticsOnboarding = new ConnectorOnboarding({
|
|
|
380
461
|
}
|
|
381
462
|
});
|
|
382
463
|
|
|
464
|
+
// ../connectors/src/connectors/google-analytics/utils.ts
|
|
465
|
+
import * as crypto2 from "crypto";
|
|
466
|
+
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
467
|
+
var ADMIN_BASE_URL = "https://analyticsadmin.googleapis.com/v1beta";
|
|
468
|
+
var SCOPE2 = "https://www.googleapis.com/auth/analytics.readonly";
|
|
469
|
+
function base64url2(input) {
|
|
470
|
+
const buf = typeof input === "string" ? Buffer.from(input) : input;
|
|
471
|
+
return buf.toString("base64url");
|
|
472
|
+
}
|
|
473
|
+
function buildJwt2(clientEmail, privateKey, nowSec) {
|
|
474
|
+
const header = base64url2(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
475
|
+
const payload = base64url2(
|
|
476
|
+
JSON.stringify({
|
|
477
|
+
iss: clientEmail,
|
|
478
|
+
scope: SCOPE2,
|
|
479
|
+
aud: TOKEN_URL2,
|
|
480
|
+
iat: nowSec,
|
|
481
|
+
exp: nowSec + 3600
|
|
482
|
+
})
|
|
483
|
+
);
|
|
484
|
+
const signingInput = `${header}.${payload}`;
|
|
485
|
+
const sign = crypto2.createSign("RSA-SHA256");
|
|
486
|
+
sign.update(signingInput);
|
|
487
|
+
sign.end();
|
|
488
|
+
const signature = base64url2(sign.sign(privateKey));
|
|
489
|
+
return `${signingInput}.${signature}`;
|
|
490
|
+
}
|
|
491
|
+
function decodeServiceAccount(serviceAccountKeyJsonBase64) {
|
|
492
|
+
let serviceAccountKey;
|
|
493
|
+
try {
|
|
494
|
+
const decoded = Buffer.from(
|
|
495
|
+
serviceAccountKeyJsonBase64,
|
|
496
|
+
"base64"
|
|
497
|
+
).toString("utf-8");
|
|
498
|
+
serviceAccountKey = JSON.parse(decoded);
|
|
499
|
+
} catch {
|
|
500
|
+
throw new Error(
|
|
501
|
+
"google-analytics: failed to decode service account key JSON from base64"
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
if (!serviceAccountKey.client_email || !serviceAccountKey.private_key) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
"google-analytics: service account key JSON must contain client_email and private_key"
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return serviceAccountKey;
|
|
510
|
+
}
|
|
511
|
+
async function getAccessToken(serviceAccountKey) {
|
|
512
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
513
|
+
const jwt = buildJwt2(
|
|
514
|
+
serviceAccountKey.client_email,
|
|
515
|
+
serviceAccountKey.private_key,
|
|
516
|
+
nowSec
|
|
517
|
+
);
|
|
518
|
+
const response = await fetch(TOKEN_URL2, {
|
|
519
|
+
method: "POST",
|
|
520
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
521
|
+
body: new URLSearchParams({
|
|
522
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
523
|
+
assertion: jwt
|
|
524
|
+
})
|
|
525
|
+
});
|
|
526
|
+
if (!response.ok) {
|
|
527
|
+
const text = await response.text();
|
|
528
|
+
throw new Error(
|
|
529
|
+
`google-analytics: token exchange failed (${response.status}): ${text}`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
const data = await response.json();
|
|
533
|
+
return data.access_token;
|
|
534
|
+
}
|
|
535
|
+
async function adminFetch(params, path2, init) {
|
|
536
|
+
const keyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
537
|
+
if (!keyJsonBase64) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
"google-analytics: missing required parameter: service-account-key-json-base64"
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
const serviceAccountKey = decodeServiceAccount(keyJsonBase64);
|
|
543
|
+
const accessToken = await getAccessToken(serviceAccountKey);
|
|
544
|
+
const url = `${ADMIN_BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
545
|
+
const headers = new Headers(init?.headers);
|
|
546
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
547
|
+
return fetch(url, { ...init, headers });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ../connectors/src/connectors/google-analytics/setup-flow.ts
|
|
551
|
+
var ALL_PROPERTIES = "__ALL_PROPERTIES__";
|
|
552
|
+
var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
|
|
553
|
+
async function listAccountSummaries(params) {
|
|
554
|
+
const summaries = [];
|
|
555
|
+
let pageToken;
|
|
556
|
+
do {
|
|
557
|
+
const path2 = pageToken ? `/accountSummaries?pageToken=${encodeURIComponent(pageToken)}` : `/accountSummaries`;
|
|
558
|
+
const res = await adminFetch(params, path2);
|
|
559
|
+
if (!res.ok) {
|
|
560
|
+
const body = await res.text().catch(() => res.statusText);
|
|
561
|
+
throw new Error(
|
|
562
|
+
`google-analytics: accountSummaries failed (${res.status}): ${body}`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
const data = await res.json();
|
|
566
|
+
summaries.push(...data.accountSummaries ?? []);
|
|
567
|
+
pageToken = data.nextPageToken;
|
|
568
|
+
} while (pageToken);
|
|
569
|
+
return summaries;
|
|
570
|
+
}
|
|
571
|
+
function propertyIdFromResource(resource) {
|
|
572
|
+
return (resource ?? "").replace(/^properties\//, "");
|
|
573
|
+
}
|
|
574
|
+
async function getProperty(params, propertyId) {
|
|
575
|
+
const res = await adminFetch(params, `/properties/${propertyId}`);
|
|
576
|
+
if (!res.ok) return null;
|
|
577
|
+
return await res.json();
|
|
578
|
+
}
|
|
579
|
+
var googleAnalyticsSetupFlow = {
|
|
580
|
+
initialState: () => ({}),
|
|
581
|
+
steps: [
|
|
582
|
+
{
|
|
583
|
+
slug: "account",
|
|
584
|
+
type: "select",
|
|
585
|
+
question: {
|
|
586
|
+
ja: "Google Analytics \u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044",
|
|
587
|
+
en: "Select a Google Analytics account"
|
|
588
|
+
},
|
|
589
|
+
async fetchOptions(_state, rt) {
|
|
590
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
591
|
+
return summaries.map((s) => {
|
|
592
|
+
const accountResource = s.account ?? s.name ?? "";
|
|
593
|
+
if (!accountResource) return null;
|
|
594
|
+
return {
|
|
595
|
+
value: accountResource,
|
|
596
|
+
label: s.displayName ?? accountResource
|
|
597
|
+
};
|
|
598
|
+
}).filter((v) => v != null);
|
|
599
|
+
},
|
|
600
|
+
applyAnswer: (state, answer) => ({ ...state, account: answer[0] })
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
slug: "properties",
|
|
604
|
+
type: "multiSelect",
|
|
605
|
+
question: {
|
|
606
|
+
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",
|
|
607
|
+
en: "Select target GA4 properties (multi-select allowed)"
|
|
608
|
+
},
|
|
609
|
+
async fetchOptions(state, rt) {
|
|
610
|
+
if (!state.account) return [];
|
|
611
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
612
|
+
const account = summaries.find(
|
|
613
|
+
(s) => (s.account ?? s.name) === state.account
|
|
614
|
+
);
|
|
615
|
+
const props = (account?.propertySummaries ?? []).map((p) => {
|
|
616
|
+
const id = propertyIdFromResource(p.property);
|
|
617
|
+
if (!id) return null;
|
|
618
|
+
return {
|
|
619
|
+
value: id,
|
|
620
|
+
label: p.displayName ? `${p.displayName} (${id})` : id
|
|
621
|
+
};
|
|
622
|
+
}).filter((v) => v != null);
|
|
623
|
+
if (props.length === 0) return [];
|
|
624
|
+
return [
|
|
625
|
+
{
|
|
626
|
+
value: ALL_PROPERTIES,
|
|
627
|
+
label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30D1\u30C6\u30A3" : "All properties"
|
|
628
|
+
},
|
|
629
|
+
...props
|
|
630
|
+
];
|
|
631
|
+
},
|
|
632
|
+
applyAnswer: (state, answer) => ({ ...state, properties: answer })
|
|
633
|
+
}
|
|
634
|
+
],
|
|
635
|
+
async finalize(state, rt) {
|
|
636
|
+
if (!state.account || !state.properties) {
|
|
637
|
+
throw new Error("Google Analytics setup: incomplete state on finalize");
|
|
638
|
+
}
|
|
639
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
640
|
+
const account = summaries.find(
|
|
641
|
+
(s) => (s.account ?? s.name) === state.account
|
|
642
|
+
);
|
|
643
|
+
const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
|
|
644
|
+
const targetPropertyIds = await resolveSetupSelection({
|
|
645
|
+
selected: state.properties,
|
|
646
|
+
allSentinel: ALL_PROPERTIES,
|
|
647
|
+
fetchAll: async () => (account?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
|
|
648
|
+
limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
|
|
649
|
+
});
|
|
650
|
+
const sections = [
|
|
651
|
+
"## Google Analytics",
|
|
652
|
+
"",
|
|
653
|
+
`### Account: ${accountLabel}`,
|
|
654
|
+
""
|
|
655
|
+
];
|
|
656
|
+
if (targetPropertyIds.length === 0) {
|
|
657
|
+
sections.push("_No properties selected._", "");
|
|
658
|
+
return sections.join("\n");
|
|
659
|
+
}
|
|
660
|
+
sections.push("| Property ID | Display Name | Time Zone | Currency | Industry |");
|
|
661
|
+
sections.push("|-------------|--------------|-----------|----------|----------|");
|
|
662
|
+
for (const propertyId of targetPropertyIds) {
|
|
663
|
+
const prop = await getProperty(rt.params, propertyId);
|
|
664
|
+
const displayName = (prop?.displayName ?? "-").replace(/\|/g, "\\|");
|
|
665
|
+
const timeZone = prop?.timeZone ?? "-";
|
|
666
|
+
const currency = prop?.currencyCode ?? "-";
|
|
667
|
+
const industry = (prop?.industryCategory ?? "-").replace(/\|/g, "\\|");
|
|
668
|
+
sections.push(
|
|
669
|
+
`| ${propertyId} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
sections.push("");
|
|
673
|
+
return sections.join("\n");
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
383
677
|
// ../connectors/src/connectors/google-analytics/tools/request.ts
|
|
384
678
|
import { z } from "zod";
|
|
385
679
|
var BASE_URL2 = "https://analyticsdata.googleapis.com/v1beta/";
|
|
@@ -613,7 +907,31 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
613
907
|
- \u7D76\u5BFE\u5024: \`"2024-01-01"\`
|
|
614
908
|
- \u76F8\u5BFE\u5024: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``
|
|
615
909
|
},
|
|
616
|
-
tools
|
|
910
|
+
tools,
|
|
911
|
+
setup: (params, ctx, config) => runSetupFlow(googleAnalyticsSetupFlow, params, ctx, config),
|
|
912
|
+
async checkConnection(params, _config) {
|
|
913
|
+
const keyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
914
|
+
if (!keyJsonBase64) {
|
|
915
|
+
return {
|
|
916
|
+
success: false,
|
|
917
|
+
error: "google-analytics: missing service account key"
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
try {
|
|
921
|
+
const res = await adminFetch(params, "/accountSummaries?pageSize=1");
|
|
922
|
+
if (!res.ok) {
|
|
923
|
+
const body = await res.text().catch(() => res.statusText);
|
|
924
|
+
return {
|
|
925
|
+
success: false,
|
|
926
|
+
error: `google-analytics: accountSummaries failed (${res.status}): ${body}`
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
return { success: true };
|
|
930
|
+
} catch (err) {
|
|
931
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
932
|
+
return { success: false, error: msg };
|
|
933
|
+
}
|
|
934
|
+
}
|
|
617
935
|
});
|
|
618
936
|
|
|
619
937
|
// src/connectors/create-connector-sdk.ts
|
|
@@ -642,6 +960,7 @@ function resolveEnvVarOptional(entry, key) {
|
|
|
642
960
|
import { getContext } from "hono/context-storage";
|
|
643
961
|
import { getCookie } from "hono/cookie";
|
|
644
962
|
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
963
|
+
var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
|
|
645
964
|
function normalizeHeaders(input) {
|
|
646
965
|
const out = {};
|
|
647
966
|
if (!input) return out;
|
|
@@ -650,6 +969,11 @@ function normalizeHeaders(input) {
|
|
|
650
969
|
});
|
|
651
970
|
return out;
|
|
652
971
|
}
|
|
972
|
+
function extractInputUrl(input) {
|
|
973
|
+
if (typeof input === "string") return input;
|
|
974
|
+
if (input instanceof URL) return input.href;
|
|
975
|
+
return input.url;
|
|
976
|
+
}
|
|
653
977
|
function createSandboxProxyFetch(connectionId) {
|
|
654
978
|
return async (input, init) => {
|
|
655
979
|
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
@@ -659,10 +983,17 @@ function createSandboxProxyFetch(connectionId) {
|
|
|
659
983
|
"Connection proxy is not configured. Please check your deployment settings."
|
|
660
984
|
);
|
|
661
985
|
}
|
|
662
|
-
const originalUrl =
|
|
986
|
+
const originalUrl = extractInputUrl(input);
|
|
987
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
988
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
989
|
+
const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
990
|
+
return fetch(sessionUrl, {
|
|
991
|
+
method: "POST",
|
|
992
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
993
|
+
});
|
|
994
|
+
}
|
|
663
995
|
const originalMethod = init?.method ?? "GET";
|
|
664
996
|
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
665
|
-
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
666
997
|
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
667
998
|
return fetch(proxyUrl, {
|
|
668
999
|
method: "POST",
|
|
@@ -688,10 +1019,9 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
688
1019
|
}
|
|
689
1020
|
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
690
1021
|
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
1022
|
+
const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
691
1023
|
return async (input, init) => {
|
|
692
|
-
const originalUrl =
|
|
693
|
-
const originalMethod = init?.method ?? "GET";
|
|
694
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
1024
|
+
const originalUrl = extractInputUrl(input);
|
|
695
1025
|
const c = getContext();
|
|
696
1026
|
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
697
1027
|
if (!appSession) {
|
|
@@ -699,6 +1029,14 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
699
1029
|
"No authentication method available for connection proxy."
|
|
700
1030
|
);
|
|
701
1031
|
}
|
|
1032
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
1033
|
+
return fetch(sessionUrl, {
|
|
1034
|
+
method: "POST",
|
|
1035
|
+
headers: { Authorization: `Bearer ${appSession}` }
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
const originalMethod = init?.method ?? "GET";
|
|
1039
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
702
1040
|
return fetch(proxyUrl, {
|
|
703
1041
|
method: "POST",
|
|
704
1042
|
headers: {
|