@lowdefy/api 5.6.0 → 6.1.0

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.
Files changed (72) hide show
  1. package/dist/context/createAuthorize.js +4 -1
  2. package/dist/context/createEvaluateOperators.js +3 -1
  3. package/dist/context/getCurrentEnvironment.js +27 -0
  4. package/dist/context/resolveStrategyCaller.js +56 -0
  5. package/dist/index.js +13 -2
  6. package/dist/response/buildEndpointResult.js +3 -1
  7. package/dist/response/createWireProjection.js +88 -0
  8. package/dist/response/normalizeErrorSources.js +10 -13
  9. package/dist/response/redactErrorResponse.js +42 -8
  10. package/dist/response/redactResponse.js +10 -10
  11. package/dist/routes/agent/callAgent.js +7 -184
  12. package/dist/routes/agent/prepareAgent.js +179 -0
  13. package/dist/routes/auth/createLogger.js +2 -5
  14. package/dist/routes/auth/createPrefixedCookies.js +52 -0
  15. package/dist/routes/auth/defaultAuthUrl.js +31 -0
  16. package/dist/routes/auth/{getNextAuthConfig.js → getAuthConfig.js} +31 -16
  17. package/dist/routes/auth/resolveCookies.js +37 -0
  18. package/dist/routes/auth/strategies/createAuthStrategies.js +77 -0
  19. package/dist/routes/auth/strategies/getAuthStrategies.js +31 -0
  20. package/dist/routes/endpoints/authorizeApiEndpoint.js +8 -2
  21. package/dist/routes/endpoints/callEndpoint.js +27 -3
  22. package/dist/routes/endpoints/control/controlFor.js +4 -7
  23. package/dist/routes/endpoints/control/controlIf.js +4 -8
  24. package/dist/routes/endpoints/control/controlLog.js +6 -14
  25. package/dist/routes/endpoints/control/controlParallelFor.js +4 -7
  26. package/dist/routes/endpoints/control/controlReject.js +16 -16
  27. package/dist/routes/endpoints/control/controlReturn.js +4 -9
  28. package/dist/routes/endpoints/control/controlSetState.js +4 -8
  29. package/dist/routes/endpoints/control/controlSwitch.js +4 -8
  30. package/dist/routes/endpoints/control/controlThrow.js +21 -20
  31. package/dist/routes/endpoints/control/controlTry.js +20 -10
  32. package/dist/routes/endpoints/evaluateRoutineOperators.js +26 -0
  33. package/dist/routes/endpoints/findSchedule.js +35 -0
  34. package/dist/routes/endpoints/forwardScheduledEndpoint.js +111 -0
  35. package/dist/routes/endpoints/getEndpointConfig.js +9 -3
  36. package/dist/routes/endpoints/getEnvironmentSchedules.js +27 -0
  37. package/dist/routes/endpoints/handleAgentCall.js +80 -0
  38. package/dist/routes/endpoints/handleEndpointCall.js +53 -8
  39. package/dist/routes/endpoints/handleRenderNotification.js +196 -0
  40. package/dist/routes/endpoints/handleRequest.js +1 -5
  41. package/dist/routes/endpoints/handleValidateSchema.js +14 -10
  42. package/dist/routes/endpoints/invokeEndpoint.js +1 -0
  43. package/dist/routes/endpoints/isUnauthenticatedHuman.js +31 -0
  44. package/dist/routes/endpoints/operatorScope.js +27 -0
  45. package/dist/routes/endpoints/resolveCronEnvironment.js +38 -0
  46. package/dist/routes/endpoints/runDetachedEndpoint.js +67 -0
  47. package/dist/routes/endpoints/runRoutine.js +12 -0
  48. package/dist/routes/endpoints/runScheduledEndpoint.js +105 -0
  49. package/dist/routes/endpoints/runWebhookEndpoint.js +84 -0
  50. package/dist/routes/endpoints/scheduleBackground.js +48 -0
  51. package/dist/routes/endpoints/test/runTest.js +17 -2
  52. package/dist/routes/mcp/createMcpServer.js +178 -0
  53. package/dist/routes/notifications/derivePreview.js +30 -0
  54. package/dist/routes/notifications/getNotificationConfig.js +32 -0
  55. package/dist/routes/notifications/resolveNotificationLinks.js +72 -0
  56. package/dist/routes/notifications/resolveThemeLogo.js +35 -0
  57. package/dist/routes/page/dynamic/resolveDynamicContent.js +172 -0
  58. package/dist/routes/page/dynamic/unescapeOperators.js +36 -0
  59. package/dist/routes/page/dynamic/validateFragment.js +104 -0
  60. package/dist/routes/page/getPageConfig.js +16 -6
  61. package/dist/routes/request/callRequest.js +9 -3
  62. package/dist/routes/request/callRequestResolver.js +6 -0
  63. package/dist/routes/request/evaluateOperators.js +8 -12
  64. package/dist/routes/websocket/authorizeWebsocket.js +27 -0
  65. package/dist/routes/websocket/createChannelRegistry.js +283 -0
  66. package/dist/routes/websocket/createWebSocketConnection.js +143 -0
  67. package/dist/routes/websocket/getWebsocketConfig.js +30 -0
  68. package/dist/routes/websocket/getWebsocketResolver.js +33 -0
  69. package/dist/routes/websocket/prepareChannel.js +72 -0
  70. package/dist/test/testContext.js +7 -3
  71. package/package.json +12 -10
  72. package/dist/response/omitErrorProps.js +0 -51
