seatalk-components 0.0.0-alpha1

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 (3) hide show
  1. package/README.md +25 -0
  2. package/index.js +1071 -0
  3. package/package.json +1 -0
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # seatalk-components
2
+
3
+ seatalk-components is a versatile npm package designed to provide a wide range of reusable UI components for web development. It simplifies the process of building user interfaces by offering pre-built components that can be easily integrated into any project.
4
+
5
+ ## Installation
6
+
7
+ To install seatalk-components, run the following command in your terminal:
8
+
9
+ ```
10
+ npm install seatalk-components
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **Responsive Design**: seatalk-components includes components that adapt to different screen sizes, ensuring a seamless user experience across devices.
16
+ - **Customization**: Each component offers customization options, allowing developers to tailor the appearance and behavior to their specific needs.
17
+ - **Accessibility**: seatalk-components prioritizes accessibility, ensuring that all components are compatible with assistive technologies and follow best practices for inclusivity.
18
+
19
+ ## Contributing
20
+
21
+ Contributions to seatalk-components are welcome! If you have any ideas for new components or improvements to existing ones, please feel free to submit a pull request. We appreciate your help in making seatalk-components even better.
22
+
23
+ ## License
24
+
25
+ seatalk-components is released under the MIT license. See the LICENSE file for more information.
package/index.js ADDED
@@ -0,0 +1,1071 @@
1
+ // index.js
2
+ // This package provides data structures and utilities for handling SeaTalk-related components
3
+ // such as messages, users, teams, and channels, along with validation and formatting helpers.
4
+ // It focuses on structuring and validating data typically exchanged or used within a SeaTalk
5
+ // integration context, simulating component-like behavior through data manipulation and validation
6
+ // without relying on external UI frameworks.
7
+
8
+ // --- Constants ---
9
+
10
+ /**
11
+ * @typedef {'text' | 'card' | 'file' | 'unknown'} SeaTalkMessageType
12
+ */
13
+
14
+ /**
15
+ * Enum for standard SeaTalk message types.
16
+ * @readonly
17
+ * @enum {SeaTalkMessageType}
18
+ */
19
+ const SeaTalkMessageTypes = {
20
+ TEXT: 'text',
21
+ CARD: 'card',
22
+ FILE: 'file',
23
+ UNKNOWN: 'unknown'
24
+ };
25
+
26
+ /**
27
+ * @typedef {'user' | 'team' | 'channel' | 'unknown'} SeaTalkEntityType
28
+ */
29
+
30
+ /**
31
+ * Enum for standard SeaTalk entity types.
32
+ * @readonly
33
+ * @enum {SeaTalkEntityType}
34
+ */
35
+ const SeaTalkEntityTypes = {
36
+ USER: 'user',
37
+ TEAM: 'team',
38
+ CHANNEL: 'channel',
39
+ UNKNOWN: 'unknown'
40
+ };
41
+
42
+ /**
43
+ * Maximum length for a text message content.
44
+ * @type {number}
45
+ */
46
+ const MAX_TEXT_MESSAGE_LENGTH = 20000;
47
+
48
+ /**
49
+ * Maximum length for a card title.
50
+ * @type {number}
51
+ */
52
+ const MAX_CARD_TITLE_LENGTH = 1000;
53
+
54
+ /**
55
+ * Maximum number of sections allowed in a card.
56
+ * @type {number}
57
+ */
58
+ const MAX_CARD_SECTIONS = 20;
59
+
60
+ /**
61
+ * Maximum number of actions allowed in a card.
62
+ * @type {number}
63
+ */
64
+ const MAX_CARD_ACTIONS = 10;
65
+
66
+ /**
67
+ * Regular expression for validating a basic URL format.
68
+ * @type {RegExp}
69
+ */
70
+ const URL_REGEX = /^(?:\w+:)?\/\/[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/;
71
+
72
+ /**
73
+ * Regular expression for validating a SeaTalk entity ID (basic alphanumeric, potentially with hyphens/underscores).
74
+ * @type {RegExp}
75
+ */
76
+ const SEATALK_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
77
+
78
+ // --- Utility Functions ---
79
+
80
+ /**
81
+ * Checks if a value is a string.
82
+ * @param {*} value - The value to check.
83
+ * @returns {boolean} True if the value is a string, false otherwise.
84
+ */
85
+ function isString(value) {
86
+ return typeof value === 'string';
87
+ }
88
+
89
+ /**
90
+ * Checks if a value is a number.
91
+ * @param {*} value - The value to check.
92
+ * @returns {boolean} True if the value is a number, false otherwise.
93
+ */
94
+ function isNumber(value) {
95
+ return typeof value === 'number';
96
+ }
97
+
98
+ /**
99
+ * Checks if a value is a boolean.
100
+ * @param {*} value - The value to check.
101
+ * @returns {boolean} True if the value is a boolean, false otherwise.
102
+ */
103
+ function isBoolean(value) {
104
+ return typeof value === 'boolean';
105
+ }
106
+
107
+ /**
108
+ * Checks if a value is an array.
109
+ * @param {*} value - The value to check.
110
+ * @returns {boolean} True if the value is an array, false otherwise.
111
+ */
112
+ function isArray(value) {
113
+ return Array.isArray(value);
114
+ }
115
+
116
+ /**
117
+ * Checks if a value is an object (and not null or an array).
118
+ * @param {*} value - The value to check.
119
+ * @returns {boolean} True if the value is an object, false otherwise.
120
+ */
121
+ function isObject(value) {
122
+ return typeof value === 'object' && value !== null && !isArray(value);
123
+ }
124
+
125
+ /**
126
+ * Checks if a string is a valid basic URL.
127
+ * @param {*} value - The value to check.
128
+ * @returns {boolean} True if the value is a valid URL string, false otherwise.
129
+ */
130
+ function isValidUrl(value) {
131
+ if (!isString(value)) {
132
+ return false;
133
+ }
134
+ try {
135
+ // Use URL constructor for a stricter check first
136
+ new URL(value);
137
+ // Then check against a basic regex for common patterns not covered by URL constructor alone (like relative paths, though SeaTalk URLs are likely absolute)
138
+ return URL_REGEX.test(value);
139
+ } catch (e) {
140
+ return false; // Invalid URL constructor input
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Checks if a string is a valid non-empty SeaTalk entity ID.
146
+ * @param {*} value - The value to check.
147
+ * @returns {boolean} True if the value is a valid SeaTalk ID string, false otherwise.
148
+ */
149
+ function isValidSeaTalkId(value) {
150
+ return isString(value) && value.length > 0 && SEATALK_ID_REGEX.test(value);
151
+ }
152
+
153
+ /**
154
+ * Formats a timestamp (Date object or number) into a SeaTalk-friendly string.
155
+ * Example: "YYYY-MM-DD HH:mm:ss"
156
+ * @param {Date | number} timestamp - The timestamp to format.
157
+ * @returns {string | null} The formatted date string or null if invalid input.
158
+ */
159
+ function formatSeaTalkTimestamp(timestamp) {
160
+ let date;
161
+ if (timestamp instanceof Date) {
162
+ date = timestamp;
163
+ } else if (isNumber(timestamp)) {
164
+ date = new Date(timestamp);
165
+ } else {
166
+ // Try parsing as string if it's a string
167
+ if (isString(timestamp)) {
168
+ const parsedDate = new Date(timestamp);
169
+ if (!isNaN(parsedDate.getTime())) {
170
+ date = parsedDate;
171
+ } else {
172
+ return null; // Invalid string format
173
+ }
174
+ } else {
175
+ return null; // Invalid input type
176
+ }
177
+ }
178
+
179
+ if (isNaN(date.getTime())) {
180
+ return null; // Invalid date value
181
+ }
182
+
183
+ const pad = (num) => num.toString().padStart(2, '0');
184
+
185
+ const year = date.getFullYear();
186
+ const month = pad(date.getMonth() + 1); // Months are 0-indexed
187
+ const day = pad(date.getDate());
188
+ const hours = pad(date.getHours());
189
+ const minutes = pad(date.getMinutes());
190
+ const seconds = pad(date.getSeconds());
191
+
192
+ // Basic ISO-like format
193
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
194
+ }
195
+
196
+ /**
197
+ * Escapes a string for potential use in a SeaTalk text message or card text (basic markdown escaping).
198
+ * @param {string} text - The text to escape.
199
+ * @returns {string} The escaped text.
200
+ */
201
+ function escapeSeaTalkText(text) {
202
+ if (!isString(text)) {
203
+ return String(text); // Coerce to string if not already
204
+ }
205
+ // Escape common markdown special characters
206
+ return text.replace(/[\\`*_[\]()#+\-.!]/g, '\\$&');
207
+ }
208
+
209
+ // --- Base Component / Entity Class ---
210
+
211
+ /**
212
+ * Base class for all SeaTalk entities (User, Team, Channel, etc.).
213
+ * Provides common properties like ID and basic validation.
214
+ */
215
+ class SeaTalkEntity {
216
+ /**
217
+ * @param {object} data - The raw data object for the entity.
218
+ * @param {string} data.id - The unique identifier for the entity.
219
+ * @param {SeaTalkEntityType} [entityType=SeaTalkEntityTypes.UNKNOWN] - The type of the entity.
220
+ * @throws {Error} If the ID is missing or invalid.
221
+ */
222
+ constructor(data, entityType = SeaTalkEntityTypes.UNKNOWN) {
223
+ if (!isObject(data)) {
224
+ throw new Error(`Invalid initialization data for SeaTalkEntity. Expected object, got ${typeof data}.`);
225
+ }
226
+ if (!isValidSeaTalkId(data.id)) {
227
+ throw new Error(`Invalid or missing ID for SeaTalkEntity. ID: "${data.id}".`);
228
+ }
229
+
230
+ /**
231
+ * The unique identifier for the entity.
232
+ * @type {string}
233
+ * @private
234
+ */
235
+ this._id = data.id;
236
+
237
+ /**
238
+ * The type of the entity.
239
+ * @type {SeaTalkEntityType}
240
+ * @private
241
+ */
242
+ this._type = entityType;
243
+ }
244
+
245
+ /**
246
+ * Gets the unique identifier of the entity.
247
+ * @returns {string} The entity ID.
248
+ */
249
+ getId() {
250
+ return this._id;
251
+ }
252
+
253
+ /**
254
+ * Gets the type of the entity.
255
+ * @returns {SeaTalkEntityType} The entity type.
256
+ */
257
+ getType() {
258
+ return this._type;
259
+ }
260
+
261
+ /**
262
+ * Validates the entity instance.
263
+ * @returns {boolean} True if the entity is valid.
264
+ * @throws {Error} If the entity is invalid.
265
+ */
266
+ validate() {
267
+ if (!isValidSeaTalkId(this._id)) {
268
+ throw new Error(`Validation Error: Invalid ID "${this._id}" for ${this._type} entity.`);
269
+ }
270
+ if (!Object.values(SeaTalkEntityTypes).includes(this._type)) {
271
+ throw new Error(`Validation Error: Invalid internal type "${this._type}" for entity ID "${this._id}".`);
272
+ }
273
+ return true;
274
+ }
275
+
276
+ /**
277
+ * Converts the entity instance to a plain JavaScript object.
278
+ * Should be overridden by subclasses.
279
+ * @returns {object} A plain object representation of the entity.
280
+ */
281
+ toJSON() {
282
+ return {
283
+ id: this._id,
284
+ type: this._type,
285
+ };
286
+ }
287
+ }
288
+
289
+ // --- Specific Entity Components ---
290
+
291
+ /**
292
+ * Represents a SeaTalk User entity.
293
+ */
294
+ class SeaTalkUser extends SeaTalkEntity {
295
+ /**
296
+ * @param {object} data - The raw data object for the user.
297
+ * @param {string} data.id - The unique user ID.
298
+ * @param {string} data.name - The user's name.
299
+ * @param {string} [data.displayName] - The user's display name (if different).
300
+ * @param {string} [data.avatarUrl] - URL to the user's avatar image.
301
+ * @param {boolean} [data.isActive=true] - Whether the user account is active.
302
+ * @throws {Error} If required properties are missing or invalid.
303
+ */
304
+ constructor(data) {
305
+ super(data, SeaTalkEntityTypes.USER);
306
+
307
+ if (!isString(data.name) || data.name.length === 0) {
308
+ throw new Error(`Invalid or missing 'name' for SeaTalkUser ID "${this.getId()}".`);
309
+ }
310
+
311
+ /** @private */ this._name = data.name;
312
+ /** @private */ this._displayName = isString(data.displayName) && data.displayName.length > 0 ? data.displayName : data.name;
313
+ /** @private */ this._avatarUrl = isValidUrl(data.avatarUrl) ? data.avatarUrl : undefined;
314
+ /** @private */ this._isActive = isBoolean(data.isActive) ? data.isActive : true;
315
+ }
316
+
317
+ /** @returns {string} The user's name. */
318
+ getName() { return this._name; }
319
+ /** @returns {string} The user's display name. */
320
+ getDisplayName() { return this._displayName; }
321
+ /** @returns {string | undefined} The avatar URL or undefined. */
322
+ getAvatarUrl() { return this._avatarUrl; }
323
+ /** @returns {boolean} True if active. */
324
+ isActive() { return this._isActive; }
325
+
326
+ /**
327
+ * Validates the SeaTalkUser instance.
328
+ * @returns {boolean} True if the user is valid.
329
+ * @throws {Error} If the user is invalid.
330
+ */
331
+ validate() {
332
+ super.validate();
333
+ if (!isString(this._name) || this._name.length === 0) {
334
+ throw new Error(`Validation Error: Invalid name "${this._name}" for user "${this.getId()}".`);
335
+ }
336
+ if (!isString(this._displayName) || this._displayName.length === 0) {
337
+ throw new Error(`Validation Error: Invalid display name "${this._displayName}" for user "${this.getId()}".`);
338
+ }
339
+ if (this._avatarUrl !== undefined && !isValidUrl(this._avatarUrl)) {
340
+ throw new Error(`Validation Error: Invalid avatar URL "${this._avatarUrl}" for user "${this.getId()}".`);
341
+ }
342
+ if (!isBoolean(this._isActive)) {
343
+ throw new Error(`Validation Error: Invalid isActive status for user "${this.getId()}".`);
344
+ }
345
+ return true;
346
+ }
347
+
348
+ /** @returns {object} A plain object representation of the user. */
349
+ toJSON() {
350
+ return {
351
+ ...super.toJSON(),
352
+ name: this._name,
353
+ displayName: this._displayName,
354
+ avatarUrl: this._avatarUrl,
355
+ isActive: this._isActive,
356
+ };
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Represents a SeaTalk Team entity.
362
+ */
363
+ class SeaTalkTeam extends SeaTalkEntity {
364
+ /**
365
+ * @param {object} data - The raw data object for the team.
366
+ * @param {string} data.id - The unique team ID.
367
+ * @param {string} data.name - The team's name.
368
+ * @param {string} [data.description] - A description for the team.
369
+ * @throws {Error} If required properties are missing or invalid.
370
+ */
371
+ constructor(data) {
372
+ super(data, SeaTalkEntityTypes.TEAM);
373
+ if (!isString(data.name) || data.name.length === 0) {
374
+ throw new Error(`Invalid or missing 'name' for SeaTalkTeam ID "${this.getId()}".`);
375
+ }
376
+ /** @private */ this._name = data.name;
377
+ /** @private */ this._description = isString(data.description) ? data.description : undefined;
378
+ }
379
+ /** @returns {string} The team's name. */
380
+ getName() { return this._name; }
381
+ /** @returns {string | undefined} The team description. */
382
+ getDescription() { return this._description; }
383
+
384
+ /**
385
+ * Validates the SeaTalkTeam instance.
386
+ * @returns {boolean} True if the team is valid.
387
+ * @throws {Error} If the team is invalid.
388
+ */
389
+ validate() {
390
+ super.validate();
391
+ if (!isString(this._name) || this._name.length === 0) {
392
+ throw new Error(`Validation Error: Invalid name "${this._name}" for team "${this.getId()}".`);
393
+ }
394
+ if (this._description !== undefined && !isString(this._description)) {
395
+ throw new Error(`Validation Error: Invalid description type for team "${this.getId()}".`);
396
+ }
397
+ return true;
398
+ }
399
+ /** @returns {object} A plain object representation of the team. */
400
+ toJSON() {
401
+ return {
402
+ ...super.toJSON(),
403
+ name: this._name,
404
+ description: this._description,
405
+ };
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Represents a SeaTalk Channel entity.
411
+ */
412
+ class SeaTalkChannel extends SeaTalkEntity {
413
+ /**
414
+ * @param {object} data - The raw data object for the channel.
415
+ * @param {string} data.id - The unique channel ID.
416
+ * @param {string} data.name - The channel's name.
417
+ * @param {string} [data.topic] - A topic for the channel.
418
+ * @throws {Error} If required properties are missing or invalid.
419
+ */
420
+ constructor(data) {
421
+ super(data, SeaTalkEntityTypes.CHANNEL);
422
+ if (!isString(data.name) || data.name.length === 0) {
423
+ throw new Error(`Invalid or missing 'name' for SeaTalkChannel ID "${this.getId()}".`);
424
+ }
425
+ /** @private */ this._name = data.name;
426
+ /** @private */ this._topic = isString(data.topic) ? data.topic : undefined;
427
+ }
428
+ /** @returns {string} The channel's name. */
429
+ getName() { return this._name; }
430
+ /** @returns {string | undefined} The channel topic. */
431
+ getTopic() { return this._topic; }
432
+
433
+ /**
434
+ * Validates the SeaTalkChannel instance.
435
+ * @returns {boolean} True if the channel is valid.
436
+ * @throws {Error} If the channel is invalid.
437
+ */
438
+ validate() {
439
+ super.validate();
440
+ if (!isString(this._name) || this._name.length === 0) {
441
+ throw new Error(`Validation Error: Invalid name "${this._name}" for channel "${this.getId()}".`);
442
+ }
443
+ if (this._topic !== undefined && !isString(this._topic)) {
444
+ throw new Error(`Validation Error: Invalid topic type for channel "${this.getId()}".`);
445
+ }
446
+ return true;
447
+ }
448
+ /** @returns {object} A plain object representation of the channel. */
449
+ toJSON() {
450
+ return {
451
+ ...super.toJSON(),
452
+ name: this._name,
453
+ topic: this._topic,
454
+ };
455
+ }
456
+ }
457
+
458
+ // --- Base Message Class ---
459
+
460
+ /**
461
+ * Base class for all SeaTalk messages.
462
+ * Provides common properties like ID, sender, timestamp, and type.
463
+ */
464
+ class SeaTalkMessage {
465
+ /**
466
+ * @param {object} data - The raw data object for the message.
467
+ * @param {string} data.id - The unique identifier for the message.
468
+ * @param {string | object} data.sender - The ID string or raw object for the sender.
469
+ * @param {Date | number | string} [data.timestamp] - The timestamp when the message was sent (defaults to now).
470
+ * @param {SeaTalkMessageType} messageType - The specific type of the message.
471
+ * @throws {Error} If required properties are missing or invalid.
472
+ */
473
+ constructor(data, messageType) {
474
+ if (!isObject(data)) {
475
+ throw new Error(`Invalid initialization data for SeaTalkMessage. Expected object, got ${typeof data}.`);
476
+ }
477
+ if (!isValidSeaTalkId(data.id)) {
478
+ throw new Error(`Invalid or missing ID for SeaTalkMessage. ID: "${data.id}".`);
479
+ }
480
+ if (!isString(data.sender) && !isObject(data.sender)) {
481
+ throw new Error(`Invalid or missing sender for SeaTalkMessage ID "${data.id}". Sender: "${data.sender}".`);
482
+ }
483
+ if (!Object.values(SeaTalkMessageTypes).includes(messageType)) {
484
+ throw new Error(`Invalid message type "${messageType}" provided for SeaTalkMessage ID "${data.id}".`);
485
+ }
486
+
487
+ /** @private */ this._id = data.id;
488
+ /** @private */ this._sender = data.sender;
489
+ /** @private */ this._timestamp = (data.timestamp instanceof Date || isNumber(data.timestamp))
490
+ ? new Date(data.timestamp)
491
+ : (isString(data.timestamp) ? new Date(data.timestamp) : new Date());
492
+
493
+ if (isNaN(this._timestamp.getTime())) {
494
+ // If Date construction from string/number failed
495
+ throw new Error(`Invalid timestamp value "${data.timestamp}" for SeaTalkMessage ID "${data.id}".`);
496
+ }
497
+
498
+ /** @private */ this._type = messageType;
499
+ }
500
+
501
+ /** @returns {string} The unique identifier of the message. */
502
+ getId() { return this._id; }
503
+ /** @returns {string | object} The sender's ID string or raw object. */
504
+ getSender() { return this._sender; }
505
+ /** @returns {Date} The timestamp of the message. */
506
+ getTimestamp() { return this._timestamp; }
507
+ /** @returns {string | null} The formatted timestamp string. */
508
+ getFormattedTimestamp() { return formatSeaTalkTimestamp(this._timestamp); }
509
+ /** @returns {SeaTalkMessageType} The type of the message. */
510
+ getType() { return this._type; }
511
+
512
+ /**
513
+ * Checks if the message is of a specific type.
514
+ * @param {SeaTalkMessageType} type - The type to check against.
515
+ * @returns {boolean} True if the message type matches.
516
+ */
517
+ isOfType(type) {
518
+ return this._type === type;
519
+ }
520
+
521
+ /**
522
+ * Validates the SeaTalkMessage instance.
523
+ * @returns {boolean} True if the message is valid.
524
+ * @throws {Error} If the message is invalid.
525
+ */
526
+ validate() {
527
+ if (!isValidSeaTalkId(this._id)) {
528
+ throw new Error(`Validation Error: Invalid ID "${this._id}" for message of type "${this._type}".`);
529
+ }
530
+ if (!isString(this._sender) && !isObject(this._sender)) {
531
+ throw new Error(`Validation Error: Invalid sender "${this._sender}" for message ID "${this._id}".`);
532
+ }
533
+ if (isNaN(this._timestamp.getTime())) {
534
+ throw new Error(`Validation Error: Invalid timestamp for message ID "${this._id}".`);
535
+ }
536
+ if (!Object.values(SeaTalkMessageTypes).includes(this._type)) {
537
+ throw new Error(`Validation Error: Invalid internal type "${this._type}" for message ID "${this._id}".`);
538
+ }
539
+ return true;
540
+ }
541
+
542
+ /**
543
+ * Converts the message instance to a plain JavaScript object.
544
+ * @returns {object} A plain object representation.
545
+ */
546
+ toJSON() {
547
+ return {
548
+ id: this._id,
549
+ sender: this._sender, // Keep sender as is in base toJSON
550
+ timestamp: this._timestamp.toISOString(),
551
+ type: this._type,
552
+ };
553
+ }
554
+ }
555
+
556
+ // --- Specific Message Components ---
557
+
558
+ /**
559
+ * Represents a SeaTalk Text Message.
560
+ */
561
+ class SeaTalkTextMessage extends SeaTalkMessage {
562
+ /**
563
+ * @param {object} data - The raw data object for the text message.
564
+ * @param {string} data.id - The unique message ID.
565
+ * @param {string | object} data.sender - The ID string or raw object for the sender.
566
+ * @param {string} data.text - The text content.
567
+ * @param {Date | number | string} [data.timestamp] - The timestamp.
568
+ * @throws {Error} If required properties are missing or invalid.
569
+ */
570
+ constructor(data) {
571
+ super(data, SeaTalkMessageTypes.TEXT);
572
+
573
+ if (!isString(data.text) || data.text.length === 0) {
574
+ throw new Error(`Invalid or missing 'text' content for SeaTalkTextMessage ID "${this.getId()}".`);
575
+ }
576
+ if (data.text.length > MAX_TEXT_MESSAGE_LENGTH) {
577
+ throw new Error(`'text' content exceeds maximum length of ${MAX_TEXT_MESSAGE_LENGTH} for SeaTalkTextMessage ID "${this.getId()}".`);
578
+ }
579
+
580
+ /** @private */ this._text = data.text;
581
+ }
582
+
583
+ /** @returns {string} The text content. */
584
+ getText() { return this._text; }
585
+ /** @returns {string} The escaped text content. */
586
+ getEscapedText() { return escapeSeaTalkText(this._text); }
587
+
588
+ /**
589
+ * Validates the SeaTalkTextMessage instance.
590
+ * @returns {boolean} True if valid.
591
+ * @throws {Error} If invalid.
592
+ */
593
+ validate() {
594
+ super.validate();
595
+ if (!isString(this._text) || this._text.length === 0) {
596
+ throw new Error(`Validation Error: Invalid text content for text message "${this.getId()}".`);
597
+ }
598
+ if (this._text.length > MAX_TEXT_MESSAGE_LENGTH) {
599
+ throw new Error(`Validation Error: Text content exceeds max length for text message "${this.getId()}".`);
600
+ }
601
+ return true;
602
+ }
603
+ /** @returns {object} A plain object representation. */
604
+ toJSON() {
605
+ return {
606
+ ...super.toJSON(),
607
+ text: this._text,
608
+ };
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Represents a SeaTalk Card Message.
614
+ */
615
+ class SeaTalkCardMessage extends SeaTalkMessage {
616
+ /**
617
+ * @param {object} data - The raw data object for the card message.
618
+ * @param {string} data.id - The unique message ID.
619
+ * @param {string | object} data.sender - The ID string or raw object for the sender.
620
+ * @param {object} data.card - The card structure object.
621
+ * @param {string} data.card.title - The title of the card.
622
+ * @param {Array<object>} [data.card.sections] - Array of card sections.
623
+ * @param {Array<object>} [data.card.actions] - Array of card actions.
624
+ * @param {Date | number | string} [data.timestamp] - The timestamp.
625
+ * @throws {Error} If required properties are missing or invalid.
626
+ */
627
+ constructor(data) {
628
+ super(data, SeaTalkMessageTypes.CARD);
629
+
630
+ if (!isObject(data.card)) {
631
+ throw new Error(`Invalid or missing 'card' structure for SeaTalkCardMessage ID "${this.getId()}".`);
632
+ }
633
+ if (!isString(data.card.title) || data.card.title.length === 0) {
634
+ throw new Error(`Invalid or missing 'card.title' for SeaTalkCardMessage ID "${this.getId()}".`);
635
+ }
636
+ if (data.card.title.length > MAX_CARD_TITLE_LENGTH) {
637
+ throw new Error(`'card.title' exceeds maximum length of ${MAX_CARD_TITLE_LENGTH} for SeaTalkCardMessage ID "${this.getId()}".`);
638
+ }
639
+ if (data.card.sections !== undefined && (!isArray(data.card.sections) || data.card.sections.length > MAX_CARD_SECTIONS)) {
640
+ throw new Error(`Invalid or too many sections in 'card.sections' for SeaTalkCardMessage ID "${this.getId()}". Max ${MAX_CARD_SECTIONS}.`);
641
+ }
642
+ if (data.card.actions !== undefined && (!isArray(data.card.actions) || data.card.actions.length > MAX_CARD_ACTIONS)) {
643
+ throw new Error(`Invalid or too many actions in 'card.actions' for SeaTalkCardMessage ID "${this.getId()}". Max ${MAX_CARD_ACTIONS}.`);
644
+ }
645
+
646
+ /**
647
+ * @type {{title: string, sections: Array<object>, actions: Array<object>}}
648
+ * @private
649
+ */
650
+ this._card = {
651
+ title: data.card.title,
652
+ sections: isArray(data.card.sections) ? data.card.sections.map(s => ({...s})) : [], // Clone sections
653
+ actions: isArray(data.card.actions) ? data.card.actions.map(a => ({...a})) : [], // Clone actions
654
+ };
655
+
656
+ // Perform initial basic validation of structure components
657
+ this._validateSections(this._card.sections, this.getId());
658
+ this._validateActions(this._card.actions, this.getId());
659
+ }
660
+
661
+ /** @returns {{title: string, sections: Array<object>, actions: Array<object>}} The card structure. */
662
+ getCard() { return {...this._card, sections: [...this._card.sections], actions: [...this._card.actions]}; } // Return clones
663
+ /** @returns {string} The card title. */
664
+ getCardTitle() { return this._card.title; }
665
+ /** @returns {Array<object>} An array of card sections (cloned). */
666
+ getCardSections() { return [...this._card.sections]; }
667
+ /** @returns {Array<object>} An array of card actions (cloned). */
668
+ getCardActions() { return [...this._card.actions]; }
669
+
670
+ /**
671
+ * Adds a section to the card.
672
+ * @param {object} section - The section object to add.
673
+ * @throws {Error} If the section is invalid or exceeds max sections.
674
+ */
675
+ addSection(section) {
676
+ if (!isObject(section)) {
677
+ throw new Error(`Invalid section object provided for card message "${this.getId()}".`);
678
+ }
679
+ if (this._card.sections.length >= MAX_CARD_SECTIONS) {
680
+ throw new Error(`Cannot add section. Maximum number of sections (${MAX_CARD_SECTIONS}) reached for card message "${this.getId()}".`);
681
+ }
682
+ // Perform basic validation on the section before adding
683
+ try {
684
+ this._validateSections([section], this.getId(), this._card.sections.length);
685
+ } catch (e) {
686
+ throw new Error(`Validation Error adding section to card message "${this.getId()}": ${e.message}`);
687
+ }
688
+ this._card.sections.push({...section}); // Add a clone
689
+ }
690
+
691
+ /**
692
+ * Adds an action to the card.
693
+ * @param {object} action - The action object to add. Must have 'type' and 'text'.
694
+ * @throws {Error} If the action is invalid or exceeds max actions.
695
+ */
696
+ addAction(action) {
697
+ if (!isObject(action) || !isString(action.type) || action.type.length === 0 || !isString(action.text) || action.text.length === 0) {
698
+ throw new Error(`Invalid action object provided for card message "${this.getId()}". Must have non-empty 'type' and 'text'.`);
699
+ }
700
+ if (this._card.actions.length >= MAX_CARD_ACTIONS) {
701
+ throw new Error(`Cannot add action. Maximum number of actions (${MAX_CARD_ACTIONS}) reached for card message "${this.getId()}".`);
702
+ }
703
+ // Perform basic validation on the action before adding
704
+ try {
705
+ this._validateActions([action], this.getId(), this._card.actions.length);
706
+ } catch (e) {
707
+ throw new Error(`Validation Error adding action to card message "${this.getId()}": ${e.message}`);
708
+ }
709
+ this._card.actions.push({...action}); // Add a clone
710
+ }
711
+
712
+ /**
713
+ * Internal helper to validate an array of card sections.
714
+ * @private
715
+ * @param {Array<object>} sections - The sections array.
716
+ * @param {string} messageId - The ID of the parent message.
717
+ * @param {number} [startIndex=0] - The starting index for error reporting.
718
+ * @throws {Error} If validation fails.
719
+ */
720
+ _validateSections(sections, messageId, startIndex = 0) {
721
+ if (!isArray(sections)) throw new Error('Internal Error: Sections must be an array.');
722
+ sections.forEach((section, index) => {
723
+ const currentIndex = startIndex + index;
724
+ if (!isObject(section)) {
725
+ throw new Error(`Invalid section structure at index ${currentIndex} for card message ID "${messageId}". Expected object.`);
726
+ }
727
+ // Example section validation: must have text or image or fields
728
+ const hasText = isString(section.text) && section.text.length > 0;
729
+ const hasImage = isObject(section.image);
730
+ const hasFields = isArray(section.fields) && section.fields.length > 0;
731
+
732
+ if (!hasText && !hasImage && !hasFields) {
733
+ throw new Error(`Section at index ${currentIndex} must contain 'text', 'image', or 'fields' for card message ID "${messageId}".`);
734
+ }
735
+
736
+ // More detailed validation for section properties
737
+ if (section.text !== undefined && !isString(section.text)) {
738
+ throw new Error(`Invalid 'text' property type in section at index ${currentIndex} for card message ID "${messageId}". Expected string.`);
739
+ }
740
+ if (hasImage) {
741
+ if (!isString(section.image.url) || !isValidUrl(section.image.url)) {
742
+ throw new Error(`Invalid or missing 'url' property in 'image' object in section at index ${currentIndex} for card message ID "${messageId}". Expected valid URL string.`);
743
+ }
744
+ if (section.image.altText !== undefined && !isString(section.image.altText)) {
745
+ throw new Error(`Invalid 'altText' property type in 'image' object in section at index ${currentIndex} for card message ID "${messageId}". Expected string.`);
746
+ }
747
+ }
748
+ if (hasFields) {
749
+ section.fields.forEach((field, fieldIndex) => {
750
+ if (!isObject(field) || !isString(field.title) || field.title.length === 0 || !isString(field.value) || field.value.length === 0) {
751
+ throw new Error(`Invalid field structure at index ${fieldIndex} in section ${currentIndex} for card message ID "${messageId}". Must have non-empty 'title' and 'value'.`);
752
+ }
753
+ });
754
+ }
755
+ });
756
+ }
757
+
758
+ /**
759
+ * Internal helper to validate an array of card actions.
760
+ * @private
761
+ * @param {Array<object>} actions - The actions array.
762
+ * @param {string} messageId - The ID of the parent message.
763
+ * @param {number} [startIndex=0] - The starting index for error reporting.
764
+ * @throws {Error} If validation fails.
765
+ */
766
+ _validateActions(actions, messageId, startIndex = 0) {
767
+ if (!isArray(actions)) throw new Error('Internal Error: Actions must be an array.');
768
+ actions.forEach((action, index) => {
769
+ const currentIndex = startIndex + index;
770
+ if (!isObject(action) || !isString(action.type) || action.type.length === 0 || !isString(action.text) || action.text.length === 0) {
771
+ throw new Error(`Invalid action structure at index ${currentIndex} for card message ID "${messageId}". Must have non-empty 'type' and 'text'.`);
772
+ }
773
+ // Detailed validation based on action type
774
+ if (action.type === 'open_url') {
775
+ if (!isString(action.url) || !isValidUrl(action.url)) {
776
+ throw new Error(`Invalid or missing 'url' property for 'open_url' action at index ${currentIndex} for card message ID "${messageId}". Expected valid URL string.`);
777
+ }
778
+ }
779
+ // Add validation for other action types here as needed (e.g., 'submit', 'message', etc.)
780
+ });
781
+ }
782
+
783
+
784
+ /**
785
+ * Validates the SeaTalkCardMessage instance and its card structure.
786
+ * @returns {boolean} True if valid.
787
+ * @throws {Error} If invalid.
788
+ */
789
+ validate() {
790
+ super.validate();
791
+ if (!isObject(this._card)) {
792
+ throw new Error(`Validation Error: Invalid internal card object for card message "${this.getId()}".`);
793
+ }
794
+ if (!isString(this._card.title) || this._card.title.length === 0) {
795
+ throw new Error(`Validation Error: Invalid card title "${this._card.title}" for card message "${this.getId()}".`);
796
+ }
797
+ if (this._card.title.length > MAX_CARD_TITLE_LENGTH) {
798
+ throw new Error(`Validation Error: Card title exceeds max length for card message "${this.getId()}".`);
799
+ }
800
+ if (!isArray(this._card.sections)) {
801
+ throw new Error(`Validation Error: Invalid internal sections array for card message "${this.getId()}".`);
802
+ }
803
+ if (this._card.sections.length > MAX_CARD_SECTIONS) {
804
+ throw new Error(`Validation Error: Too many sections for card message "${this.getId()}".`);
805
+ }
806
+ // Validate all current sections
807
+ this._validateSections(this._card.sections, this.getId());
808
+
809
+ if (!isArray(this._card.actions)) {
810
+ throw new Error(`Validation Error: Invalid internal actions array for card message "${this.getId()}".`);
811
+ }
812
+ if (this._card.actions.length > MAX_CARD_ACTIONS) {
813
+ throw new Error(`Validation Error: Too many actions for card message "${this.getId()}".`);
814
+ }
815
+ // Validate all current actions
816
+ this._validateActions(this._card.actions, this.getId());
817
+
818
+ return true;
819
+ }
820
+
821
+ /** @returns {object} A plain object representation. */
822
+ toJSON() {
823
+ return {
824
+ ...super.toJSON(),
825
+ card: {
826
+ title: this._card.title,
827
+ sections: this._card.sections.map(s => ({...s})), // Return clones
828
+ actions: this._card.actions.map(a => ({...a})), // Return clones
829
+ },
830
+ };
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Represents a SeaTalk File Message.
836
+ */
837
+ class SeaTalkFileMessage extends SeaTalkMessage {
838
+ /**
839
+ * @param {object} data - The raw data object for the file message.
840
+ * @param {string} data.id - The unique message ID.
841
+ * @param {string | object} data.sender - The ID string or raw object for the sender.
842
+ * @param {object} data.file - The file details object.
843
+ * @param {string} data.file.name - The name of the file.
844
+ * @param {number} data.file.size - The size of the file in bytes.
845
+ * @param {string} data.file.url - The URL to the file.
846
+ * @param {string} [data.file.mimeType] - The MIME type.
847
+ * @param {Date | number | string} [data.timestamp] - The timestamp.
848
+ * @throws {Error} If required properties are missing or invalid.
849
+ */
850
+ constructor(data) {
851
+ super(data, SeaTalkMessageTypes.FILE);
852
+
853
+ if (!isObject(data.file)) {
854
+ throw new Error(`Invalid or missing 'file' structure for SeaTalkFileMessage ID "${this.getId()}".`);
855
+ }
856
+ if (!isString(data.file.name) || data.file.name.length === 0) {
857
+ throw new Error(`Invalid or missing 'file.name' for SeaTalkFileMessage ID "${this.getId()}".`);
858
+ }
859
+ if (!isNumber(data.file.size) || data.file.size < 0) {
860
+ throw new Error(`Invalid or missing 'file.size' for SeaTalkFileMessage ID "${this.getId()}". Must be a non-negative number.`);
861
+ }
862
+ if (!isString(data.file.url) || !isValidUrl(data.file.url)) {
863
+ throw new Error(`Invalid or missing 'file.url' for SeaTalkFileMessage ID "${this.getId()}".`);
864
+ }
865
+ if (data.file.mimeType !== undefined && !isString(data.file.mimeType)) {
866
+ throw new Error(`Invalid 'file.mimeType' type for SeaTalkFileMessage ID "${this.getId()}". Expected string.`);
867
+ }
868
+
869
+ /**
870
+ * @type {{name: string, size: number, url: string, mimeType?: string}}
871
+ * @private
872
+ */
873
+ this._file = {
874
+ name: data.file.name,
875
+ size: data.file.size,
876
+ url: data.file.url,
877
+ mimeType: isString(data.file.mimeType) ? data.file.mimeType : undefined,
878
+ };
879
+ }
880
+
881
+ /** @returns {{name: string, size: number, url: string, mimeType?: string}} The file details. */
882
+ getFile() { return {...this._file}; } // Return clone
883
+ /** @returns {string} The file name. */
884
+ getFileName() { return this._file.name; }
885
+ /** @returns {number} The file size. */
886
+ getFileSize() { return this._file.size; }
887
+ /** @returns {string} The file URL. */
888
+ getFileUrl() { return this._file.url; }
889
+ /** @returns {string | undefined} The file MIME type or undefined. */
890
+ getFileMimeType() { return this._file.mimeType; }
891
+
892
+ /**
893
+ * Validates the SeaTalkFileMessage instance and its file structure.
894
+ * @returns {boolean} True if valid.
895
+ * @throws {Error} If invalid.
896
+ */
897
+ validate() {
898
+ super.validate();
899
+ if (!isObject(this._file)) {
900
+ throw new Error(`Validation Error: Invalid internal file object for file message "${this.getId()}".`);
901
+ }
902
+ if (!isString(this._file.name) || this._file.name.length === 0) {
903
+ throw new Error(`Validation Error: Invalid file name "${this._file.name}" for file message "${this.getId()}".`);
904
+ }
905
+ if (!isNumber(this._file.size) || this._file.size < 0) {
906
+ throw new Error(`Validation Error: Invalid file size "${this._file.size}" for file message "${this.getId()}".`);
907
+ }
908
+ if (!isString(this._file.url) || !isValidUrl(this._file.url)) {
909
+ throw new Error(`Validation Error: Invalid file URL "${this._file.url}" for file message "${this.getId()}".`);
910
+ }
911
+ if (this._file.mimeType !== undefined && !isString(this._file.mimeType)) {
912
+ throw new Error(`Validation Error: Invalid file mimeType "${this._file.mimeType}" for file message "${this.getId()}".`);
913
+ }
914
+ return true;
915
+ }
916
+
917
+ /** @returns {object} A plain object representation. */
918
+ toJSON() {
919
+ return {
920
+ ...super.toJSON(),
921
+ file: {...this._file}, // Return clone
922
+ };
923
+ }
924
+ }
925
+
926
+
927
+ // --- Factory / Parser Functions ---
928
+
929
+ /**
930
+ * Attempts to parse a raw data object into a specific SeaTalk message class instance.
931
+ * It requires the 'type' property to determine which class to instantiate.
932
+ * @param {object} rawData - The raw data object received, potentially from an API.
933
+ * @returns {SeaTalkMessage | SeaTalkTextMessage | SeaTalkCardMessage | SeaTalkFileMessage | null} An instance of the appropriate message class, or null if parsing fails or type is unknown/invalid.
934
+ * @throws {Error} If basic message properties (id, sender, type) are missing or invalid.
935
+ */
936
+ function parseSeaTalkMessage(rawData) {
937
+ if (!isObject(rawData)) {
938
+ console.error('parseSeaTalkMessage Error: Input is not an object.');
939
+ throw new Error('parseSeaTalkMessage Error: Input is not an object.');
940
+ }
941
+ if (!isValidSeaTalkId(rawData.id)) {
942
+ console.error(`parseSeaTalkMessage Error: Invalid or missing message ID "${rawData.id}".`);
943
+ throw new Error(`parseSeaTalkMessage Error: Invalid or missing message ID "${rawData.id}".`);
944
+ }
945
+ if (!isString(rawData.sender) && !isObject(rawData.sender)) {
946
+ console.error(`parseSeaTalkMessage Error: Invalid or missing message sender for ID "${rawData.id}".`);
947
+ throw new Error(`parseSeaTalkMessage Error: Invalid or missing message sender for ID "${rawData.id}".`);
948
+ }
949
+ if (!Object.values(SeaTalkMessageTypes).includes(rawData.type)) {
950
+ console.warn(`parseSeaTalkMessage Warning: Unknown or invalid message type "${rawData.type}" for message ID "${rawData.id}". Cannot parse into specific type.`);
951
+ return null; // Return null for unknown types
952
+ }
953
+
954
+ try {
955
+ switch (rawData.type) {
956
+ case SeaTalkMessageTypes.TEXT:
957
+ return new SeaTalkTextMessage(rawData);
958
+ case SeaTalkMessageTypes.CARD:
959
+ return new SeaTalkCardMessage(rawData);
960
+ case SeaTalkMessageTypes.FILE:
961
+ return new SeaTalkFileMessage(rawData);
962
+ case SeaTalkMessageTypes.UNKNOWN:
963
+ console.warn(`parseSeaTalkMessage Warning: Explicitly unknown message type for ID "${rawData.id}". Returning null.`);
964
+ return null;
965
+ default:
966
+ // This case should be covered by the initial type check, but as a safeguard
967
+ console.warn(`parseSeaTalkMessage Warning: Unhandled message type "${rawData.type}" for ID "${rawData.id}". Returning null.`);
968
+ return null;
969
+ }
970
+ } catch (error) {
971
+ // Catch errors during specific class construction (e.g., detailed validation errors)
972
+ console.error(`parseSeaTalkMessage Error: Failed to construct message instance for ID "${rawData.id}" with type "${rawData.type}".`, error.message);
973
+ throw new Error(`parseSeaTalkMessage Error: Failed to construct message instance for ID "${rawData.id}" with type "${rawData.type}". Details: ${error.message}`);
974
+ }
975
+ }
976
+
977
+ /**
978
+ * Attempts to parse a raw data object into a specific SeaTalk entity class instance.
979
+ * Requires the 'type' property to determine which class to instantiate.
980
+ * @param {object} rawData - The raw data object.
981
+ * @returns {SeaTalkEntity | SeaTalkUser | SeaTalkTeam | SeaTalkChannel | null} An instance of the appropriate entity class, or null if parsing fails or type is unknown/invalid.
982
+ * @throws {Error} If basic entity properties (id, type) are missing or invalid.
983
+ */
984
+ function parseSeaTalkEntity(rawData) {
985
+ if (!isObject(rawData)) {
986
+ console.error('parseSeaTalkEntity Error: Input is not an object.');
987
+ throw new Error('parseSeaTalkEntity Error: Input is not an object.');
988
+ }
989
+ if (!isValidSeaTalkId(rawData.id)) {
990
+ console.error(`parseSeaTalkEntity Error: Invalid or missing entity ID "${rawData.id}".`);
991
+ throw new Error(`parseSeaTalkEntity Error: Invalid or missing entity ID "${rawData.id}".`);
992
+ }
993
+ if (!Object.values(SeaTalkEntityTypes).includes(rawData.type)) {
994
+ console.warn(`parseSeaTalkEntity Warning: Unknown or invalid entity type "${rawData.type}" for entity ID "${rawData.id}". Cannot parse into specific type.`);
995
+ return null; // Return null for unknown types
996
+ }
997
+
998
+ try {
999
+ switch (rawData.type) {
1000
+ case SeaTalkEntityTypes.USER:
1001
+ return new SeaTalkUser(rawData);
1002
+ case SeaTalkEntityTypes.TEAM:
1003
+ return new SeaTalkTeam(rawData);
1004
+ case SeaTalkEntityTypes.CHANNEL:
1005
+ return new SeaTalkChannel(rawData);
1006
+ case SeaTalkEntityTypes.UNKNOWN:
1007
+ console.warn(`parseSeaTalkEntity Warning: Explicitly unknown entity type for ID "${rawData.id}". Returning base entity or null.`);
1008
+ try {
1009
+ return new SeaTalkEntity(rawData); // Return base if explicitly unknown type is given
1010
+ } catch (e) {
1011
+ console.error(`parseSeaTalkEntity Error: Failed to construct base entity for ID "${rawData.id}".`, e.message);
1012
+ return null; // Return null if even base construction fails
1013
+ }
1014
+ default:
1015
+ // This case should be covered by the initial type check, but as a safeguard
1016
+ console.warn(`parseSeaTalkEntity Warning: Unhandled entity type "${rawData.type}" for ID "${rawData.id}". Returning null.`);
1017
+ return null;
1018
+ }
1019
+ } catch (error) {
1020
+ // Catch errors during specific class construction (e.g., detailed validation errors)
1021
+ console.error(`parseSeaTalkEntity Error: Failed to construct entity instance for ID "${rawData.id}" with type "${rawData.type}".`, error.message);
1022
+ throw new Error(`parseSeaTalkEntity Error: Failed to construct entity instance for ID "${rawData.id}" with type "${rawData.type}". Details: ${error.message}`);
1023
+ }
1024
+ }
1025
+
1026
+
1027
+ // --- Main Export ---
1028
+
1029
+ /**
1030
+ * @namespace SeaTalkComponents
1031
+ * @description Provides data structures, utilities, and validation for SeaTalk-related components.
1032
+ */
1033
+ const SeaTalkComponents = {
1034
+ // Constants
1035
+ SeaTalkMessageTypes,
1036
+ SeaTalkEntityTypes,
1037
+ MAX_TEXT_MESSAGE_LENGTH,
1038
+ MAX_CARD_TITLE_LENGTH,
1039
+ MAX_CARD_SECTIONS,
1040
+ MAX_CARD_ACTIONS,
1041
+
1042
+ // Utility Functions
1043
+ isString,
1044
+ isNumber,
1045
+ isBoolean,
1046
+ isArray,
1047
+ isObject,
1048
+ isValidUrl,
1049
+ isValidSeaTalkId,
1050
+ formatSeaTalkTimestamp,
1051
+ escapeSeaTalkText,
1052
+
1053
+ // Entity Classes
1054
+ SeaTalkEntity,
1055
+ SeaTalkUser,
1056
+ SeaTalkTeam,
1057
+ SeaTalkChannel,
1058
+
1059
+ // Message Classes
1060
+ SeaTalkMessage,
1061
+ SeaTalkTextMessage,
1062
+ SeaTalkCardMessage,
1063
+ SeaTalkFileMessage,
1064
+
1065
+ // Parsing/Factory Functions
1066
+ parseSeaTalkMessage,
1067
+ parseSeaTalkEntity,
1068
+ };
1069
+
1070
+ // Export the main object
1071
+ module.exports = SeaTalkComponents;
package/package.json ADDED
@@ -0,0 +1 @@
1
+ {"author":"","description":"seatalk-components: A collection of reusable UI components for building modern web applications.","keywords":[],"license":"MIT","main":"index.js","name":"seatalk-components","scripts":{"test":"echo \"test\" \u0026\u0026 exit 1"},"version":"0.0.0-alpha1"}