lucid-extension-sdk 0.0.107 → 0.0.108

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lucid-extension-sdk",
3
- "version": "0.0.107",
3
+ "version": "0.0.108",
4
4
  "description": "Utility classes for writing Lucid Software editor extensions",
5
5
  "main": "sdk/index.js",
6
6
  "types": "sdk/index.d.ts",
@@ -48,16 +48,16 @@ function serializePatchChanges(changes) {
48
48
  const joinedErrors = {};
49
49
  for (const changeGroup of changes) {
50
50
  for (const change of changeGroup) {
51
- if (!(change.patchId in joinedErrors)) {
52
- joinedErrors[change.patchId] = {};
53
- }
54
- const patchErrors = joinedErrors[change.patchId];
55
51
  for (const [primaryKey, fieldNames] of Object.entries(change.getErrors())) {
56
- if (!(primaryKey in patchErrors)) {
57
- patchErrors[primaryKey] = {};
58
- }
59
- const primaryKeyErrors = patchErrors[primaryKey];
60
52
  for (const [fieldName, error] of Object.entries(fieldNames)) {
53
+ if (!(change.patchId in joinedErrors)) {
54
+ joinedErrors[change.patchId] = {};
55
+ }
56
+ const patchErrors = joinedErrors[change.patchId];
57
+ if (!(primaryKey in patchErrors)) {
58
+ patchErrors[primaryKey] = {};
59
+ }
60
+ const primaryKeyErrors = patchErrors[primaryKey];
61
61
  primaryKeyErrors[fieldName] = error;
62
62
  }
63
63
  }
@@ -9,11 +9,11 @@ exports.actionsHeaderValidator = (0, validators_1.objectValidator)({ 'x-lucid-rs
9
9
  const serializedActionsBaseValidator = (0, validators_1.objectValidator)({
10
10
  'action': (0, validators_1.objectValidator)({ 'name': checks_1.isString, 'data': checks_1.isUnknown }),
11
11
  'packageId': checks_1.isString,
12
- 'packageVersion': (0, validators_1.option)(checks_1.isString),
12
+ 'packageVersion': (0, validators_1.nullableOption)(checks_1.isString),
13
13
  'dataConnectorName': checks_1.isString,
14
- 'installationId': (0, validators_1.option)(checks_1.isString),
15
- 'userCredential': (0, validators_1.option)(checks_1.isString),
16
- 'documentUpdateToken': (0, validators_1.option)(checks_1.isString),
14
+ 'installationId': (0, validators_1.nullableOption)(checks_1.isString),
15
+ 'userCredential': (0, validators_1.nullableOption)(checks_1.isString),
16
+ 'documentUpdateToken': (0, validators_1.nullableOption)(checks_1.isString),
17
17
  'documentCollections': (0, validators_1.objectOfValidator)((0, validators_1.arrayValidator)(checks_1.isString)),
18
18
  'updateFilterType': (0, validators_1.enumValidator)(dataupdatefiltertype_1.DataUpdateFilterType),
19
19
  });
@@ -36,7 +36,6 @@ const serializedPatchDataValidator = (0, validators_1.arrayValidator)((0, valida
36
36
  const serializedManageWebhookDataValidator = (0, validators_1.objectValidator)({
37
37
  'webhooks': (0, validators_1.arrayValidator)((0, validators_1.objectValidator)({
38
38
  'documentCollections': (0, validators_1.objectOfValidator)((0, validators_1.arrayValidator)(checks_1.isString)),
39
- 'updateFilterType': (0, validators_1.enumValidator)(dataupdatefiltertype_1.DataUpdateFilterType),
40
39
  'webhookData': (0, validators_1.option)(checks_1.isUnknown),
41
40
  })),
42
41
  'webhookToken': checks_1.isString,
@@ -43,62 +43,62 @@ class DataConnector {
43
43
  /** Call a defined action handler and gather its serialized response */
44
44
  async runAction(url, headers, body) {
45
45
  const actions = await this.client.parseActions(url, headers, body);
46
- if (actions) {
47
- try {
48
- const responses = [];
49
- for (const action of actions) {
50
- if (!(action.name in this.actions)) {
51
- throw DataConnectorRunError.withMessage(404, `Action ${action.name} is not implemented`);
52
- }
53
- const { actionResponder, asynchronous } = this.actions[action.name];
54
- const response = (() => {
55
- if (asynchronous) {
56
- if (!(action instanceof action_1.DataConnectorAsynchronousAction)) {
57
- throw DataConnectorRunError.withMessage(400, `Passing uncredentialed action to ${action.name} which is defined to be asynchronous`);
58
- }
59
- return actionResponder(action);
60
- }
61
- else {
62
- return actionResponder(action);
63
- }
64
- })();
65
- responses.push(await response);
66
- }
67
- if (actions[0].name === exports.DataConnectorActionKeys.ManageWebhook) {
68
- const response = responses[0];
69
- // leave the type on this variable because our return type isn't strict. This means that
70
- // changes to serializeManageWebhookResponse's return type will need to consider this point
71
- const body = (0, managewebhookresponsebody_1.serializeManageWebhookResponse)(response);
72
- return { status: 200, body };
73
- }
74
- if (actions[0].name === exports.DataConnectorActionKeys.Patch) {
75
- const responsesCasted = responses;
76
- // leave the type on this variable because our return type isn't strict. This means that
77
- // changes to serializePatchChanges's return type will need to consider this point
78
- const body = (0, patchresponsebody_1.serializePatchChanges)(responsesCasted);
79
- return { status: 200, body };
80
- }
81
- else if (responses.length === 1) {
82
- return { status: 200, body: responses[0] };
83
- }
84
- else {
85
- return { status: 200, body: responses };
46
+ if (!actions) {
47
+ console.warn('Received bad request', url, headers, body);
48
+ return { status: 404, body: { error: 'Bad request' } };
49
+ }
50
+ const actionName = actions[0].name;
51
+ try {
52
+ const responses = [];
53
+ for (const action of actions) {
54
+ if (!(action.name in this.actions)) {
55
+ throw DataConnectorRunError.withMessage(404, `Action ${action.name} is not implemented`);
86
56
  }
57
+ const { actionResponder, asynchronous } = this.actions[action.name];
58
+ const response = (() => {
59
+ if (asynchronous) {
60
+ if (!(action instanceof action_1.DataConnectorAsynchronousAction)) {
61
+ throw DataConnectorRunError.withMessage(400, `Passing uncredentialed action to ${action.name} which is defined to be asynchronous`);
62
+ }
63
+ return actionResponder(action);
64
+ }
65
+ else {
66
+ return actionResponder(action);
67
+ }
68
+ })();
69
+ responses.push(await response);
87
70
  }
88
- catch (e) {
89
- const errorLogger = console.error ? console.error : console.log;
90
- if (e instanceof DataConnectorRunError) {
91
- errorLogger('Error running action', e);
92
- return { status: e.status, body: e.body };
93
- }
94
- else {
95
- errorLogger('Error running action', e);
96
- return { status: 500, body: { error: `error running action ${actions[0].name}` } };
97
- }
71
+ if (actionName === exports.DataConnectorActionKeys.ManageWebhook) {
72
+ const response = responses[0];
73
+ // leave the type on this variable because our return type isn't strict. This means that
74
+ // changes to serializeManageWebhookResponse's return type will need to consider this point
75
+ const body = (0, managewebhookresponsebody_1.serializeManageWebhookResponse)(response);
76
+ return { status: 200, body };
77
+ }
78
+ if (actionName === exports.DataConnectorActionKeys.Patch) {
79
+ const responsesCasted = responses;
80
+ // leave the type on this variable because our return type isn't strict. This means that
81
+ // changes to serializePatchChanges's return type will need to consider this point
82
+ const body = (0, patchresponsebody_1.serializePatchChanges)(responsesCasted);
83
+ return { status: 200, body };
84
+ }
85
+ else if (responses.length === 1) {
86
+ return { status: 200, body: responses[0] };
87
+ }
88
+ else {
89
+ return { status: 200, body: responses };
98
90
  }
99
91
  }
100
- else {
101
- return { status: 404, body: { error: 'Bad request' } };
92
+ catch (e) {
93
+ const errorLogger = console.error ? console.error : console.log;
94
+ if (e instanceof DataConnectorRunError) {
95
+ errorLogger(`Error running ${actionName} action`, e);
96
+ return { status: e.status, body: e.body };
97
+ }
98
+ else {
99
+ errorLogger(`Error running ${actionName} action`, e);
100
+ return { status: 500, body: { error: `error running ${actionName} action` } };
101
+ }
102
102
  }
103
103
  }
104
104
  /** Add a route to the given express app (or generate a new app) to serve this data connector */
@@ -1,6 +1,14 @@
1
+ import { JsonObject } from '../core/jsonserializable';
1
2
  import { DataConnectorAction } from './actions/action';
2
3
  import { DataSourceClient, MockDataSourceClient } from './datasourceclient';
3
4
  import { SignatureValidator } from './signaturevalidator';
5
+ /** @ignore */
6
+ export declare type WebhookValidationSecret = {
7
+ 'oauth2': {
8
+ 'clientId': string;
9
+ 'clientSecret': string;
10
+ };
11
+ };
4
12
  /**
5
13
  * Client for requests to and from Lucid
6
14
  */
@@ -24,6 +32,15 @@ export declare class DataConnectorClient {
24
32
  getPublicKey(): Promise<string>;
25
33
  /** Validate and parse an action request from Lucid */
26
34
  parseActions(url: string, headers: Record<string, string | string[] | unknown>, actions: unknown): Promise<DataConnectorAction[] | undefined>;
35
+ /** @ignore */
36
+ getSecretFromWebhook(webhookUpdateToken: string, WebhookValidationSecret: WebhookValidationSecret): Promise<string | undefined>;
37
+ /** @ignore */
38
+ getCustomWebhookData(webhookUpdateToken: string): Promise<JsonObject>;
39
+ /** @ignore */
40
+ patchCustomWebhookData(webhookUpdateToken: string, patch: JsonObject): Promise<{
41
+ data: JsonObject;
42
+ status: number;
43
+ }>;
27
44
  }
28
45
  /** Use this for writing automated tests for your data connector */
29
46
  export declare class MockDataConnectorClient extends DataConnectorClient {
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MockDataConnectorClient = exports.DataConnectorClient = void 0;
4
+ const checks_1 = require("../core/checks");
4
5
  const serializedactions_1 = require("./actions/serializedactions");
6
+ const dataconnector_1 = require("./dataconnector");
5
7
  const datasourceclient_1 = require("./datasourceclient");
6
8
  const defaultfetchfunction_1 = require("./defaultfetchfunction");
7
9
  const signaturevalidator_1 = require("./signaturevalidator");
@@ -29,6 +31,9 @@ class DataConnectorClient {
29
31
  if (!this.cachedPublicKey) {
30
32
  const url = `${this.urls.main}.well-known/pem/TPCP`;
31
33
  const result = await this.fetchMethod(url);
34
+ if (Math.floor(result.status / 100) !== 2) {
35
+ throw new dataconnector_1.DataConnectorRunError(500, { text: await result.text(), message: 'Error getting public key' });
36
+ }
32
37
  this.cachedPublicKey = await result.text();
33
38
  }
34
39
  return this.cachedPublicKey;
@@ -43,6 +48,40 @@ class DataConnectorClient {
43
48
  }
44
49
  return (0, serializedactions_1.deserializeActions)(this, actions);
45
50
  }
51
+ /** @ignore */
52
+ async getSecretFromWebhook(webhookUpdateToken, WebhookValidationSecret) {
53
+ // todo: this should be using the public API
54
+ const result = await this.fetchMethod(`${this.urls.dataSync}/webhookAuthorization`, {
55
+ headers: {
56
+ 'data-update-token': webhookUpdateToken,
57
+ 'webhook-validation-secret': JSON.stringify(WebhookValidationSecret),
58
+ },
59
+ });
60
+ const responseBody = await result.json();
61
+ if ((0, checks_1.isString)(responseBody)) {
62
+ return responseBody;
63
+ }
64
+ else {
65
+ return undefined;
66
+ }
67
+ }
68
+ /** @ignore */
69
+ async getCustomWebhookData(webhookUpdateToken) {
70
+ return (await // todo: this should be using the public API
71
+ (await this.fetchMethod(`${this.urls.dataSync}customWebhookData`, {
72
+ headers: { 'data-update-token': webhookUpdateToken },
73
+ })).json());
74
+ }
75
+ /** @ignore */
76
+ async patchCustomWebhookData(webhookUpdateToken, patch) {
77
+ // todo: this should be using the public API
78
+ const response = await this.fetchMethod(`${this.urls.dataSync}customWebhookData`, {
79
+ method: 'PATCH',
80
+ headers: { 'data-update-token': webhookUpdateToken },
81
+ body: patch,
82
+ });
83
+ return { status: response.status, data: (await response.json()) };
84
+ }
46
85
  }
47
86
  exports.DataConnectorClient = DataConnectorClient;
48
87
  /** Use this for writing automated tests for your data connector */
@@ -16,11 +16,6 @@ export declare type DataSourceRequest = {
16
16
  /** Collections to add or update to the data source */
17
17
  collections: Record<string, CollectionPatch>;
18
18
  };
19
- /** A response to a data source update. Basically just the HTTP response from the server with JSON.parse called on it */
20
- export declare type DataSourceResponse = {
21
- data: unknown;
22
- status: number;
23
- };
24
19
  declare type SerializedFieldConstraintForApi = {
25
20
  'type': FieldConstraintType;
26
21
  'details'?: JsonSerializable;
@@ -73,7 +68,7 @@ export declare class DataSourceClient {
73
68
  /** Create or update a datasource. If you create a new collection it must be fully specified in terms of schema and
74
69
  * and items. If it's just an update then the schema can be omitted (if unchanged) and items that already exist can
75
70
  * be partial.*/
76
- update(request: DataSourceRequest): Promise<DataSourceResponse>;
71
+ update(request: DataSourceRequest): Promise<void>;
77
72
  }
78
73
  /**
79
74
  * A version of the DataSourceClient that you can use for any automated tests of your data connector.
@@ -81,9 +76,9 @@ export declare class DataSourceClient {
81
76
  **/
82
77
  export declare class MockDataSourceClient extends DataSourceClient {
83
78
  /** Assign this to your mocked update function */
84
- gotUpdate: (request: DataSourceRequest) => DataSourceResponse;
79
+ gotUpdate: (request: DataSourceRequest) => void;
85
80
  constructor();
86
81
  /** @ignore */
87
- update(request: DataSourceRequest): Promise<DataSourceResponse>;
82
+ update(request: DataSourceRequest): Promise<void>;
88
83
  }
89
84
  export {};
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MockDataSourceClient = exports.DataSourceClient = exports.serializeFieldDefinitionForApi = void 0;
4
4
  const fieldtypedefinition_1 = require("../core/data/fieldtypedefinition/fieldtypedefinition");
5
5
  const object_1 = require("../core/object");
6
+ const dataconnector_1 = require("./dataconnector");
6
7
  const defaultfetchfunction_1 = require("./defaultfetchfunction");
7
8
  function serializeFieldConstraintForApi(constraint) {
8
9
  return {
@@ -82,7 +83,18 @@ class DataSourceClient {
82
83
  },
83
84
  'body': JSON.stringify(this.formatBody(request)),
84
85
  });
85
- return { status: response.status, data: await response.json() };
86
+ if (Math.floor(response.status / 100) !== 2) {
87
+ throw new dataconnector_1.DataConnectorRunError(500, { message: 'Error updating data source', json: await response.text() });
88
+ }
89
+ // webhooks return 200 with an empty body
90
+ // document updates return valid json
91
+ // we can't use .json because that will fail for webhooks
92
+ // if we update the response type for webhook updates
93
+ // we can handle that here too. We should probably unify the responses
94
+ // to be meaningful
95
+ // we could just:
96
+ // return await response.text();
97
+ // but lets avoid users trying to interpret the response
86
98
  }
87
99
  }
88
100
  exports.DataSourceClient = DataSourceClient;
@@ -48,7 +48,7 @@ function routeDebugServer(dataConnector, options) {
48
48
  }
49
49
  }
50
50
  catch (e) {
51
- console.log(`Error serving Connector: ${prefix} Flow: ${req.query['name']}, Exception:` + e);
51
+ console.log(`Error serving Connector: ${prefix} Flow: ${req.query['name']}, Exception:`, e);
52
52
  res.status(500).end();
53
53
  }
54
54
  });