@sockethub/schemas 3.0.0-alpha.10 → 3.0.0-alpha.12

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 (46) hide show
  1. package/dist/context.js +101 -0
  2. package/dist/context.js.map +10 -0
  3. package/dist/contexts/platform/dummy/v1.jsonld +8 -0
  4. package/dist/contexts/platform/error/v1.jsonld +8 -0
  5. package/dist/contexts/platform/feeds/v1.jsonld +8 -0
  6. package/dist/contexts/platform/irc/v1.jsonld +8 -0
  7. package/dist/contexts/platform/metadata/v1.jsonld +8 -0
  8. package/dist/contexts/platform/sockethub%3Ainternal/v1.jsonld +8 -0
  9. package/dist/contexts/platform/xmpp/v1.jsonld +8 -0
  10. package/dist/contexts/v1.jsonld +7 -0
  11. package/dist/index.js +533 -796
  12. package/dist/index.js.map +10 -15
  13. package/dist/maps/context-map.json +35 -0
  14. package/dist/schemas/platform/dummy/v1/credentials.schema.json +1 -0
  15. package/dist/schemas/platform/dummy/v1/messages.schema.json +22 -0
  16. package/dist/schemas/platform/error/v1/credentials.schema.json +1 -0
  17. package/dist/schemas/platform/error/v1/messages.schema.json +1 -0
  18. package/dist/schemas/platform/feeds/v1/credentials.schema.json +1 -0
  19. package/dist/schemas/platform/feeds/v1/messages.schema.json +36 -0
  20. package/dist/schemas/platform/irc/v1/credentials.schema.json +48 -0
  21. package/dist/schemas/platform/irc/v1/messages.schema.json +19 -0
  22. package/dist/schemas/platform/metadata/v1/credentials.schema.json +1 -0
  23. package/dist/schemas/platform/metadata/v1/messages.schema.json +29 -0
  24. package/dist/schemas/platform/sockethub%3Ainternal/v1/credentials.schema.json +1 -0
  25. package/dist/schemas/platform/sockethub%3Ainternal/v1/messages.schema.json +1 -0
  26. package/dist/schemas/platform/xmpp/v1/credentials.schema.json +43 -0
  27. package/dist/schemas/platform/xmpp/v1/messages.schema.json +21 -0
  28. package/package.json +15 -8
  29. package/src/context.ts +125 -0
  30. package/src/helpers/error-parser.ts +65 -18
  31. package/src/helpers/objects.ts +59 -3
  32. package/src/index.test.data.credentials.ts +37 -5
  33. package/src/index.test.data.platform.ts +3 -0
  34. package/src/index.test.data.streams.ts +117 -25
  35. package/src/index.test.ts +31 -3
  36. package/src/index.ts +30 -1
  37. package/src/schemas/activity-stream.ts +30 -9
  38. package/src/schemas/json/activity-object.json +50 -1
  39. package/src/schemas/json/activity-stream.json +77 -44
  40. package/src/schemas/json/platform.json +22 -2
  41. package/src/schemas/json/sockethub-config.json +120 -28
  42. package/src/schemas/platform.ts +25 -1
  43. package/src/schemas/sockethub-config.ts +94 -14
  44. package/src/types/ajv-formats-draft2019.d.ts +5 -0
  45. package/src/types.ts +29 -12
  46. package/src/validator.ts +138 -25
