@jskit-ai/connectors-web 0.1.61 → 0.1.63

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 CHANGED
@@ -22,6 +22,7 @@ preserve other integration slots, registrations and extension values.
22
22
 
23
23
  Provider `settingsFields` metadata supplies labelled select controls when `items`
24
24
  are provided and text controls otherwise, with optional hints and placeholders.
25
+ A hint may be text or a function of the current settings.
25
26
  Defaults and applicable fields come from
26
27
  `getProviderSettingsSchema(provider, settings).getFieldDefinitions()`, which also
27
28
  supports schemas containing custom validators. A provider may select a schema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/connectors-web",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
4
4
  "description": "Shared Vue fields for portable application integration configuration.",
5
5
  "type": "module",
6
6
  "files": [
@@ -10,7 +10,7 @@
10
10
  "./client": "./src/client/index.js"
11
11
  },
12
12
  "dependencies": {
13
- "@jskit-ai/connectors-core": "0.1.61"
13
+ "@jskit-ai/connectors-core": "0.1.63"
14
14
  },
15
15
  "peerDependencies": {
16
16
  "vue": "^3.5.13",
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { computed, ref } from "vue";
3
- import { getProviderAccountModes, getProviderAuthenticationMethods, getProviderClientAuthenticationMethods, getProviderScopes, getProviderSettingsSchema } from "@jskit-ai/connectors-core/shared/configuration";
3
+ import { DEFAULT_GRANT_TYPE, DEFAULT_TOKEN_ENDPOINT_AUTH_METHOD, getProviderAccountModes, getProviderAuthenticationMethods, getProviderClientAuthenticationMethods, getProviderScopes, getProviderSettingsSchema, providerGrantTypes, registrationClientAuthentication, registrationGrantType } from "@jskit-ai/connectors-core/shared/configuration";
4
4
 
5
5
  const props = defineProps({
6
6
  modelValue: { type: Object, required: true },
@@ -15,8 +15,8 @@ const panels = ref(["details", "credentials"]);
15
15
  const integration = computed(() => props.modelValue.integrations[props.integrationId]);
16
16
  const registrationId = computed(() => integration.value?.authentication.registrationRef);
17
17
  const registration = computed(() => props.modelValue.registrations[registrationId.value]);
18
- const grantType = computed(() => registration.value?.grantType || "authorization_code");
19
- const grantOptions = computed(() => (props.provider.oauthGrantTypes || ["authorization_code"]).map((value) => ({
18
+ const grantType = computed(() => registrationGrantType(registration.value));
19
+ const grantOptions = computed(() => providerGrantTypes(props.provider).map((value) => ({
20
20
  value, title: { authorization_code: "User consent", client_credentials: "Service account" }[value] || value
21
21
  })));
22
22
  const modes = computed(() => getProviderAccountModes(props.provider, integration.value.settings || {}).filter((mode) =>
@@ -37,10 +37,35 @@ const visibleSettings = computed(() => (props.provider.settingsFields || []).fil
37
37
  (!field.authenticationMethods || field.authenticationMethods.includes(integration.value.authentication.method))
38
38
  ));
39
39
  const visibleScopes = computed(() => getProviderScopes(props.provider, integration.value.settings || {}, grantType.value, integration.value.authentication.method));
40
+ const clientSecretHint = computed(() =>
41
+ `Use an environment reference such as env:${environmentPrefix(registrationId.value || props.integrationId)}_CLIENT_SECRET. Keep the value outside this file.`);
40
42
  const assistantPermissions = [
41
43
  { value: "ask", title: "Ask each time" }, { value: "always", title: "Always allow" }, { value: "never", title: "Never allow" }
42
44
  ];
43
45
 
46
+ // The environment-variable prefix editAuthentication uses for a new registration's references.
47
+ function environmentPrefix(id) {
48
+ return id.toUpperCase().replaceAll("-", "_");
49
+ }
50
+
51
+ // A service account cannot connect as each app user; fall back to a mode these settings allow.
52
+ function accountModeForGrant(grant, settings, accountMode) {
53
+ return grant === "client_credentials" && accountMode === "per-user"
54
+ ? getProviderAccountModes(props.provider, settings || {}).find((mode) => mode !== "per-user") : accountMode;
55
+ }
56
+
57
+ function settingValue(field) {
58
+ return integration.value.settings?.[field.name] ?? settingProperties.value[field.name]?.defaultTo;
59
+ }
60
+
61
+ function settingHint(field) {
62
+ return typeof field.hint === "function" ? field.hint(integration.value.settings) : field.hint;
63
+ }
64
+
65
+ function settingErrors(field) {
66
+ return props.fieldErrors[`${integrationPath.value}.settings.${field.name}`];
67
+ }
68
+
44
69
  function editAssistantPolicy(changes) {
45
70
  editIntegration({ assistantPolicy: { enabled: true, defaultPermission: "ask", actions: {}, ...integration.value.assistantPolicy, ...changes } });
46
71
  }
@@ -91,25 +116,26 @@ function editAuthentication(method, current = integration.value) {
91
116
  for (const field of props.provider.settingsFields || []) {
92
117
  if (field.authenticationMethods && !field.authenticationMethods.includes(method)) delete settings[field.name];
93
118
  }
94
- const availableScopes = getProviderScopes(props.provider, settings, "authorization_code", method);
119
+ const grant = method === "oauth2" ? props.provider.oauthGrantTypes?.[0] || DEFAULT_GRANT_TYPE : DEFAULT_GRANT_TYPE;
120
+ const availableScopes = getProviderScopes(props.provider, settings, grant, method);
95
121
  const scopes = availableScopes.filter(scope => current.scopes.includes(scope.value) || scope.required || (scope.authenticationMethods && scope.recommended)).map(scope => scope.value);
96
122
  if (method === "oauth2") {
97
123
  let id = props.integrationId;
98
124
  let suffix = 2;
99
125
  while (Object.hasOwn(props.modelValue.registrations, id)) id = `${props.integrationId}-${suffix++}`;
100
- const prefix = id.toUpperCase().replaceAll("-", "_");
101
- const grant = props.provider.oauthGrantTypes?.[0] || "authorization_code";
126
+ const prefix = environmentPrefix(id);
102
127
  const clientAuthentication = getProviderClientAuthenticationMethods(props.provider, grant, settings)[0];
103
128
  emit("update:modelValue", {
104
129
  ...props.modelValue,
105
130
  integrations: { ...props.modelValue.integrations, [props.integrationId]: {
106
131
  ...current, scopes, authentication: { method, registrationRef: id },
132
+ accountMode: accountModeForGrant(grant, settings, current.accountMode),
107
133
  ...(current.settings ? { settings } : {})
108
134
  } },
109
135
  registrations: { ...props.modelValue.registrations, [id]: {
110
136
  source: "own", clientId: "",
111
137
  ...(grant === "authorization_code" ? { callbackUrlRef: `env:${prefix}_CALLBACK_URL` } : { grantType: grant }),
112
- ...(clientAuthentication !== "client_secret_post" ? { tokenEndpointAuthMethod: clientAuthentication } : {}),
138
+ ...(clientAuthentication !== DEFAULT_TOKEN_ENDPOINT_AUTH_METHOD ? { tokenEndpointAuthMethod: clientAuthentication } : {}),
113
139
  ...(clientAuthentication !== "none" ? { clientSecretRef: `env:${prefix}_CLIENT_SECRET` } : {})
114
140
  } }
115
141
  });
@@ -137,7 +163,7 @@ function editRegistration(field, value) {
137
163
  function editGrantType(value) {
138
164
  const nextRegistration = { ...registration.value, grantType: value };
139
165
  const methods = getProviderClientAuthenticationMethods(props.provider, value, integration.value.settings || {});
140
- if (!methods.includes(nextRegistration.tokenEndpointAuthMethod || "client_secret_post")) nextRegistration.tokenEndpointAuthMethod = methods[0];
166
+ if (!methods.includes(registrationClientAuthentication(nextRegistration))) nextRegistration.tokenEndpointAuthMethod = methods[0];
141
167
  if (nextRegistration.tokenEndpointAuthMethod === "none") delete nextRegistration.clientSecretRef;
142
168
  if (value === "client_credentials") delete nextRegistration.callbackUrlRef;
143
169
  const allowed = getProviderScopes(props.provider, integration.value.settings || {}, value, integration.value.authentication.method).map((scope) => scope.value);
@@ -146,8 +172,7 @@ function editGrantType(value) {
146
172
  registrations: { ...props.modelValue.registrations, [registrationId.value]: nextRegistration },
147
173
  integrations: { ...props.modelValue.integrations, [props.integrationId]: {
148
174
  ...integration.value,
149
- accountMode: value === "client_credentials" && integration.value.accountMode === "per-user"
150
- ? props.provider.accountModes.find((mode) => mode !== "per-user") : integration.value.accountMode,
175
+ accountMode: accountModeForGrant(value, integration.value.settings, integration.value.accountMode),
151
176
  scopes: integration.value.scopes.filter((scope) => allowed.includes(scope))
152
177
  } }
153
178
  });
@@ -185,22 +210,22 @@ function editGrantType(value) {
185
210
  <v-autocomplete
186
211
  v-if="field.searchable"
187
212
  :label="field.label" :items="field.items" :disabled="disabled" auto-select-first
188
- :model-value="integration.settings?.[field.name] ?? settingProperties[field.name]?.defaultTo"
189
- :hint="typeof field.hint === 'function' ? field.hint(integration.settings) : field.hint" persistent-hint :error-messages="fieldErrors[`${integrationPath}.settings.${field.name}`]"
213
+ :model-value="settingValue(field)"
214
+ :hint="settingHint(field)" persistent-hint :error-messages="settingErrors(field)"
190
215
  @update:model-value="editSetting(field.name, $event)"
191
216
  />
192
217
  <v-select
193
218
  v-else-if="field.items"
194
219
  :label="field.label" :items="field.items" :disabled="disabled"
195
- :model-value="integration.settings?.[field.name] ?? settingProperties[field.name]?.defaultTo"
196
- :hint="field.hint" persistent-hint :error-messages="fieldErrors[`${integrationPath}.settings.${field.name}`]"
220
+ :model-value="settingValue(field)"
221
+ :hint="settingHint(field)" persistent-hint :error-messages="settingErrors(field)"
197
222
  @update:model-value="editSetting(field.name, $event)"
198
223
  />
199
224
  <v-text-field
200
225
  v-else
201
226
  :label="field.label" :disabled="disabled" :placeholder="field.placeholder"
202
- :model-value="integration.settings?.[field.name] ?? settingProperties[field.name]?.defaultTo ?? ''"
203
- :hint="field.hint" persistent-hint :error-messages="fieldErrors[`${integrationPath}.settings.${field.name}`]"
227
+ :model-value="settingValue(field) ?? ''"
228
+ :hint="settingHint(field)" persistent-hint :error-messages="settingErrors(field)"
204
229
  @update:model-value="editSetting(field.name, $event)"
205
230
  />
206
231
  </template>
@@ -236,7 +261,7 @@ function editGrantType(value) {
236
261
  <slot v-if="registration.tokenEndpointAuthMethod !== 'none'" name="credential" :reference="registration.clientSecretRef" :disabled="disabled">
237
262
  <v-text-field
238
263
  label="Client secret reference" :model-value="registration.clientSecretRef" :disabled="disabled"
239
- hint="Use an environment reference such as env:GOOGLE_CLIENT_SECRET. Keep the value outside this file."
264
+ :hint="clientSecretHint"
240
265
  persistent-hint :error-messages="fieldErrors[`${registrationPath}.clientSecretRef`]"
241
266
  @update:model-value="editRegistration('clientSecretRef', $event)"
242
267
  />