@jskit-ai/connectors-catalog 0.2.2 → 0.2.4
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/README.md +4 -4
- package/docs/ai.md +3 -3
- package/docs/airtable.md +1 -1
- package/docs/algolia.md +2 -2
- package/docs/amazon-redshift.md +2 -2
- package/docs/apollo-io.md +2 -2
- package/docs/asana.md +2 -2
- package/docs/ashby.md +1 -1
- package/docs/attention.md +2 -2
- package/docs/aws-athena.md +1 -1
- package/docs/aws-credentials.md +3 -3
- package/docs/aws-s3.md +1 -1
- package/docs/bigquery.md +2 -2
- package/docs/brevo.md +2 -2
- package/docs/calendly.md +3 -3
- package/docs/canva.md +2 -2
- package/docs/chargebee.md +2 -2
- package/docs/clay.md +2 -2
- package/docs/clickhouse.md +1 -1
- package/docs/confidence-flags.md +1 -1
- package/docs/contentful.md +1 -1
- package/docs/dbt-semantic-layer.md +2 -2
- package/docs/elevenlabs.md +2 -2
- package/docs/firebase-cloud-messaging.md +3 -3
- package/docs/firecrawl.md +4 -4
- package/docs/fireflies.md +1 -1
- package/docs/gatewayapi.md +1 -1
- package/docs/gemini-enterprise.md +1 -1
- package/docs/github-api.md +1 -1
- package/docs/gitlab-api.md +1 -1
- package/docs/gmail.md +1 -1
- package/docs/gong.md +1 -1
- package/docs/google-ads.md +3 -3
- package/docs/google-analytics.md +2 -2
- package/docs/google-docs.md +1 -1
- package/docs/google-drive.md +1 -1
- package/docs/google-maps-platform.md +2 -2
- package/docs/google-search-console.md +1 -1
- package/docs/google-sheets.md +1 -1
- package/docs/google-slides.md +2 -2
- package/docs/granola.md +2 -2
- package/docs/hex.md +2 -2
- package/docs/heygen.md +2 -2
- package/docs/inngest.md +1 -1
- package/docs/klipy.md +1 -1
- package/docs/lightspeed.md +2 -2
- package/docs/linear.md +1 -1
- package/docs/linkedin.md +2 -2
- package/docs/mailgun.md +1 -1
- package/docs/microsoft-excel.md +2 -2
- package/docs/microsoft-fabric.md +1 -1
- package/docs/microsoft-outlook.md +1 -1
- package/docs/microsoft-powerpoint.md +1 -1
- package/docs/microsoft-teams.md +1 -1
- package/docs/microsoft-word.md +1 -1
- package/docs/notion.md +1 -1
- package/docs/oura.md +3 -3
- package/docs/paddle.md +3 -3
- package/docs/pipedrive.md +2 -2
- package/docs/prestashop.md +1 -1
- package/docs/replicate.md +1 -1
- package/docs/resend.md +1 -1
- package/docs/salesforce.md +1 -1
- package/docs/sanity.md +3 -3
- package/docs/semrush.md +2 -2
- package/docs/sentry.md +1 -1
- package/docs/slack.md +1 -1
- package/docs/snowflake.md +5 -5
- package/docs/storyblok.md +1 -1
- package/docs/stripe.md +2 -2
- package/docs/tally.md +1 -1
- package/docs/telegram.md +2 -2
- package/docs/tiktok.md +3 -3
- package/docs/twilio.md +3 -3
- package/docs/twitch.md +2 -2
- package/docs/wave.md +3 -3
- package/docs/wix.md +13 -13
- package/docs/wordpress-com.md +1 -1
- package/docs/wordpress-self-hosted.md +1 -1
- package/docs/workday.md +2 -2
- package/docs/x-twitter.md +3 -3
- package/docs/xero.md +3 -3
- package/docs/zoho-books.md +2 -2
- package/docs/zoho-crm.md +2 -2
- package/package.json +4 -3
- package/patterns/ai-connections/PATTERN.md +16 -1
- package/patterns/ai-connections/example/ai-model.js +12 -3
- package/patterns/ai-connections/example/perplexity-answer.js +15 -4
- package/patterns/api-key-connection/PATTERN.md +9 -1
- package/patterns/api-key-connection/example/connections.js +5 -2
- package/patterns/aws-storage-queries/PATTERN.md +13 -3
- package/patterns/aws-storage-queries/example/s3-transfer.js +14 -3
- package/patterns/event-delivery/PATTERN.md +1 -1
- package/patterns/firebase-messaging/PATTERN.md +10 -0
- package/patterns/firebase-messaging/example/server/notifications.js +8 -0
- package/patterns/google-ads-search/PATTERN.md +18 -7
- package/patterns/google-ads-search/example/ads-setup.js +13 -5
- package/patterns/oauth-connection/PATTERN.md +1 -1
- package/patterns/paddle-catalogue/PATTERN.md +11 -2
- package/patterns/paddle-catalogue/example/create-products.js +5 -2
- package/patterns/public-image/PATTERN.md +10 -1
- package/patterns/public-image/example/logo-url.js +4 -1
- package/patterns/redshift-queries/PATTERN.md +1 -1
- package/patterns/source-scanning/PATTERN.md +13 -2
- package/patterns/source-scanning/example/source-scanner.js +9 -0
- package/src/server/ai.js +19 -5
- package/src/server/apify.js +2 -2
- package/src/server/asana.js +6 -6
- package/src/server/ashby.js +2 -1
- package/src/server/attention.js +12 -11
- package/src/server/awsTransport.js +6 -0
- package/src/server/bigquery.js +5 -4
- package/src/server/brevo.js +2 -1
- package/src/server/clay.js +2 -1
- package/src/server/clickhouse.js +2 -1
- package/src/server/confidence.js +6 -1
- package/src/server/connectorValues.js +33 -0
- package/src/server/databricks.js +7 -7
- package/src/server/dbt-semantic-layer.js +5 -5
- package/src/server/elevenlabs.js +2 -2
- package/src/server/firebase-cloud-messaging.js +2 -2
- package/src/server/gemini-enterprise.js +5 -5
- package/src/server/google-ads-search.js +68 -48
- package/src/server/google-ads.js +15 -17
- package/src/server/google-drive.js +7 -7
- package/src/server/google-maps-platform.js +4 -2
- package/src/server/google-search-console.js +4 -3
- package/src/server/google.js +8 -4
- package/src/server/googleAdsApi.js +14 -0
- package/src/server/granola.js +2 -2
- package/src/server/hubspot.js +2 -1
- package/src/server/incident-io.js +3 -2
- package/src/server/lexware.js +5 -4
- package/src/server/lightspeed.js +7 -5
- package/src/server/linkedin.js +2 -2
- package/src/server/mcpTransport.js +8 -4
- package/src/server/microsoft-fabric.js +3 -3
- package/src/server/microsoft-onedrive.js +2 -2
- package/src/server/microsoft-onenote.js +4 -3
- package/src/server/microsoft-outlook.js +6 -6
- package/src/server/microsoft-sharepoint.js +4 -2
- package/src/server/microsoft-teams.js +2 -1
- package/src/server/microsoft.js +20 -13
- package/src/server/notion.js +2 -1
- package/src/server/pipedrive.js +2 -1
- package/src/server/salesforce.js +12 -12
- package/src/server/semrush.js +9 -8
- package/src/server/snowflake.js +20 -20
- package/src/server/storyblok.js +2 -1
- package/src/server/tally.js +2 -1
- package/src/server/telegram.js +2 -6
- package/src/server/tiktok.js +3 -3
- package/src/server/twilio.js +3 -2
- package/src/server/twitch.js +5 -3
- package/src/server/types.js +13 -0
- package/src/server/wix.js +20 -878
- package/src/server/wixBookings.js +449 -0
- package/src/server/wixCms.js +132 -0
- package/src/server/wixStores.js +296 -0
- package/src/server/wixValues.js +35 -0
- package/src/server/wiz.js +18 -4
- package/src/server/woocommerce.js +1 -1
- package/src/server/wordpress-com.js +2 -1
- package/src/server/workday.js +9 -9
- package/src/server/x-twitter.js +2 -1
- package/src/server/xero.js +40 -40
- package/src/server/zoho-books.js +6 -6
- package/src/server/zoho-crm.js +5 -5
- package/src/shared/confidence.js +6 -1
- package/src/shared/dbt-semantic-layer.js +1 -1
- package/src/shared/figma.js +1 -1
- package/src/shared/google.js +8 -4
- package/src/shared/googleScope.js +4 -0
- package/src/shared/microsoft-fabric.js +4 -5
- package/src/shared/microsoft.js +8 -3
- package/src/shared/salesforce.js +1 -1
- package/src/shared/tokens.js +7 -3
- package/src/shared/uuid.js +13 -0
- package/src/shared/wix.js +4 -3
- package/src/shared/zoho-books.js +3 -4
- package/src/shared/zoho-crm.js +1 -1
- package/src/shared/{zoho-regions.js → zoho.js} +4 -1
- package/test/ai.test.js +1 -1
- package/test/brevo.test.js +1 -1
- package/test/chargebee.test.js +1 -1
- package/test/google-ads.test.js +8 -4
- package/test/linkedin.test.js +1 -1
- package/test/resend.test.js +2 -2
- package/test/salesforce.test.js +1 -1
- package/test/snowflake.test.js +7 -7
- package/test/tiktok.test.js +1 -1
- package/test/wiz.test.js +1 -1
package/src/server/xero.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { xeroDefinition } from "../shared/xero.js";
|
|
5
5
|
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
import { isUuid } from "../shared/uuid.js";
|
|
7
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
8
|
|
|
7
|
-
const
|
|
8
|
-
const uuid = (value) => typeof value === "string" && uuidPattern.test(value);
|
|
9
|
-
const tenantId = { type: "string", required: true, validator: (value) => uuid(value) || "Choose an organisation ID returned by connections.list." };
|
|
9
|
+
const tenantId = { type: "string", required: true, validator: (value) => isUuid(value) || "Choose an organisation ID returned by connections.list." };
|
|
10
10
|
const paging = {
|
|
11
11
|
page: { type: "integer", min: 1, max: 1_000_000, defaultTo: 1 },
|
|
12
12
|
pageSize: { type: "integer", min: 1, max: 200, defaultTo: 100 }
|
|
@@ -22,12 +22,12 @@ const reportFields = {
|
|
|
22
22
|
timeframe: { type: "string", enum: ["MONTH", "QUARTER", "YEAR"] },
|
|
23
23
|
standardLayout: { type: "boolean" }, paymentsOnly: { type: "boolean" }
|
|
24
24
|
};
|
|
25
|
-
const reportRow = row =>
|
|
25
|
+
const reportRow = row => isRecord(row) && typeof row.RowType === "string" &&
|
|
26
26
|
(row.Cells === undefined || Array.isArray(row.Cells) && row.Cells.every(cell => typeof cell?.Value === "string"));
|
|
27
27
|
const reportResult = value => Array.isArray(value?.Reports) && value.Reports.length === 1 && value.Reports.every(report =>
|
|
28
28
|
typeof report?.ReportID === "string" && Array.isArray(report.Rows) && report.Rows.every(row => reportRow(row) &&
|
|
29
29
|
(row.Rows === undefined || Array.isArray(row.Rows) && row.Rows.every(reportRow))));
|
|
30
|
-
const validConnections = (value) => Array.isArray(value) && value.every((entry) =>
|
|
30
|
+
const validConnections = (value) => Array.isArray(value) && value.every((entry) => isUuid(entry?.id) && isUuid(entry.tenantId) &&
|
|
31
31
|
["ORGANISATION", "PRACTICEMANAGER", "PRACTICE"].includes(entry.tenantType) && (entry.tenantName === null || typeof entry.tenantName === "string"));
|
|
32
32
|
|
|
33
33
|
function accountingRead(resource, fields, scopes, validateResult) {
|
|
@@ -49,7 +49,7 @@ function accountingRead(resource, fields, scopes, validateResult) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function accountingRecord(resource, idName, scopes, fields = {}) {
|
|
52
|
-
const schema = createSchema({ tenantId, ...fields, id: { type: "string", required: true, validator: value =>
|
|
52
|
+
const schema = createSchema({ tenantId, ...fields, id: { type: "string", required: true, validator: value => isUuid(value) || "Use the record ID returned by Xero." } });
|
|
53
53
|
return {
|
|
54
54
|
scopes,
|
|
55
55
|
request(input) {
|
|
@@ -62,7 +62,7 @@ function accountingRecord(resource, idName, scopes, fields = {}) {
|
|
|
62
62
|
return { method: "GET", url: url.href,
|
|
63
63
|
headers: { "Xero-tenant-id": tenant.toLowerCase() } };
|
|
64
64
|
},
|
|
65
|
-
validateResult: value => Array.isArray(value?.[resource]) && value[resource].length === 1 &&
|
|
65
|
+
validateResult: value => Array.isArray(value?.[resource]) && value[resource].length === 1 && isUuid(value[resource][0]?.[idName])
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -70,7 +70,7 @@ function contactWrite(update) {
|
|
|
70
70
|
const schema = createSchema({
|
|
71
71
|
tenantId,
|
|
72
72
|
idempotencyKey: idempotencyField,
|
|
73
|
-
...(update ? { id: { type: "string", required: true, validator: value =>
|
|
73
|
+
...(update ? { id: { type: "string", required: true, validator: value => isUuid(value) || "Use the contact ID returned by Xero." } } : {}),
|
|
74
74
|
Name: { type: "string", required: !update, minLength: 1, maxLength: 255 },
|
|
75
75
|
FirstName: { type: "string", maxLength: 255 }, LastName: { type: "string", maxLength: 255 },
|
|
76
76
|
EmailAddress: { type: "string", maxLength: 255 }, ContactNumber: { type: "string", maxLength: 50 }
|
|
@@ -83,15 +83,15 @@ function contactWrite(update) {
|
|
|
83
83
|
return { method: update ? "POST" : "PUT", url: `https://api.xero.com/api.xro/2.0/Contacts${update ? `/${id.toLowerCase()}` : ""}`,
|
|
84
84
|
headers: { "Xero-tenant-id": tenant.toLowerCase(), "Idempotency-Key": idempotencyKey }, body: { Contacts: [contact] } };
|
|
85
85
|
},
|
|
86
|
-
validateResult: value => Array.isArray(value?.Contacts) && value.Contacts.length === 1 &&
|
|
86
|
+
validateResult: value => Array.isArray(value?.Contacts) && value.Contacts.length === 1 && isUuid(value.Contacts[0]?.ContactID) &&
|
|
87
87
|
value.Contacts[0].HasValidationErrors !== true && !value.Contacts[0].ValidationErrors?.length
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
const paymentSchema = createSchema({
|
|
92
92
|
tenantId, idempotencyKey: idempotencyField,
|
|
93
|
-
invoiceId: { type: "string", required: true, validator: value =>
|
|
94
|
-
accountId: { type: "string", required: true, validator: value =>
|
|
93
|
+
invoiceId: { type: "string", required: true, validator: value => isUuid(value) || "Choose an invoice ID." },
|
|
94
|
+
accountId: { type: "string", required: true, validator: value => isUuid(value) || "Choose an account ID." },
|
|
95
95
|
Date: dateField,
|
|
96
96
|
Amount: { type: "number", required: true, validator: value => Number.isFinite(value) && value > 0 && value <= Number.MAX_SAFE_INTEGER || "Use a positive finite payment amount." },
|
|
97
97
|
CurrencyRate: { type: "number", validator: value => Number.isFinite(value) && value > 0 || "Use a positive finite exchange rate." },
|
|
@@ -101,9 +101,9 @@ const finiteAmount = { type: "number", validator: value => Number.isFinite(value
|
|
|
101
101
|
function invoiceWrite(update) {
|
|
102
102
|
const schema = createSchema({
|
|
103
103
|
tenantId, idempotencyKey: idempotencyField,
|
|
104
|
-
...(update ? { id: { type: "string", required: true, validator: value =>
|
|
104
|
+
...(update ? { id: { type: "string", required: true, validator: value => isUuid(value) || "Choose the invoice ID." } } : {}),
|
|
105
105
|
Type: { type: "string", required: !update, enum: ["ACCREC", "ACCPAY"] },
|
|
106
|
-
contactId: { type: "string", required: !update, validator: value =>
|
|
106
|
+
contactId: { type: "string", required: !update, validator: value => isUuid(value) || "Choose a contact ID." },
|
|
107
107
|
Date: { ...dateField, required: !update }, DueDate: { ...dateField, required: false },
|
|
108
108
|
CurrencyCode: { type: "string", required: !update, validator: value => /^[A-Z]{3}$/u.test(value) || "Use the three-letter invoice currency code." },
|
|
109
109
|
LineAmountTypes: { type: "string", required: !update, enum: ["Exclusive", "Inclusive", "NoTax"] },
|
|
@@ -111,7 +111,7 @@ function invoiceWrite(update) {
|
|
|
111
111
|
Reference: { type: "string", maxLength: 255 }, InvoiceNumber: { type: "string", maxLength: 255 },
|
|
112
112
|
LineItems: { type: "array", required: !update, validator: values => values.length > 0 && values.length <= 100 || "Supply 1–100 invoice lines.",
|
|
113
113
|
items: { type: "object", schema: createSchema({
|
|
114
|
-
LineItemID: { type: "string", validator: value =>
|
|
114
|
+
LineItemID: { type: "string", validator: value => isUuid(value) || "Use the existing line item ID." },
|
|
115
115
|
Description: { type: "string", required: true, minLength: 1, maxLength: 4000 },
|
|
116
116
|
Quantity: { ...finiteAmount, required: true, min: 0.0001 },
|
|
117
117
|
UnitAmount: { ...finiteAmount, required: true }, AccountCode: { type: "string", required: true, minLength: 1, maxLength: 50 },
|
|
@@ -131,7 +131,7 @@ function invoiceWrite(update) {
|
|
|
131
131
|
return { method: update ? "POST" : "PUT", url: `https://api.xero.com/api.xro/2.0/Invoices${update ? `/${id.toLowerCase()}` : ""}?unitdp=4`,
|
|
132
132
|
headers: { "Xero-tenant-id": tenant.toLowerCase(), "Idempotency-Key": idempotencyKey }, body: { Invoices: [invoice] } };
|
|
133
133
|
},
|
|
134
|
-
validateResult: value => Array.isArray(value?.Invoices) && value.Invoices.length === 1 &&
|
|
134
|
+
validateResult: value => Array.isArray(value?.Invoices) && value.Invoices.length === 1 && isUuid(value.Invoices[0]?.InvoiceID) &&
|
|
135
135
|
value.Invoices[0].HasErrors !== true && !value.Invoices[0].ValidationErrors?.length
|
|
136
136
|
};
|
|
137
137
|
}
|
|
@@ -141,8 +141,8 @@ function attachmentOperation(action) {
|
|
|
141
141
|
const schema = createSchema({
|
|
142
142
|
tenantId,
|
|
143
143
|
resource: { type: "string", required: true, enum: ["Contacts", "Invoices", "BankTransactions", "ManualJournals"] },
|
|
144
|
-
id: { type: "string", required: true, validator: value =>
|
|
145
|
-
...(action === "download" ? { attachmentId: { type: "string", required: true, validator: value =>
|
|
144
|
+
id: { type: "string", required: true, validator: value => isUuid(value) || "Choose the parent record ID." },
|
|
145
|
+
...(action === "download" ? { attachmentId: { type: "string", required: true, validator: value => isUuid(value) || "Choose an attachment ID from attachments.list." } } : {}),
|
|
146
146
|
...(writing ? {
|
|
147
147
|
idempotencyKey: idempotencyField,
|
|
148
148
|
filename: { type: "string", required: true, minLength: 1, maxLength: 255,
|
|
@@ -172,24 +172,24 @@ function attachmentOperation(action) {
|
|
|
172
172
|
validateResult: action === "download"
|
|
173
173
|
? value => typeof value?.contentBase64 === "string" && Number.isInteger(value.size) && value.size > 0
|
|
174
174
|
: value => Array.isArray(value?.Attachments) && (!writing || value.Attachments.length === 1) && value.Attachments.every(file =>
|
|
175
|
-
|
|
175
|
+
isUuid(file?.AttachmentID) && typeof file.FileName === "string" && Number.isInteger(file.ContentLength) && file.ContentLength >= 0)
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
function bankTransactionWrite(update) {
|
|
180
180
|
const schema = createSchema({
|
|
181
181
|
tenantId, idempotencyKey: idempotencyField,
|
|
182
|
-
...(update ? { id: { type: "string", required: true, validator: value =>
|
|
182
|
+
...(update ? { id: { type: "string", required: true, validator: value => isUuid(value) || "Choose a bank transaction ID." } } : {}),
|
|
183
183
|
Type: { type: "string", required: !update, enum: ["SPEND", "RECEIVE"] },
|
|
184
|
-
contactId: { type: "string", required: !update, validator: value =>
|
|
185
|
-
bankAccountId: { type: "string", required: !update, validator: value =>
|
|
184
|
+
contactId: { type: "string", required: !update, validator: value => isUuid(value) || "Choose a contact ID." },
|
|
185
|
+
bankAccountId: { type: "string", required: !update, validator: value => isUuid(value) || "Choose a bank account ID." },
|
|
186
186
|
Date: { ...dateField, required: !update }, Reference: { type: "string", maxLength: 255 },
|
|
187
187
|
CurrencyRate: { type: "number", validator: value => Number.isFinite(value) && value > 0 || "Use a positive finite exchange rate." },
|
|
188
188
|
LineAmountTypes: { type: "string", required: !update, enum: ["Exclusive", "Inclusive", "NoTax"] },
|
|
189
189
|
...(update ? { Status: { type: "string", enum: ["AUTHORISED", "DELETED"] } } : {}),
|
|
190
190
|
LineItems: { type: "array", required: !update, validator: values => values.length > 0 && values.length <= 100 || "Supply 1–100 transaction lines.",
|
|
191
191
|
items: { type: "object", schema: createSchema({
|
|
192
|
-
LineItemID: { type: "string", validator: value =>
|
|
192
|
+
LineItemID: { type: "string", validator: value => isUuid(value) || "Use the existing line item ID." },
|
|
193
193
|
Description: { type: "string", required: true, minLength: 1, maxLength: 4000 },
|
|
194
194
|
Quantity: { ...finiteAmount, required: true, min: 0.0001 }, UnitAmount: { ...finiteAmount, required: true },
|
|
195
195
|
AccountCode: { type: "string", required: true, minLength: 1, maxLength: 50 }, TaxType: { type: "string", maxLength: 50 }
|
|
@@ -207,7 +207,7 @@ function bankTransactionWrite(update) {
|
|
|
207
207
|
return { method: update ? "POST" : "PUT", url: `https://api.xero.com/api.xro/2.0/BankTransactions${update ? `/${id.toLowerCase()}` : ""}?unitdp=4`,
|
|
208
208
|
headers: { "Xero-tenant-id": tenant.toLowerCase(), "Idempotency-Key": idempotencyKey }, body: { BankTransactions: [transaction] } };
|
|
209
209
|
},
|
|
210
|
-
validateResult: value => Array.isArray(value?.BankTransactions) && value.BankTransactions.length === 1 &&
|
|
210
|
+
validateResult: value => Array.isArray(value?.BankTransactions) && value.BankTransactions.length === 1 && isUuid(value.BankTransactions[0]?.BankTransactionID) &&
|
|
211
211
|
!value.BankTransactions[0].ValidationErrors?.length
|
|
212
212
|
};
|
|
213
213
|
}
|
|
@@ -215,7 +215,7 @@ function bankTransactionWrite(update) {
|
|
|
215
215
|
function manualJournalWrite(update) {
|
|
216
216
|
const schema = createSchema({
|
|
217
217
|
tenantId, idempotencyKey: idempotencyField,
|
|
218
|
-
...(update ? { id: { type: "string", required: true, validator: value =>
|
|
218
|
+
...(update ? { id: { type: "string", required: true, validator: value => isUuid(value) || "Choose a manual journal ID." } } : {}),
|
|
219
219
|
Narration: { type: "string", required: !update, minLength: 1, maxLength: 4000 },
|
|
220
220
|
Date: { ...dateField, required: !update },
|
|
221
221
|
LineAmountTypes: { type: "string", required: !update, enum: ["Exclusive", "Inclusive", "NoTax"] },
|
|
@@ -244,7 +244,7 @@ function manualJournalWrite(update) {
|
|
|
244
244
|
return { method: update ? "POST" : "PUT", url: `https://api.xero.com/api.xro/2.0/ManualJournals${update ? `/${id.toLowerCase()}` : ""}`,
|
|
245
245
|
headers: { "Xero-tenant-id": tenant.toLowerCase(), "Idempotency-Key": idempotencyKey }, body: { ManualJournals: [journal] } };
|
|
246
246
|
},
|
|
247
|
-
validateResult: value => Array.isArray(value?.ManualJournals) && value.ManualJournals.length === 1 &&
|
|
247
|
+
validateResult: value => Array.isArray(value?.ManualJournals) && value.ManualJournals.length === 1 && isUuid(value.ManualJournals[0]?.ManualJournalID) &&
|
|
248
248
|
!value.ManualJournals[0].ValidationErrors?.length
|
|
249
249
|
};
|
|
250
250
|
}
|
|
@@ -260,20 +260,20 @@ const xeroProvider = Object.freeze({
|
|
|
260
260
|
"attachments.replace": attachmentOperation("replace"),
|
|
261
261
|
"connections.list": jsonOperation(connectionsUrl, {}, validConnections),
|
|
262
262
|
"accounts.list": accountingRead("Accounts", {}, ["accounting.settings.read", "accounting.settings"],
|
|
263
|
-
value => Array.isArray(value?.Accounts) && value.Accounts.every(account =>
|
|
263
|
+
value => Array.isArray(value?.Accounts) && value.Accounts.every(account => isUuid(account?.AccountID) && typeof account.Type === "string")),
|
|
264
264
|
"currencies.list": accountingRead("Currencies", {}, ["accounting.settings.read", "accounting.settings"],
|
|
265
265
|
value => Array.isArray(value?.Currencies) && value.Currencies.every(currency => /^[A-Z]{3}$/u.test(currency?.Code) && typeof currency.Description === "string")),
|
|
266
266
|
"taxRates.list": accountingRead("TaxRates", {}, ["accounting.settings.read", "accounting.settings"],
|
|
267
267
|
value => Array.isArray(value?.TaxRates) && value.TaxRates.every(rate => typeof rate?.TaxType === "string" && typeof rate.Name === "string")),
|
|
268
268
|
"trackingCategories.list": accountingRead("TrackingCategories", { includeArchived: { type: "boolean", defaultTo: false } },
|
|
269
269
|
["accounting.settings.read", "accounting.settings"],
|
|
270
|
-
value => Array.isArray(value?.TrackingCategories) && value.TrackingCategories.every(category =>
|
|
270
|
+
value => Array.isArray(value?.TrackingCategories) && value.TrackingCategories.every(category => isUuid(category?.TrackingCategoryID) && typeof category.Name === "string")),
|
|
271
271
|
"reports.agedReceivables": accountingRead("Reports/AgedReceivablesByContact", {
|
|
272
|
-
contactId: { type: "string", required: true, validator: value =>
|
|
272
|
+
contactId: { type: "string", required: true, validator: value => isUuid(value) || "Choose a contact ID." },
|
|
273
273
|
date: dateField, fromDate: { ...dateField, required: false }, toDate: { ...dateField, required: false }
|
|
274
274
|
}, ["accounting.reports.aged.read"], reportResult),
|
|
275
275
|
"reports.agedPayables": accountingRead("Reports/AgedPayablesByContact", {
|
|
276
|
-
contactId: { type: "string", required: true, validator: value =>
|
|
276
|
+
contactId: { type: "string", required: true, validator: value => isUuid(value) || "Choose a contact ID." },
|
|
277
277
|
date: dateField, fromDate: { ...dateField, required: false }, toDate: { ...dateField, required: false }
|
|
278
278
|
}, ["accounting.reports.aged.read"], reportResult),
|
|
279
279
|
"reports.executiveSummary": accountingRead("Reports/ExecutiveSummary", { date: dateField },
|
|
@@ -285,7 +285,7 @@ const xeroProvider = Object.freeze({
|
|
|
285
285
|
"budgets.list": accountingRead("Budgets", {
|
|
286
286
|
DateFrom: { ...dateField, required: false }, DateTo: { ...dateField, required: false }
|
|
287
287
|
}, ["accounting.budgets.read"],
|
|
288
|
-
value => Array.isArray(value?.Budgets) && value.Budgets.every(budget =>
|
|
288
|
+
value => Array.isArray(value?.Budgets) && value.Budgets.every(budget => isUuid(budget?.BudgetID) && ["OVERALL", "TRACKING"].includes(budget.Type))),
|
|
289
289
|
"budgets.get": accountingRecord("Budgets", "BudgetID", ["accounting.budgets.read"], {
|
|
290
290
|
DateFrom: { ...dateField, required: false }, DateTo: { ...dateField, required: false }
|
|
291
291
|
}),
|
|
@@ -297,13 +297,13 @@ const xeroProvider = Object.freeze({
|
|
|
297
297
|
"bankTransactions.update": bankTransactionWrite(true),
|
|
298
298
|
"bankTransactions.list": accountingRead("BankTransactions", { page: paging.page },
|
|
299
299
|
["accounting.banktransactions.read", "accounting.banktransactions"],
|
|
300
|
-
value => Array.isArray(value?.BankTransactions) && value.BankTransactions.length <= 100 && value.BankTransactions.every(record =>
|
|
300
|
+
value => Array.isArray(value?.BankTransactions) && value.BankTransactions.length <= 100 && value.BankTransactions.every(record => isUuid(record?.BankTransactionID))),
|
|
301
301
|
"bankTransactions.get": accountingRecord("BankTransactions", "BankTransactionID", ["accounting.banktransactions.read", "accounting.banktransactions"]),
|
|
302
302
|
"manualJournals.create": manualJournalWrite(false),
|
|
303
303
|
"manualJournals.update": manualJournalWrite(true),
|
|
304
304
|
"manualJournals.list": accountingRead("ManualJournals", { page: paging.page },
|
|
305
305
|
["accounting.manualjournals.read", "accounting.manualjournals"],
|
|
306
|
-
value => Array.isArray(value?.ManualJournals) && value.ManualJournals.length <= 100 && value.ManualJournals.every(record =>
|
|
306
|
+
value => Array.isArray(value?.ManualJournals) && value.ManualJournals.length <= 100 && value.ManualJournals.every(record => isUuid(record?.ManualJournalID))),
|
|
307
307
|
"manualJournals.get": accountingRecord("ManualJournals", "ManualJournalID", ["accounting.manualjournals.read", "accounting.manualjournals"]),
|
|
308
308
|
"payments.create": {
|
|
309
309
|
scopes: ["accounting.payments"],
|
|
@@ -313,7 +313,7 @@ const xeroProvider = Object.freeze({
|
|
|
313
313
|
headers: { "Xero-tenant-id": tenant.toLowerCase(), "Idempotency-Key": idempotencyKey },
|
|
314
314
|
body: { Payments: [{ ...payment, Invoice: { InvoiceID: invoiceId }, Account: { AccountID: accountId } }] } };
|
|
315
315
|
},
|
|
316
|
-
validateResult: value => Array.isArray(value?.Payments) && value.Payments.length === 1 &&
|
|
316
|
+
validateResult: value => Array.isArray(value?.Payments) && value.Payments.length === 1 && isUuid(value.Payments[0]?.PaymentID) &&
|
|
317
317
|
Number.isFinite(value.Payments[0].Amount) && value.Payments[0].HasValidationErrors !== true && !value.Payments[0].ValidationErrors?.length
|
|
318
318
|
},
|
|
319
319
|
"contacts.get": accountingRecord("Contacts", "ContactID", ["accounting.contacts.read", "accounting.contacts"]),
|
|
@@ -329,13 +329,13 @@ const xeroProvider = Object.freeze({
|
|
|
329
329
|
"payments.list": accountingRead("Payments", { page: paging.page, pageSize: { ...paging.pageSize, max: 100 } },
|
|
330
330
|
["accounting.payments.read", "accounting.payments"],
|
|
331
331
|
(value) => Array.isArray(value?.Payments) && value.Payments.length <= 100 && value.Payments.every((entry) =>
|
|
332
|
-
|
|
332
|
+
isUuid(entry?.PaymentID) && Number.isFinite(entry.Amount) && typeof entry.Status === "string")),
|
|
333
333
|
"organisation.read": accountingRead("Organisation", {}, ["accounting.settings.read", "accounting.settings"],
|
|
334
|
-
(value) => Array.isArray(value?.Organisations) && value.Organisations.length === 1 &&
|
|
334
|
+
(value) => Array.isArray(value?.Organisations) && value.Organisations.length === 1 && isUuid(value.Organisations[0]?.OrganisationID)),
|
|
335
335
|
"contacts.list": accountingRead("Contacts", { ...paging, includeArchived: { type: "boolean", defaultTo: false }, searchTerm: { type: "string", minLength: 1, maxLength: 255 } },
|
|
336
|
-
["accounting.contacts.read", "accounting.contacts"], (value) => Array.isArray(value?.Contacts) && value.Contacts.length <= 200 && value.Contacts.every((entry) =>
|
|
336
|
+
["accounting.contacts.read", "accounting.contacts"], (value) => Array.isArray(value?.Contacts) && value.Contacts.length <= 200 && value.Contacts.every((entry) => isUuid(entry?.ContactID))),
|
|
337
337
|
"invoices.list": accountingRead("Invoices", paging, ["accounting.invoices.read", "accounting.invoices"],
|
|
338
|
-
(value) => Array.isArray(value?.Invoices) && value.Invoices.length <= 200 && value.Invoices.every((entry) =>
|
|
338
|
+
(value) => Array.isArray(value?.Invoices) && value.Invoices.length <= 200 && value.Invoices.every((entry) => isUuid(entry?.InvoiceID)))
|
|
339
339
|
},
|
|
340
340
|
async exchange(address, options, { request, fetchImpl }) {
|
|
341
341
|
if (address !== connectionsUrl) {
|
|
@@ -354,7 +354,7 @@ const xeroProvider = Object.freeze({
|
|
|
354
354
|
headers: { ...options.headers, Accept: downloading ? "application/octet-stream" : "application/json" } });
|
|
355
355
|
if (!response.ok) {
|
|
356
356
|
await response.body?.cancel();
|
|
357
|
-
throw
|
|
357
|
+
throw providerStatusError(response.status);
|
|
358
358
|
}
|
|
359
359
|
const reader = response.body?.getReader(); const chunks = []; let size = 0;
|
|
360
360
|
if (reader) try {
|
|
@@ -380,7 +380,7 @@ const xeroProvider = Object.freeze({
|
|
|
380
380
|
if (record) {
|
|
381
381
|
const idName = { Contacts: "ContactID", Invoices: "InvoiceID", Budgets: "BudgetID", BankTransactions: "BankTransactionID", ManualJournals: "ManualJournalID" }[record[1]];
|
|
382
382
|
const returnedId = result?.[record[1]]?.[0]?.[idName];
|
|
383
|
-
if (!
|
|
383
|
+
if (!isUuid(returnedId) || returnedId.toLowerCase() !== record[2].toLowerCase()) {
|
|
384
384
|
throw new ConnectorError("connector_response_invalid", "Xero returned a different record.", { statusCode: 502, messageCode: "connectors.shared.differentRecord", messageParams: { provider: "Xero" } });
|
|
385
385
|
}
|
|
386
386
|
}
|
package/src/server/zoho-books.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
|
-
import { zohoBooksDefinition,
|
|
5
|
-
import { zohoRegions } from "../shared/zoho
|
|
3
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
4
|
+
import { zohoBooksDefinition, zohoBooksOrganizationField } from "../shared/zoho-books.js";
|
|
5
|
+
import { isZohoId, zohoRegions } from "../shared/zoho.js";
|
|
6
6
|
import { jsonOperation } from "./jsonOperation.js";
|
|
7
7
|
import { zohoAuthorizationParameters, zohoOAuthMetadata, normalizeZohoTokenResponse } from "./zohoOAuth.js";
|
|
8
8
|
|
|
9
9
|
const regionFor = ({ region = "eu" } = {}) => zohoRegions.find(({ value }) => value === region);
|
|
10
10
|
const apiOrigin = (settings) => `https://www.zohoapis.${regionFor(settings).suffix}`;
|
|
11
11
|
const validOrganizations = (value) => Array.isArray(value?.organizations) && value.organizations.every((org) =>
|
|
12
|
-
|
|
12
|
+
isZohoId(org?.organization_id) && typeof org.name === "string" && typeof org.is_org_active === "boolean");
|
|
13
13
|
|
|
14
14
|
function booksList(resource, identifier) {
|
|
15
15
|
const schema = createSchema({
|
|
@@ -33,7 +33,7 @@ function booksList(resource, identifier) {
|
|
|
33
33
|
return { method: "GET", url: url.href };
|
|
34
34
|
},
|
|
35
35
|
validateResult: (value) => Array.isArray(value?.[resource]) && value[resource].length <= 200 &&
|
|
36
|
-
value[resource].every((record) =>
|
|
36
|
+
value[resource].every((record) => isZohoId(record?.[identifier])) && typeof value.page_context?.has_more_page === "boolean"
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -57,7 +57,7 @@ const zohoBooksProvider = Object.freeze({
|
|
|
57
57
|
const response = await fetchImpl(address, { ...options, headers, credentials: "omit" });
|
|
58
58
|
if (!response.ok) {
|
|
59
59
|
if (response.status === 404) throw new ConnectorError("connector_resource_not_found", "This Books resource is not available.", { statusCode: 404, messageCode: "connectors.zohoBooks.resourceNotAvailable" });
|
|
60
|
-
throw
|
|
60
|
+
throw providerStatusError(response.status);
|
|
61
61
|
}
|
|
62
62
|
let value;
|
|
63
63
|
try { value = await response.json(); } catch { /* validated below */ }
|
package/src/server/zoho-crm.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
4
|
+
import { isZohoId } from "../shared/zoho.js";
|
|
4
5
|
import { zohoCrmDefinition, zohoCrmRegions } from "../shared/zoho-crm.js";
|
|
5
6
|
import { jsonOperation } from "./jsonOperation.js";
|
|
6
7
|
import { zohoAuthorizationParameters, zohoOAuthMetadata, normalizeZohoTokenResponse } from "./zohoOAuth.js";
|
|
7
8
|
|
|
8
9
|
const regionFor = ({ region = "us" } = {}) => zohoCrmRegions.find(({ value }) => value === region);
|
|
9
10
|
const apiOrigin = (settings = {}) => `https://${settings.environment === "sandbox" ? "sandbox" : settings.environment === "developer" ? "developer" : "www"}.zohoapis.${regionFor(settings).suffix}`;
|
|
10
|
-
const identifier = (value) => typeof value === "string" && /^[1-9][0-9]{0,29}$/u.test(value);
|
|
11
11
|
const pageToken = (value) => typeof value === "string" && value.length > 0 && value.length <= 4096 && !/[\s\p{Cc}]/u.test(value);
|
|
12
12
|
|
|
13
13
|
function recordsList(module, defaults) {
|
|
@@ -32,7 +32,7 @@ function recordsList(module, defaults) {
|
|
|
32
32
|
for (const [name, value] of Object.entries(values)) url.searchParams.set(name, String(value));
|
|
33
33
|
return { method: "GET", url: url.href };
|
|
34
34
|
},
|
|
35
|
-
validateResult: (result) => Array.isArray(result?.data) && result.data.length <= 200 && result.data.every((record) =>
|
|
35
|
+
validateResult: (result) => Array.isArray(result?.data) && result.data.length <= 200 && result.data.every((record) => isZohoId(record?.id)) &&
|
|
36
36
|
Number.isSafeInteger(result.info?.count) && result.info.count === result.data.length &&
|
|
37
37
|
Number.isSafeInteger(result.info.per_page) && result.info.per_page >= 1 && result.info.per_page <= 200 &&
|
|
38
38
|
typeof result.info.more_records === "boolean" &&
|
|
@@ -51,7 +51,7 @@ const zohoCrmProvider = Object.freeze({
|
|
|
51
51
|
operations: {
|
|
52
52
|
"users.current": {
|
|
53
53
|
...jsonOperation((settings) => `${apiOrigin(settings)}/crm/v8/users?type=CurrentUser`, {},
|
|
54
|
-
(value) => Array.isArray(value?.users) && value.users.length === 1 &&
|
|
54
|
+
(value) => Array.isArray(value?.users) && value.users.length === 1 && isZohoId(value.users[0]?.id)),
|
|
55
55
|
scopes: ["ZohoCRM.users.READ", "ZohoCRM.users.ALL"]
|
|
56
56
|
},
|
|
57
57
|
"leads.list": recordsList("Leads", "Last_Name,Email"),
|
|
@@ -73,7 +73,7 @@ const zohoCrmProvider = Object.freeze({
|
|
|
73
73
|
if (!response.ok) {
|
|
74
74
|
if (response.status === 404) throw new ConnectorError("connector_resource_not_found", "This CRM resource is not available.", { statusCode: 404, messageCode: "connectors.zohoCrm.resourceNotAvailable" });
|
|
75
75
|
// The shared runtime maps HTTP status without exposing provider response text.
|
|
76
|
-
throw
|
|
76
|
+
throw providerStatusError(response.status);
|
|
77
77
|
}
|
|
78
78
|
if (response.status !== 200 || !value || value.status === "error" || value.code) {
|
|
79
79
|
throw new ConnectorError("connector_response_invalid", "Zoho CRM returned an unexpected response.", { statusCode: 502, messageCode: "connectors.shared.unexpectedResponse", messageParams: { provider: "Zoho CRM" } });
|
package/src/shared/confidence.js
CHANGED
|
@@ -7,7 +7,12 @@ const confidenceScopes = [
|
|
|
7
7
|
|
|
8
8
|
function definition(id, name, description) {
|
|
9
9
|
return Object.freeze({
|
|
10
|
-
id,
|
|
10
|
+
id,
|
|
11
|
+
name,
|
|
12
|
+
description,
|
|
13
|
+
accountModes: ["assistant"],
|
|
14
|
+
authenticationMethods: ["oauth2"],
|
|
15
|
+
scopes: confidenceScopes,
|
|
11
16
|
setup: { url: "https://confidence.spotify.com/docs/sdks/mcp-servers", clientRegistrationEndpoint: "https://mcp.confidence.dev/register", steps: [
|
|
12
17
|
"Use a Confidence account with access to the intended organisation. This assistant connection uses browser OAuth, not a flag client secret or management API token.",
|
|
13
18
|
"Confirm the suggested callback is the route your application will serve. Choose Register client and connect to create a project-owned client and save its ID, secret and callback through project Env. Existing Env values are preserved. Manual users can send the displayed registration JSON with Content-Type application/json; CLI users can call registerConfidenceClient.",
|
|
@@ -25,7 +25,7 @@ const dbtSemanticLayerDefinition = Object.freeze({
|
|
|
25
25
|
"Return to Project details and copy the GraphQL hostname and Environment ID from the Semantic Layer connection details. Enter them here, preserving every ID digit. Enter env:DBT_SERVICE_TOKEN in Service token reference and save configuration. Choose Set credential in Env, paste the token as DBT_SERVICE_TOKEN and save it there.",
|
|
26
26
|
"Return here and choose Connect account / Verify again. Check connection only reads saved status. Verification reads environment metadata; an explicitly wired application can then create metric queries and request their status and JSON result pages. If denied, check the host, environment, linked warehouse credential and token permissions. Replace the token through Env when rotating it; local Disconnect does not revoke it in dbt.",
|
|
27
27
|
"Query execution uses your warehouse capacity. The app owns permitted metrics, trusted dimension/time filters and bounded polling. JSON results suit small tables; use dbt’s native Arrow tooling for large production workloads. Do not retry an uncertain query submission automatically. Your application must explicitly expose approved dbt tools to its assistant.",
|
|
28
|
-
"This token flow has no OAuth callback or app-user login.
|
|
28
|
+
"This token flow has no OAuth callback or app-user login. Every deployment needs the customer's own account, permissions and capacity."
|
|
29
29
|
] }
|
|
30
30
|
});
|
|
31
31
|
|
package/src/shared/figma.js
CHANGED
|
@@ -12,7 +12,7 @@ const figmaDefinition = Object.freeze({
|
|
|
12
12
|
"Save, begin authorization, sign into Figma and review its consent screen. A personal access token cannot replace this MCP OAuth registration.",
|
|
13
13
|
"The host must authorize each tool and its arguments. The MCP permission can expose write tools; listing a tool does not authorize an edit.",
|
|
14
14
|
"Use a Figma file/frame link to identify the allowed file and node. The assistant host consumes returned design text/images and chooses its framework; connecting alone does not generate screens. Assistant tool attachment requires explicit host integration.",
|
|
15
|
-
"Remote MCP supports both local and hosted assistant applications. Desktop MCP must run beside the consuming desktop client; a
|
|
15
|
+
"Remote MCP supports both local and hosted assistant applications. Desktop MCP must run beside the consuming desktop client; a server's localhost address points to that server, not your laptop. Do not enter a desktop localhost URL as the hosted remote endpoint.",
|
|
16
16
|
"Disconnect removes the local grant; it does not undo design edits or revoke all provider access. This uses Figma's remote server. Desktop MCP, app-user login and automatic assistant attachment are separate capabilities."
|
|
17
17
|
]
|
|
18
18
|
}
|
package/src/shared/google.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
|
-
|
|
2
|
+
import { googleScope } from "./googleScope.js";
|
|
3
3
|
function definition(id, name, api, description, recommended, permissions, verificationFields = [], setupNotes = []) {
|
|
4
4
|
return Object.freeze({
|
|
5
|
-
id,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
id,
|
|
6
|
+
name,
|
|
7
|
+
description,
|
|
8
|
+
verificationFields,
|
|
9
|
+
accountModes: ["shared", "per-user", "assistant"],
|
|
10
|
+
authenticationMethods: ["oauth2"],
|
|
11
|
+
scopes: permissions.map(([value, label, required = false]) => ({ value: googleScope(value), label,
|
|
8
12
|
recommended: Array.isArray(recommended) ? recommended.includes(value) : value === recommended, ...(required ? { required: true } : {}) })),
|
|
9
13
|
setup: {
|
|
10
14
|
url: "https://developers.google.com/identity/protocols/oauth2/web-server",
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { UUID_SOURCE, isUuid } from "./uuid.js";
|
|
2
3
|
|
|
3
4
|
const delegatedScope = "https://analysis.windows.net/powerbi/api/GraphQLApi.Execute.All";
|
|
4
5
|
const serviceScope = "https://api.fabric.microsoft.com/.default";
|
|
5
|
-
const
|
|
6
|
-
const guidPattern = new RegExp(`^${guid}$`, "u");
|
|
7
|
-
const endpointPattern = new RegExp(`^https://api\\.fabric\\.microsoft\\.com/v1/workspaces/${guid}/(?:graphqlapis|graphQLApis|GraphQLApis)/${guid}/graphql$`, "u");
|
|
6
|
+
const endpointPattern = new RegExp(`^https://api\\.fabric\\.microsoft\\.com/v1/workspaces/${UUID_SOURCE}/(?:graphqlapis|graphQLApis|GraphQLApis)/${UUID_SOURCE}/graphql$`, "u");
|
|
8
7
|
const scopes = [
|
|
9
8
|
{ value: delegatedScope, label: "jskit.connectors.microsoftFabric.scopes.delegatedScope.label", recommended: true },
|
|
10
9
|
{ value: "offline_access", label: "jskit.connectors.microsoftFabric.scopes.offlineAccess.label", recommended: true },
|
|
@@ -16,11 +15,11 @@ const microsoftFabricDefinition = Object.freeze({
|
|
|
16
15
|
description: "jskit.connectors.microsoftFabric.description",
|
|
17
16
|
accountModes: ["shared", "per-user", "assistant"], authenticationMethods: ["oauth2"],
|
|
18
17
|
oauthGrantTypes: ["authorization_code", "client_credentials"], oauthClientAuthenticationMethods: ["client_secret_post"],
|
|
19
|
-
validateClientId: (value) =>
|
|
18
|
+
validateClientId: (value) => isUuid(value) || "Enter the Application (client) ID as a GUID.",
|
|
20
19
|
oauthGrantHint: "jskit.connectors.microsoftFabric.oauthGrantHint",
|
|
21
20
|
settingsSchema: createSchema({
|
|
22
21
|
tenantId: { type: "string", required: true, maxLength: 36,
|
|
23
|
-
validator: (value) =>
|
|
22
|
+
validator: (value) => isUuid(value) || "Enter the Directory (tenant) ID as a GUID, not common or a client ID." },
|
|
24
23
|
graphqlEndpoint: { type: "string", required: true, maxLength: 256,
|
|
25
24
|
validator: (value) => endpointPattern.test(value) || "Copy the Fabric GraphQL API endpoint with workspace and API IDs, without a query string or fragment." }
|
|
26
25
|
}),
|
package/src/shared/microsoft.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { isUuid } from "./uuid.js";
|
|
2
3
|
|
|
3
4
|
const registrationUrl = "https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app";
|
|
4
5
|
|
|
@@ -6,15 +7,19 @@ function definition(id, name, description, permission, label, organizationOnly =
|
|
|
6
7
|
const authority = organizationOnly ? "organizations" : "common";
|
|
7
8
|
const audiences = organizationOnly ? ["organizations"] : ["common", "organizations", "consumers"];
|
|
8
9
|
return Object.freeze({
|
|
9
|
-
id,
|
|
10
|
-
|
|
10
|
+
id,
|
|
11
|
+
name,
|
|
12
|
+
description,
|
|
13
|
+
verificationFields,
|
|
14
|
+
accountModes: ["shared", "per-user", "assistant"],
|
|
15
|
+
authenticationMethods: ["oauth2"],
|
|
11
16
|
scopes: [
|
|
12
17
|
{ value: permission, label, recommended: true },
|
|
13
18
|
{ value: "offline_access", label: "jskit.connectors.microsoft.scopes.offlineAccess.label", recommended: true }
|
|
14
19
|
],
|
|
15
20
|
settingsSchema: createSchema({
|
|
16
21
|
tenantId: { type: "string", maxLength: 36, defaultTo: authority,
|
|
17
|
-
validator: (value) => audiences.includes(value) ||
|
|
22
|
+
validator: (value) => audiences.includes(value) || isUuid(value) ||
|
|
18
23
|
`Use ${audiences.join(", ")} or your Directory (tenant) ID as a GUID.` }
|
|
19
24
|
}),
|
|
20
25
|
settingsFields: [{ name: "tenantId", label: "jskit.connectors.microsoft.fields.tenantId.label", placeholder: authority,
|
package/src/shared/salesforce.js
CHANGED
|
@@ -29,7 +29,7 @@ const salesforceDefinition = Object.freeze({
|
|
|
29
29
|
"Enable OAuth, register the exact backend callback, and select api plus refresh_token/offline_access. Keep PKCE enabled and require the secret for Web Server and Refresh Token flows.",
|
|
30
30
|
"Open the app's Settings, then Consumer Key and Secret. Copy the key as Client ID; put the secret and callback in Env and save their references here.",
|
|
31
31
|
"An administrator grants API access and authorizes the app/users through its policies and permission sets. Save configuration, then start consent from your application's connection flow.",
|
|
32
|
-
"
|
|
32
|
+
"Registrations shared beyond this org need separate provisioning and distribution decisions. Local apps cannot serve unrelated customer orgs; packaged apps require installation and subscriber policies. Org API quotas remain shared."
|
|
33
33
|
] }
|
|
34
34
|
});
|
|
35
35
|
|
package/src/shared/tokens.js
CHANGED
|
@@ -2,8 +2,13 @@ import { createSchema } from "json-rest-schema";
|
|
|
2
2
|
|
|
3
3
|
function definition(id, name, description, url, steps) {
|
|
4
4
|
return Object.freeze({
|
|
5
|
-
id,
|
|
6
|
-
|
|
5
|
+
id,
|
|
6
|
+
name,
|
|
7
|
+
description,
|
|
8
|
+
accountModes: ["shared", "assistant"],
|
|
9
|
+
authenticationMethods: ["api-key"],
|
|
10
|
+
scopes: [],
|
|
11
|
+
setup: { url, steps }
|
|
7
12
|
});
|
|
8
13
|
}
|
|
9
14
|
|
|
@@ -725,7 +730,6 @@ const clayDefinition = Object.freeze({
|
|
|
725
730
|
]),
|
|
726
731
|
apiKeyReferenceLabel: "jskit.connectors.clay.apiKeyReferenceLabel"
|
|
727
732
|
});
|
|
728
|
-
|
|
729
733
|
const tokenDefinitions = Object.freeze([
|
|
730
734
|
airtableDefinition, notionDefinition, brevoDefinition, elevenlabsDefinition, githubApiDefinition, apifyDefinition,
|
|
731
735
|
calendlyDefinition, hubspotDefinition, linearDefinition, pipedriveDefinition, gitlabApiDefinition, tallyDefinition, contentfulDefinition, asanaDefinition,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The UUID form providers give account, tenant, site and resource IDs.
|
|
2
|
+
|
|
3
|
+
/** A hyphenated hexadecimal UUID in either case, for building larger patterns. */
|
|
4
|
+
const UUID_SOURCE = "[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}";
|
|
5
|
+
const uuidPattern = new RegExp(`^${UUID_SOURCE}$`, "u");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A UUID in its hyphenated hexadecimal form.
|
|
9
|
+
* @param {unknown} value
|
|
10
|
+
*/
|
|
11
|
+
const isUuid = (value) => typeof value === "string" && uuidPattern.test(value);
|
|
12
|
+
|
|
13
|
+
export { UUID_SOURCE, isUuid };
|
package/src/shared/wix.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { isUuid } from "./uuid.js";
|
|
2
3
|
|
|
3
4
|
const wixDefinition = Object.freeze({
|
|
4
5
|
id: "wix", name: "Wix", description: "jskit.connectors.wix.description",
|
|
5
6
|
accountModes: ["shared", "assistant"], authenticationMethods: ["api-key"], scopes: [],
|
|
6
7
|
settingsSchema: createSchema({ accountId: { type: "string", required: true, minLength: 36, maxLength: 36,
|
|
7
|
-
validator: (value) =>
|
|
8
|
+
validator: (value) => isUuid(value) || "Enter the Account ID from Wix's API Keys Manager." },
|
|
8
9
|
selectableSiteIds: { type: "string", maxLength: 4000, validator: value => {
|
|
9
10
|
const ids = value.split(",").map(id => id.trim());
|
|
10
|
-
return ids.length <= 100 && ids.every(id =>
|
|
11
|
+
return ids.length <= 100 && ids.every(id => isUuid(id)) && new Set(ids.map(id => id.toLowerCase())).size === ids.length || "Enter up to 100 distinct Site IDs separated by commas.";
|
|
11
12
|
} },
|
|
12
|
-
siteId: { type: "string", minLength: 36, maxLength: 36, validator: value =>
|
|
13
|
+
siteId: { type: "string", minLength: 36, maxLength: 36, validator: value => isUuid(value) || "Copy the Site ID from the Wix site dashboard URL." } }),
|
|
13
14
|
settingsFields: [{ name: "accountId", label: "jskit.connectors.wix.fields.accountId.label", placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
14
15
|
hint: "jskit.connectors.wix.fields.accountId.hint" },
|
|
15
16
|
{ name: "selectableSiteIds", label: "jskit.connectors.wix.fields.selectableSiteIds.label", placeholder: "jskit.connectors.wix.fields.selectableSiteIds.placeholder", hint: "jskit.connectors.wix.fields.selectableSiteIds.hint" },
|
package/src/shared/zoho-books.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
|
-
import { zohoRegions } from "./zoho
|
|
2
|
+
import { isZohoId, zohoRegions } from "./zoho.js";
|
|
3
3
|
|
|
4
|
-
const zohoBooksIdentifier = (value) => typeof value === "string" && /^[1-9][0-9]{0,29}$/u.test(value);
|
|
5
4
|
const zohoBooksOrganizationField = { type: "string", noTrim: true,
|
|
6
|
-
validator: (value) =>
|
|
5
|
+
validator: (value) => isZohoId(value) || "Use an organisation ID containing digits only." };
|
|
7
6
|
|
|
8
7
|
const zohoBooksDefinition = Object.freeze({
|
|
9
8
|
id: "zoho-books", name: "Zoho Books", description: "jskit.connectors.zohoBooks.description",
|
|
@@ -36,4 +35,4 @@ const zohoBooksDefinition = Object.freeze({
|
|
|
36
35
|
] }
|
|
37
36
|
});
|
|
38
37
|
|
|
39
|
-
export { zohoBooksDefinition,
|
|
38
|
+
export { zohoBooksDefinition, zohoBooksOrganizationField };
|
package/src/shared/zoho-crm.js
CHANGED
|
@@ -9,4 +9,7 @@ const zohoRegions = Object.freeze([
|
|
|
9
9
|
{ value: "sa", title: "jskit.connectors.zoho.regions.sa.title", accounts: "https://accounts.zoho.sa", suffix: "sa" }
|
|
10
10
|
]);
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// Zoho's organisation and record IDs: up to 30 digits, without a leading zero.
|
|
13
|
+
const isZohoId = (value) => typeof value === "string" && /^[1-9][0-9]{0,29}$/u.test(value);
|
|
14
|
+
|
|
15
|
+
export { isZohoId, zohoRegions };
|
package/test/ai.test.js
CHANGED
|
@@ -9,7 +9,7 @@ import { parseIntegrationConfiguration, validateIntegrationConfiguration } from
|
|
|
9
9
|
const configuration = (patch = {}) => ({ schemaVersion: 1, registrations: {}, integrations: {
|
|
10
10
|
suggestions: { provider: "ai", accountMode: "shared", scopes: [], authentication: { method: "none" }, ...patch }
|
|
11
11
|
} });
|
|
12
|
-
const actor = { applicationId: "
|
|
12
|
+
const actor = { applicationId: "bookings", subjectId: "customer-one" };
|
|
13
13
|
const request = { context: actor, integrationId: "suggestions" };
|
|
14
14
|
const authorize = async (context) => context?.applicationId === actor.applicationId ? context : null;
|
|
15
15
|
const validate = (config) => validateIntegrationConfiguration(config, { providers: [aiDefinition] });
|