@@ -9,29 +9,41 @@ export const SockethubConfigSchema = {
9
9
  description: "Sockethub Config Schema",
10
10
  type: "object",
11
11
  required: ["platforms"],
12
+ additionalProperties: false,
12
13
  properties: {
14
+ $schema: {
15
+ type: "string",
16
+ },
13
17
  examples: {
18
+ type: "boolean",
19
+ default: true,
20
+ },
21
+ logging: {
14
22
  type: "object",
23
+ additionalProperties: false,
15
24
  properties: {
16
- enabled: {
17
- type: "boolean",
18
- default: false,
25
+ level: {
26
+ type: "string",
27
+ enum: ["error", "warn", "info", "debug"],
28
+ default: "info",
29
+ },
30
+ fileLevel: {
31
+ type: "string",
32
+ enum: ["error", "warn", "info", "debug"],
33
+ default: "debug",
19
34
  },
20
- secret: {
35
+ file: {
21
36
  type: "string",
22
- default: "1234567890",
37
+ default: "sockethub.log",
23
38
  },
24
39
  },
25
- additionalProperties: false,
26
- },
27
- log_file: {
28
- type: "string",
29
40
  },
30
41
  packageConfig: {
31
42
  type: "object",
32
43
  properties: {
33
44
  "@sockethub/activity-streams": {
34
45
  type: "object",
46
+ additionalProperties: false,
35
47
  properties: {
36
48
  specialObjs: {
37
49
  type: "array",
@@ -45,38 +57,37 @@ export const SockethubConfigSchema = {
45
57
  default: true,
46
58
  },
47
59
  },
48
- additionalProperties: false,
49
60
  },
50
61
  "@sockethub/platform-dummy": {
51
62
  type: "object",
63
+ additionalProperties: false,
52
64
  properties: {
53
65
  greeting: {
54
66
  type: "string",
55
67
  default: "Hello",
56
68
  },
57
69
  },
58
- additionalProperties: false,
59
70
  },
60
71
  "@sockethub/platform-feeds": {
61
72
  type: "object",
73
+ additionalProperties: false,
62
74
  properties: {
63
75
  ...connectConfigPlatforms,
64
76
  },
65
- additionalProperties: false,
66
77
  },
67
78
  "@sockethub/platform-irc": {
68
79
  type: "object",
80
+ additionalProperties: false,
69
81
  properties: {
70
82
  ...connectConfigPlatforms,
71
83
  },
72
- additionalProperties: false,
73
84
  },
74
85
  "@sockethub/platform-xmpp": {
75
86
  type: "object",
87
+ additionalProperties: false,
76
88
  properties: {
77
89
  ...connectConfigPlatforms,
78
90
  },
79
- additionalProperties: false,
80
91
  },
81
92
  },
82
93
  },
@@ -85,9 +96,17 @@ export const SockethubConfigSchema = {
85
96
  items: {
86
97
  type: "string",
87
98
  },
99
+ default: [
100
+ "@sockethub/platform-dummy",
101
+ "@sockethub/platform-feeds",
102
+ "@sockethub/platform-irc",
103
+ "@sockethub/platform-metadata",
104
+ "@sockethub/platform-xmpp",
105
+ ],
88
106
  },
89
107
  public: {
90
108
  type: "object",
109
+ additionalProperties: false,
91
110
  properties: {
92
111
  protocol: {
93
112
  type: "string",
@@ -106,20 +125,68 @@ export const SockethubConfigSchema = {
106
125
  default: "/",
107
126
  },
108
127
  },
128
+ },
129
+ rateLimiter: {
130
+ type: "object",
109
131
  additionalProperties: false,
132
+ properties: {
133
+ windowMs: {
134
+ type: "number",
135
+ default: 1000,
136
+ },
137
+ maxRequests: {
138
+ type: "number",
139
+ default: 100,
140
+ },
141
+ blockDurationMs: {
142
+ type: "number",
143
+ default: 5000,
144
+ },
145
+ },
110
146
  },
111
147
  redis: {
112
148
  type: "object",
149
+ additionalProperties: false,
113
150
  properties: {
114
151
  url: {
115
152
  type: "string",
116
153
  default: "redis://127.0.0.1:6379",
117
154
  },
155
+ connectTimeout: {
156
+ type: "number",
157
+ default: 10000,
158
+ description: "Connection timeout in milliseconds",
159
+ },
160
+ disconnectTimeout: {
161
+ type: "number",
162
+ default: 5000,
163
+ description: "Disconnect timeout in milliseconds",
164
+ },
165
+ maxRetriesPerRequest: {
166
+ type: ["number", "null"],
167
+ default: null as number | null,
168
+ description:
169
+ "Maximum number of retries per request (null for BullMQ default)",
170
+ },
118
171
  },
172
+ },
173
+ sentry: {
174
+ type: "object",
119
175
  additionalProperties: false,
176
+ properties: {
177
+ dsn: {
178
+ type: "string",
179
+ default: "",
180
+ },
181
+ traceSampleRate: {
182
+ type: "number",
183
+ default: 1.0,
184
+ },
185
+ },
120
186
  },
121
187
  sockethub: {
122
188
  type: "object",
189
+ additionalProperties: false,
123
190
  properties: {
124
191
  port: {
125
192
  type: "number",
@@ -134,7 +201,20 @@ export const SockethubConfigSchema = {
134
201
  default: "/sockethub",
135
202
  },
136
203
  },
204
+ },
205
+ platformHeartbeat: {
206
+ type: "object",
137
207
  additionalProperties: false,
208
+ properties: {
209
+ intervalMs: {
210
+ type: "number",
211
+ default: 5000,
212
+ },
213
+ timeoutMs: {
214
+ type: "number",
215
+ default: 15000,
216
+ },
217
+ },
138
218
  },
139
219
  },
140
220
  };
@@ -0,0 +1,5 @@
1
+ declare module "ajv-formats-draft2019" {
2
+ import type Ajv from "ajv";
3
+ const addFormats: (ajv: Ajv) => void;
4
+ export default addFormats;
5
+ }
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export interface ASCollection {
2
- context: string;
2
+ "@context": JsonLdContext;
3
3
  id?: string;
4
4
  summary: string;
5
5
  type: "collection";
@@ -7,10 +7,13 @@ export interface ASCollection {
7
7
  items: Array<ActivityStream>;
8
8
  }
9
9
 
10
+ export type JsonLdContext = Array<string>;
11
+
10
12
  export interface ActivityStream {
13
+ "@context": JsonLdContext;
11
14
  id?: string;
12
15
  type: string;
13
- context: string;
16
+ platform?: string;
14
17
  actor: ActivityActor;
15
18
  object?: ActivityObject;
16
19
  target?: ActivityActor;
@@ -59,19 +62,20 @@ export type PlatformSendToClient = (
59
62
  export type PlatformUpdateActor = (credentials: object) => Promise<void>;
60
63
 
61
64
  export interface Logger {
62
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
63
- // biome-ignore lint/style/useShorthandFunctionType: <explanation>
64
- (msg: string, data?: any): void;
65
+ error(message: string, meta?: object): void;
66
+ warn(message: string, meta?: object): void;
67
+ info(message: string, meta?: object): void;
68
+ debug(message: string, meta?: object): void;
65
69
  }
66
70
 
67
71
  export interface PlatformSession {
68
- debug: Logger;
72
+ log: Logger;
69
73
  sendToClient: PlatformSendToClient;
70
74
  updateActor: PlatformUpdateActor;
71
75
  }
72
76
 
73
77
  export interface CredentialsObject {
74
- context: string;
78
+ "@context": JsonLdContext;
75
79
  type: "credentials";
76
80
  actor: ActivityActor;
77
81
  object: {
@@ -118,13 +122,15 @@ export interface PersistentPlatformConfig extends BasePlatformConfig {
118
122
  persist: true;
119
123
  /** Message types requiring credentials (must be non-empty for persistent platforms) */
120
124
  requireCredentials: string[];
121
- /** Whether the platform has completed initialization */
122
- initialized: boolean;
123
125
  }
124
126
 
125
127
  export interface PlatformSchemaStruct {
126
128
  name: string;
127
129
  version: string;
130
+ contextUrl: string;
131
+ contextVersion: string;
132
+ schemaVersion: string;
133
+ messageConstraints?: object;
128
134
  credentials?: object;
129
135
  messages?: {
130
136
  required?: string[];
@@ -137,7 +143,7 @@ export interface PlatformSchemaStruct {
137
143
  }
138
144
 
139
145
  export interface PlatformConstructor {
140
- new (session: PlatformSession): { debug: Logger };
146
+ new (session: PlatformSession): { log: Logger };
141
147
  }
142
148
 
143
149
  /**
@@ -145,9 +151,20 @@ export interface PlatformConstructor {
145
151
  * Platforms implement protocol-specific logic for ActivityStreams messages.
146
152
  */
147
153
  export interface PlatformInterface {
148
- debug: Logger;
149
154
  get config(): PlatformConfig;
150
155
  get schema(): PlatformSchemaStruct;
156
+ /**
157
+ * Returns whether the platform is ready to handle jobs.
158
+ *
159
+ * For persistent platforms: Returns true once the initial connection succeeds.
160
+ * During temporary network interruptions with automatic reconnection, should
161
+ * remain true to allow queued jobs to retry rather than fail.
162
+ *
163
+ * For stateless platforms: Can always return true since they have no connection state.
164
+ *
165
+ * @returns true if the platform can process jobs, false otherwise
166
+ */
167
+ isInitialized(): boolean;
151
168
  cleanup(cb: PlatformCallback): void;
152
169
  }
153
170
 
@@ -176,7 +193,7 @@ export interface PersistentPlatformInterface extends PlatformInterface {
176
193
  }
177
194
 
178
195
  export interface PlatformSession {
179
- debug: Logger;
196
+ log: Logger;
180
197
  sendToClient: PlatformSendToClient;
181
198
  updateActor: PlatformUpdateActor;
182
199
  }
package/src/validator.ts CHANGED
@@ -1,39 +1,82 @@
1
1
  import Ajv, { type Schema } from "ajv";
2
2
  import addFormats from "ajv-formats";
3
3
  import additionsFormats2019 from "ajv-formats-draft2019";
4
- import debug from "debug";
5
- import getErrorMessage from "./helpers/error-parser.js";
4
+ import {
5
+ ERROR_PLATFORM_CONTEXT_URL,
6
+ ERROR_PLATFORM_ID,
7
+ getCredentialsSchemaIdByContextUrl,
8
+ getPlatformIdByContextUrl,
9
+ getSchemaIdByContextUrl,
10
+ INTERNAL_PLATFORM_CONTEXT_URL,
11
+ INTERNAL_PLATFORM_ID,
12
+ registerPlatformContext,
13
+ resolvePlatformContextUrl,
14
+ } from "./context.js";
15
+ import getErrorMessage, {
16
+ type ValidationErrorOptions,
17
+ } from "./helpers/error-parser.js";
6
18
  import { ActivityObjectSchema } from "./schemas/activity-object.js";
7
19
  import { ActivityStreamSchema } from "./schemas/activity-stream.js";
8
20
  import { PlatformSchema } from "./schemas/platform.js";
9
- import type { ActivityStream } from "./types.js";
21
+ import type { ActivityObject, ActivityStream } from "./types.js";
10
22
 
11
23
  const ajv = new Ajv({ strictTypes: false, allErrors: true });
12
- addFormats(ajv);
13
- additionsFormats2019(ajv);
24
+ addFormats(ajv as unknown as Parameters<typeof addFormats>[0]);
25
+ additionsFormats2019(ajv as unknown as Ajv);
14
26
 
15
- interface SchemasDict {
16
- string?: Schema;
17
- }
27
+ type SchemasDict = Record<string, Schema>;
18
28
 
19
- const log = debug("sockethub:schemas:validator");
20
29
  const schemaURL = "https://sockethub.org/schemas/v0";
21
30
  const schemas: SchemasDict = {};
31
+ let validationErrorOptions: ValidationErrorOptions = {};
32
+ let systemContextsRegistered = false;
22
33
 
23
34
  schemas[`${schemaURL}/activity-stream`] = ActivityStreamSchema;
24
35
  schemas[`${schemaURL}/activity-object`] = ActivityObjectSchema;
25
36
 
26
37
  for (const uri in schemas) {
27
- log(`registering schema ${uri}`);
28
38
  ajv.addSchema(schemas[uri], uri);
29
39
  }
30
40
 
41
+ function registerSystemContext(platformId: string, contextUrl: string): void {
42
+ registerPlatformContext(
43
+ platformId,
44
+ contextUrl,
45
+ `${schemaURL}/context/${platformId}/messages`,
46
+ `${schemaURL}/context/${platformId}/credentials`,
47
+ );
48
+
49
+ const messageSchemaRef = `${schemaURL}/context/${platformId}/messages`;
50
+ if (!ajv.getSchema(messageSchemaRef)) {
51
+ ajv.addSchema({}, messageSchemaRef);
52
+ }
53
+
54
+ const credentialsSchemaRef = `${schemaURL}/context/${platformId}/credentials`;
55
+ if (!ajv.getSchema(credentialsSchemaRef)) {
56
+ ajv.addSchema({}, credentialsSchemaRef);
57
+ }
58
+ }
59
+
60
+ export function registerSystemPlatformContexts(): void {
61
+ if (systemContextsRegistered) {
62
+ return;
63
+ }
64
+ registerSystemContext(ERROR_PLATFORM_ID, ERROR_PLATFORM_CONTEXT_URL);
65
+ registerSystemContext(INTERNAL_PLATFORM_ID, INTERNAL_PLATFORM_CONTEXT_URL);
66
+ systemContextsRegistered = true;
67
+ }
68
+
69
+ registerSystemPlatformContexts();
70
+
31
71
  function handleValidation(
32
72
  schemaRef: string,
33
- msg: ActivityStream,
73
+ msg: ActivityStream | ActivityObject,
34
74
  isObject = false,
35
75
  ): string {
36
76
  const validator = ajv.getSchema(schemaRef);
77
+ if (!validator) {
78
+ return `schema ${schemaRef} not found`;
79
+ }
37
80
  let result: boolean | Promise<unknown>;
38
81
  if (isObject) {
39
82
  result = validator({ object: msg });
@@ -41,37 +84,81 @@ function handleValidation(
41
84
  result = validator(msg);
42
85
  }
43
86
  if (!result) {
44
- let errorMessage = getErrorMessage(msg, validator.errors);
45
- if (msg.context) {
46
- errorMessage = `[${msg.context}] ${errorMessage}`;
87
+ let errorMessage = getErrorMessage(
88
+ msg,
89
+ validator.errors,
90
+ validationErrorOptions,
91
+ );
92
+ if (Array.isArray((msg as ActivityStream)["@context"])) {
93
+ const platformContextUrl = resolvePlatformContextUrl(
94
+ msg as ActivityStream,
95
+ );
96
+ if (platformContextUrl) {
97
+ const platformId =
98
+ getPlatformIdByContextUrl(platformContextUrl);
99
+ if (platformId) {
100
+ errorMessage = `[${platformId}] ${errorMessage}`;
101
+ }
102
+ }
47
103
  }
48
104
  return errorMessage;
49
105
  }
50
106
  return "";
51
107
  }
52
108
 
53
- export function validateActivityObject(msg: ActivityStream): string {
109
+ export function setValidationErrorOptions(
110
+ options: ValidationErrorOptions,
111
+ ): void {
112
+ validationErrorOptions = { ...validationErrorOptions, ...options };
113
+ }
114
+
115
+ export function validateActivityObject(msg: ActivityObject): string {
54
116
  return handleValidation(`${schemaURL}/activity-object`, msg, true);
55
117
  }
56
118
 
57
119
  export function validateActivityStream(msg: ActivityStream): string {
58
- return handleValidation(`${schemaURL}/activity-stream`, msg);
120
+ const streamValidationError = handleValidation(
121
+ `${schemaURL}/activity-stream`,
122
+ msg,
123
+ );
124
+ if (streamValidationError) {
125
+ return streamValidationError;
126
+ }
127
+ const platformContextUrl = resolvePlatformContextUrl(msg);
128
+ if (!platformContextUrl) {
129
+ return "activity streams must have exactly one registered platform context URL in @context";
130
+ }
131
+ const schemaId = getSchemaIdByContextUrl(platformContextUrl);
132
+ if (!schemaId) {
133
+ return `platform context URL ${platformContextUrl} does not have a registered message schema`;
134
+ }
135
+ return handleValidation(schemaId, msg);
59
136
  }
60
137
 
61
138
  export function validateCredentials(msg: ActivityStream): string {
62
- if (!msg.context) {
63
- return "credential activity streams must have a context set";
139
+ if (!Array.isArray(msg["@context"])) {
140
+ return "credential activity streams must have an @context set";
64
141
  }
65
142
  if (msg.type !== "credentials") {
66
143
  return "credential activity streams must have credentials set as type";
67
144
  }
68
- log(
69
- `validating credentials against ${schemaURL}/context/${msg.context}/credentials`,
70
- );
71
- return handleValidation(
72
- `${schemaURL}/context/${msg.context}/credentials`,
145
+ const streamValidationError = handleValidation(
146
+ `${schemaURL}/activity-stream`,
73
147
  msg,
74
148
  );
149
+ if (streamValidationError) {
150
+ return streamValidationError;
151
+ }
152
+ const platformContextUrl = resolvePlatformContextUrl(msg);
153
+ if (!platformContextUrl) {
154
+ return "credential activity streams must have exactly one registered platform context URL in @context";
155
+ }
156
+ const credentialsSchemaId =
157
+ getCredentialsSchemaIdByContextUrl(platformContextUrl);
158
+ if (!credentialsSchemaId) {
159
+ return `platform context URL ${platformContextUrl} does not have a registered credentials schema`;
160
+ }
161
+ return handleValidation(credentialsSchemaId, msg);
75
162
  }
76
163
 
77
164
  export function validatePlatformSchema(schema: Schema): string {
@@ -85,10 +172,36 @@ export function validatePlatformSchema(schema: Schema): string {
85
172
  }
86
173
 
87
174
  export function addPlatformSchema(schema: Schema, platform_type: string) {
88
- log(`registering schema ${schemaURL}/context/${platform_type}`);
89
- ajv.addSchema(schema, `${schemaURL}/context/${platform_type}`);
175
+ const schemaRef = `${schemaURL}/context/${platform_type}`;
176
+ if (!ajv.getSchema(schemaRef)) {
177
+ ajv.addSchema(schema, schemaRef);
178
+ }
90
179
  }
91
180
 
92
181
  export function getPlatformSchema(platform_type: string) {
93
182
  return ajv.getSchema(`${schemaURL}/context/${platform_type}`);
94
183
  }
184
+
185
+ export function addPlatformContext(
186
+ platformId: string,
187
+ platformContextUrl: string,
188
+ ) {
189
+ registerPlatformContext(
190
+ platformId,
191
+ platformContextUrl,
192
+ `${schemaURL}/context/${platformId}/messages`,
193
+ `${schemaURL}/context/${platformId}/credentials`,
194
+ );
195
+ }
196
+
197
+ export function resolvePlatformId(msg: ActivityStream): string | null {
198
+ const platformContextUrl = resolvePlatformContextUrl(msg);
199
+ if (platformContextUrl) {
200
+ const id = getPlatformIdByContextUrl(platformContextUrl);
201
+ if (id) return id;
202
+ }
203
+ if (typeof msg.platform === "string" && msg.platform) {
204
+ return msg.platform;
205
+ }
206
+ return null;
207
+ }