@@ -0,0 +1,172 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
17
+ import createEvaluateOperators from '../../../context/createEvaluateOperators.js';
18
+ import invokeEndpoint from '../../endpoints/invokeEndpoint.js';
19
+ import unescapeOperators from './unescapeOperators.js';
20
+ import validateFragment from './validateFragment.js';
21
+ const MAX_DYNAMIC_DEPTH = 5;
22
+ function collectDynamicBlocks(block, found) {
23
+ if (block.type === 'Dynamic') {
24
+ // A Dynamic block's content comes from resolution; its fallback slot is
25
+ // resolved separately when a failure activates it.
26
+ found.push(block);
27
+ return;
28
+ }
29
+ Object.values(block.slots ?? {}).forEach((slot)=>{
30
+ (slot.blocks ?? []).forEach((child)=>collectDynamicBlocks(child, found));
31
+ });
32
+ }
33
+ function setResolvedContent(block, blocks) {
34
+ if (!block.slots) {
35
+ block.slots = {};
36
+ }
37
+ block.slots.content = {
38
+ ...block.slots.content ?? {},
39
+ blocks
40
+ };
41
+ delete block.slots.fallback;
42
+ delete block.properties.endpointId;
43
+ delete block.properties.params;
44
+ delete block.properties.required;
45
+ delete block.properties.types;
46
+ }
47
+ async function resolveBlocks(context, { blocks, depth, shared }) {
48
+ const found = [];
49
+ blocks.forEach((block)=>collectDynamicBlocks(block, found));
50
+ await Promise.all(found.map((block)=>resolveDynamicBlock(context, {
51
+ block,
52
+ depth,
53
+ shared
54
+ })));
55
+ }
56
+ async function resolveDynamicBlock(context, { block, depth, shared }) {
57
+ const { logger } = context;
58
+ const { endpointId, params, required } = block.properties;
59
+ try {
60
+ if (depth >= MAX_DYNAMIC_DEPTH) {
61
+ throw new ConfigError(`Dynamic block "${block.blockId}" on page "${shared.pageId}" exceeded the maximum dynamic nesting depth of ${MAX_DYNAMIC_DEPTH}.`, {
62
+ configKey: block['~k']
63
+ });
64
+ }
65
+ const { error, response, status } = await invokeEndpoint(context, {
66
+ endpointId,
67
+ payload: {
68
+ blockId: block.blockId,
69
+ pageId: shared.pageId,
70
+ params: params ?? {},
71
+ urlQuery: shared.urlQuery ?? {}
72
+ },
73
+ endpointDepth: 0
74
+ });
75
+ if ([
76
+ 'error',
77
+ 'reject'
78
+ ].includes(status)) {
79
+ throw error ?? new ConfigError(`Dynamic block "${block.blockId}" on page "${shared.pageId}" endpoint "${endpointId}" failed with status "${status}".`, {
80
+ configKey: block['~k']
81
+ });
82
+ }
83
+ if (!type.isObject(response) || !type.isArray(response.blocks)) {
84
+ throw new ConfigError(`Dynamic block "${block.blockId}" on page "${shared.pageId}" endpoint "${endpointId}" must return an object with a "blocks" array.`, {
85
+ received: response,
86
+ configKey: block['~k']
87
+ });
88
+ }
89
+ // Unescape before building so operator counting validates the real
90
+ // (client-evaluated) operators against the bundle.
91
+ const { blocks, callApiActionRefs, requestActionRefs, warnings } = shared.buildDynamicBlocks({
92
+ blocks: unescapeOperators(response.blocks),
93
+ pageId: shared.pageId,
94
+ dynamicBlockId: block.blockId,
95
+ idPrefix: block.id,
96
+ types: shared.types,
97
+ blockMetas: shared.blockMetas
98
+ });
99
+ warnings.forEach((warning)=>{
100
+ logger.warn({
101
+ event: 'dynamic_block_warning',
102
+ blockId: block.blockId,
103
+ pageId: shared.pageId
104
+ }, warning.message);
105
+ });
106
+ await validateFragment(context, {
107
+ blocks,
108
+ blockSchemas: shared.blockSchemas,
109
+ callApiActionRefs,
110
+ dynamicBlockId: block.blockId,
111
+ pageId: shared.pageId,
112
+ pageRequests: shared.pageRequests,
113
+ requestActionRefs
114
+ });
115
+ await resolveBlocks(context, {
116
+ blocks,
117
+ depth: depth + 1,
118
+ shared
119
+ });
120
+ setResolvedContent(block, blocks);
121
+ } catch (error) {
122
+ if (required === true) {
123
+ throw new ConfigError(`Dynamic block "${block.blockId}" on page "${shared.pageId}" failed to resolve: ${error.message}`, {
124
+ configKey: block['~k'],
125
+ cause: error
126
+ });
127
+ }
128
+ logger.error({
129
+ event: 'dynamic_block_error',
130
+ blockId: block.blockId,
131
+ endpointId,
132
+ pageId: shared.pageId,
133
+ err: error
134
+ }, `Dynamic block "${block.blockId}" on page "${shared.pageId}" failed to resolve: ${error.message}`);
135
+ const fallbackBlocks = block.slots?.fallback?.blocks ?? [];
136
+ setResolvedContent(block, fallbackBlocks);
137
+ await resolveBlocks(context, {
138
+ blocks: fallbackBlocks,
139
+ depth: depth + 1,
140
+ shared
141
+ });
142
+ }
143
+ }
144
+ async function resolveDynamicContent(context, { pageConfig, urlQuery }) {
145
+ // Loaded lazily so apps without dynamic pages never load the build package.
146
+ const { default: buildDynamicBlocks } = await import('@lowdefy/build/dynamic');
147
+ const [types, blockMetas, blockSchemas] = await Promise.all([
148
+ context.readConfigFile('types.json'),
149
+ context.readConfigFile('plugins/blockMetas.json'),
150
+ context.readConfigFile('plugins/blockSchemas.json')
151
+ ]);
152
+ context.evaluateOperators = createEvaluateOperators(context);
153
+ const shared = {
154
+ blockMetas: blockMetas ?? {},
155
+ blockSchemas: blockSchemas ?? {},
156
+ buildDynamicBlocks,
157
+ pageId: pageConfig.pageId,
158
+ pageRequests: pageConfig.requests ?? [],
159
+ types: types ?? {},
160
+ urlQuery
161
+ };
162
+ // The page root block itself can be a Dynamic block.
163
+ await resolveBlocks(context, {
164
+ blocks: [
165
+ pageConfig
166
+ ],
167
+ depth: 0,
168
+ shared
169
+ });
170
+ return pageConfig;
171
+ }
172
+ export default resolveDynamicContent;
@@ -0,0 +1,36 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ // One extra leading underscore defers operator evaluation by one level — the
17
+ // same convention _function bodies use for __args. Shared operators like
18
+ // _state are registered on the server, so a plain `_state` in a routine's
19
+ // :return evaluates there (against empty routine state). Authors write
20
+ // `__state` instead: it survives the server evaluation untouched, and this
21
+ // unescape strips one underscore so the client evaluates the real operator.
22
+ function unescapeOperators(value) {
23
+ if (type.isArray(value)) {
24
+ return value.map(unescapeOperators);
25
+ }
26
+ if (!type.isObject(value)) {
27
+ return value;
28
+ }
29
+ const result = {};
30
+ Object.keys(value).forEach((key)=>{
31
+ const unescapedKey = key.startsWith('__') ? key.slice(1) : key;
32
+ result[unescapedKey] = unescapeOperators(value[key]);
33
+ });
34
+ return result;
35
+ }
36
+ export default unescapeOperators;
@@ -0,0 +1,104 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { validate } from '@lowdefy/ajv';
16
+ import { getOperatorType, type } from '@lowdefy/helpers';
17
+ import { ConfigError } from '@lowdefy/errors';
18
+ function isOperatorObject(value) {
19
+ return getOperatorType(value) !== null;
20
+ }
21
+ function escapePointerSegment(segment) {
22
+ return segment.replace(/~/g, '~0').replace(/\//g, '~1');
23
+ }
24
+ function collectOperatorPaths(value, path, paths) {
25
+ if (type.isArray(value)) {
26
+ value.forEach((item, index)=>collectOperatorPaths(item, `${path}/${index}`, paths));
27
+ return;
28
+ }
29
+ if (!type.isObject(value)) {
30
+ return;
31
+ }
32
+ if (isOperatorObject(value)) {
33
+ paths.push(path);
34
+ return;
35
+ }
36
+ Object.keys(value).forEach((key)=>{
37
+ if (key.startsWith('~')) return;
38
+ collectOperatorPaths(value[key], `${path}/${escapePointerSegment(key)}`, paths);
39
+ });
40
+ }
41
+ // A schema violation at or under an operator node cannot be judged before the
42
+ // operator evaluates on the client — { _state: columns } may legitimately sit
43
+ // where the schema wants an array. Violations on operator-free paths stand.
44
+ function validateBlockProperties(block, { blockSchemas, dynamicBlockId, pageId }) {
45
+ const properties = block.properties;
46
+ if (!type.isObject(properties) || isOperatorObject(properties)) {
47
+ return;
48
+ }
49
+ // Block schemas validate the whole pre-build block shape; the plugin's
50
+ // properties schema sits at schema.properties.properties.
51
+ const propertiesSchema = blockSchemas[block.type]?.properties?.properties;
52
+ if (type.isNone(propertiesSchema)) {
53
+ return;
54
+ }
55
+ const result = validate({
56
+ schema: propertiesSchema,
57
+ data: properties,
58
+ returnErrors: true
59
+ });
60
+ if (result.valid) {
61
+ return;
62
+ }
63
+ const operatorPaths = [];
64
+ collectOperatorPaths(properties, '', operatorPaths);
65
+ const errors = result.errors.filter((error)=>!operatorPaths.some((path)=>error.instancePath === path || error.instancePath.startsWith(`${path}/`)));
66
+ if (errors.length > 0) {
67
+ const messages = errors.map((error)=>`properties${error.instancePath || ''} ${error.message}`);
68
+ throw new ConfigError(`Dynamic block "${dynamicBlockId}" on page "${pageId}" resolved block "${block.blockId}" (${block.type}) has invalid properties:\n${messages.map((message)=>` - ${message}`).join('\n')}`);
69
+ }
70
+ }
71
+ function walkBlocks(blocks, callback) {
72
+ blocks.forEach((block)=>{
73
+ callback(block);
74
+ Object.values(block.slots ?? {}).forEach((slot)=>{
75
+ walkBlocks(slot.blocks ?? [], callback);
76
+ });
77
+ });
78
+ }
79
+ async function validateFragment(context, { blocks, blockSchemas, callApiActionRefs, dynamicBlockId, pageId, pageRequests, requestActionRefs }) {
80
+ walkBlocks(blocks, (block)=>{
81
+ validateBlockProperties(block, {
82
+ blockSchemas,
83
+ dynamicBlockId,
84
+ pageId
85
+ });
86
+ });
87
+ // Request actions can only reference requests defined statically on the page —
88
+ // request artifacts are written at build time.
89
+ const pageRequestIds = new Set(pageRequests.map((request)=>request.requestId));
90
+ requestActionRefs.forEach(({ requestId, blockId, eventId })=>{
91
+ if (!pageRequestIds.has(requestId)) {
92
+ throw new ConfigError(`Dynamic block "${dynamicBlockId}" on page "${pageId}" resolved content references request "${requestId}" on event "${eventId}" on block "${blockId}" which is not defined on the page.`);
93
+ }
94
+ });
95
+ // CallAPI refs fail resolution instead of the user's click — same checks the
96
+ // HTTP endpoint route applies.
97
+ await Promise.all(callApiActionRefs.map(async ({ endpointId, blockId, eventId })=>{
98
+ const endpointConfig = await context.readConfigFile(`api/${endpointId}.json`);
99
+ if (!endpointConfig || endpointConfig.type === 'InternalApi') {
100
+ throw new ConfigError(`Dynamic block "${dynamicBlockId}" on page "${pageId}" resolved content has a CallAPI action on event "${eventId}" on block "${blockId}" targeting endpoint "${endpointId}" which does not exist or is not accessible from client pages.`);
101
+ }
102
+ }));
103
+ }
104
+ export default validateFragment;
@@ -13,14 +13,24 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { serializer } from '@lowdefy/helpers';
16
- async function getPageConfig({ authorize, readConfigFile }, { pageId }) {
17
- const pageConfig = await readConfigFile(`pages/${pageId}.json`);
18
- if (pageConfig && authorize(pageConfig)) {
16
+ import resolveDynamicContent from './dynamic/resolveDynamicContent.js';
17
+ async function getPageConfig(context, { pageId, urlQuery }) {
18
+ const pageConfig = await context.readConfigFile(`pages/${pageId}.json`);
19
+ if (pageConfig && context.authorize(pageConfig)) {
19
20
  // eslint-disable-next-line no-unused-vars
20
21
  const { auth, ...rest } = pageConfig;
21
- // Use serializer.serialize to ensure ~k keys (non-enumerable after deserialize)
22
- // are made enumerable again for JSON transfer to client
23
- return serializer.serialize(rest);
22
+ if (rest.dynamic !== true) {
23
+ // Use serializer.serialize to ensure ~k keys (non-enumerable after deserialize)
24
+ // are made enumerable again for JSON transfer to client
25
+ return serializer.serialize(rest);
26
+ }
27
+ // readConfigFile caches parsed artifacts — deep copy before resolution so
28
+ // one request's resolved content never reaches another via the cache.
29
+ const resolved = await resolveDynamicContent(context, {
30
+ pageConfig: serializer.copy(rest),
31
+ urlQuery
32
+ });
33
+ return serializer.serialize(resolved);
24
34
  }
25
35
  return null;
26
36
  }
@@ -59,10 +59,16 @@ async function callRequest(context, { blockId, pageId, payload, requestId }) {
59
59
  });
60
60
  const { connectionProperties, requestProperties } = evaluateOperators(context, {
61
61
  connectionConfig,
62
- payload: requestPayload,
63
62
  requestConfig,
64
- state: {},
65
- steps: {}
63
+ // A page request runs outside any routine, so it evaluates against an empty frame.
64
+ routineContext: {
65
+ arrayIndices: [],
66
+ error: null,
67
+ items: {},
68
+ payload: requestPayload,
69
+ state: {},
70
+ steps: {}
71
+ }
66
72
  });
67
73
  checkConnectionRead(context, {
68
74
  connectionConfig,
@@ -13,6 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { RequestError, ServiceError } from '@lowdefy/errors';
16
+ import getCurrentEnvironment from '../../context/getCurrentEnvironment.js';
16
17
  import invokeEndpoint from '../endpoints/invokeEndpoint.js';
17
18
  async function callRequestResolver(context, { connectionProperties, endpointDepth, requestConfig, requestProperties, requestResolver }) {
18
19
  const { blockId, endpointId, logger, pageId, payload } = context;
@@ -49,6 +50,11 @@ async function callRequestResolver(context, { connectionProperties, endpointDept
49
50
  connection: connectionProperties,
50
51
  connectionId: requestConfig.connectionId,
51
52
  endpointId,
53
+ // The deployment's current environment ({ name, url, email, ... }) or null, so connection
54
+ // types can apply environment settings such as the email delivery filter.
55
+ environment: getCurrentEnvironment({
56
+ config: context.config
57
+ }),
52
58
  pageId,
53
59
  payload,
54
60
  request: requestProperties,
@@ -12,21 +12,17 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ function evaluateOperators({ evaluateOperators }, { connectionConfig, items, payload, requestConfig, state, steps }) {
16
- const connectionProperties = evaluateOperators({
15
+ */ import evaluateRoutineOperators from '../endpoints/evaluateRoutineOperators.js';
16
+ // Connection and request properties evaluate against the same frame, so `_item` in a connection's
17
+ // properties resolves inside a `:for` loop just like it does in the request's.
18
+ function evaluateOperators(context, { connectionConfig, requestConfig, routineContext }) {
19
+ const connectionProperties = evaluateRoutineOperators(context, routineContext, {
17
20
  input: connectionConfig.properties || {},
18
- location: connectionConfig.connectionId,
19
- payload,
20
- state,
21
- steps
21
+ location: connectionConfig.connectionId
22
22
  });
23
- const requestProperties = evaluateOperators({
23
+ const requestProperties = evaluateRoutineOperators(context, routineContext, {
24
24
  input: requestConfig.properties || {},
25
- items,
26
- location: requestConfig.stepId ?? requestConfig.requestId,
27
- payload,
28
- state,
29
- steps
25
+ location: requestConfig.stepId ?? requestConfig.requestId
30
26
  });
31
27
  return {
32
28
  connectionProperties,
@@ -0,0 +1,27 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { ConfigError } from '@lowdefy/errors';
16
+ function authorizeWebsocket({ authorize, logger }, { websocketConfig }) {
17
+ if (!authorize(websocketConfig)) {
18
+ logger.debug({
19
+ event: 'debug_websocket_authorize',
20
+ authorized: false,
21
+ auth_config: websocketConfig.auth
22
+ });
23
+ // Same message as a missing websocket so channel existence does not leak.
24
+ throw new ConfigError(`Websocket "${websocketConfig.websocketId}" does not exist.`);
25
+ }
26
+ }
27
+ export default authorizeWebsocket;