stream-chat 9.5.1 → 9.6.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.
@@ -10,6 +10,7 @@ export * from './insights';
10
10
  export * from './messageComposer';
11
11
  export * from './middleware';
12
12
  export * from './moderation';
13
+ export * from './notifications';
13
14
  export * from './pagination';
14
15
  export * from './permissions';
15
16
  export * from './poll';
@@ -29,9 +29,10 @@ export type LocalMessageWithLegacyThreadId = LocalMessage & {
29
29
  export type CompositionContext = Channel | Thread | LocalMessageWithLegacyThreadId;
30
30
  export type MessageComposerState = {
31
31
  id: string;
32
- quotedMessage: LocalMessageBase | null;
33
- pollId: string | null;
34
32
  draftId: string | null;
33
+ pollId: string | null;
34
+ quotedMessage: LocalMessageBase | null;
35
+ showReplyInChannel: boolean;
35
36
  };
36
37
  export type MessageComposerOptions = {
37
38
  client: StreamChat;
@@ -54,12 +55,12 @@ export declare class MessageComposer extends WithSubscriptions {
54
55
  pollComposer: PollComposer;
55
56
  customDataManager: CustomDataManager;
56
57
  constructor({ composition, config, compositionContext, client, }: MessageComposerOptions);
57
- static evaluateContextType(compositionContext: CompositionContext): "channel" | "thread" | "legacy_thread" | "message";
58
+ static evaluateContextType(compositionContext: CompositionContext): "message" | "channel" | "thread" | "legacy_thread";
58
59
  static constructTag(compositionContext: CompositionContext): `${ReturnType<typeof MessageComposer.evaluateContextType>}_${string}`;
59
60
  get config(): MessageComposerConfig;
60
61
  updateConfig(config: DeepPartial<MessageComposerConfig>): void;
61
- get contextType(): "channel" | "thread" | "legacy_thread" | "message";
62
- get tag(): `channel_${string}` | `thread_${string}` | `legacy_thread_${string}` | `message_${string}`;
62
+ get contextType(): "message" | "channel" | "thread" | "legacy_thread";
63
+ get tag(): `message_${string}` | `channel_${string}` | `thread_${string}` | `legacy_thread_${string}`;
63
64
  get threadId(): string | null;
64
65
  get client(): StreamChat;
65
66
  get id(): string;
@@ -67,6 +68,7 @@ export declare class MessageComposer extends WithSubscriptions {
67
68
  get lastChange(): LastComposerChange;
68
69
  get quotedMessage(): LocalMessageBase | null;
69
70
  get pollId(): string | null;
71
+ get showReplyInChannel(): boolean;
70
72
  get hasSendableData(): boolean;
71
73
  get compositionIsEmpty(): boolean;
72
74
  get lastChangeOriginIsLocal(): boolean;
@@ -91,6 +93,7 @@ export declare class MessageComposer extends WithSubscriptions {
91
93
  private subscribeMessageComposerStateChanged;
92
94
  private subscribeMessageComposerConfigStateChanged;
93
95
  setQuotedMessage: (quotedMessage: LocalMessage | null) => void;
96
+ toggleShowReplyInChannel: () => void;
94
97
  clear: () => void;
95
98
  restore: () => void;
96
99
  compose: () => Promise<MessageComposerMiddlewareValue["state"] | undefined>;
@@ -0,0 +1,3 @@
1
+ import type { MessageComposer } from '../../messageComposer';
2
+ import type { MessageCompositionMiddleware } from './types';
3
+ export declare const createUserDataInjectionMiddleware: (composer: MessageComposer) => MessageCompositionMiddleware;
@@ -0,0 +1,2 @@
1
+ import type { NotificationManagerConfig } from './types';
2
+ export declare const DEFAULT_NOTIFICATION_MANAGER_CONFIG: NotificationManagerConfig;
@@ -28,38 +28,77 @@ export type Notification = {
28
28
  * The identifier then can be recognized by notification consumers to act upon specific origin values.
29
29
  */
30
30
  origin: NotificationOrigin;
31
- /** Optional timestamp when notification should expire */
32
- expiresAt?: number;
33
- /** Whether notification should automatically close after duration. Defaults to true */
34
- autoClose?: boolean;
35
31
  /** Array of action buttons for the notification */
36
32
  actions?: NotificationAction[];
33
+ /**
34
+ * Optional code that can be used to group the notifications of the same type, e.g. attachment-upload-blocked.
35
+ * Format: domain:entity:operation:result
36
+ * domain: where the error occurred (api, validation, permission, etc)
37
+ * entity: what was being operated on (poll, attachment, message, etc)
38
+ * operation: what was being attempted (create, upload, validate, etc)
39
+ * result: what happened (failed, blocked, invalid, etc)
40
+ *
41
+ * Poll related errors
42
+ * 'api:poll:create:failed' // API call to create poll failed
43
+ * 'validation:poll:create:invalid' // Poll creation validation failed
44
+ *
45
+ * Attachment related errors
46
+ * 'validation:attachment:file:missing' // Required file is missing
47
+ * 'permission:attachment:upload:blocked' // Upload blocked due to permissions
48
+ * 'api:attachment:upload:failed' // API upload call failed
49
+ * 'validation:attachment:type:unsupported' // Unsupported file type
50
+ * 'validation:attachment:size:exceeded' // File size too large
51
+ * 'validation:attachment:count:exceeded' // Too many attachments
52
+ *
53
+ * Message related errors
54
+ * 'api:message:send:failed' // Message send failed
55
+ * 'validation:message:content:empty' // Message content validation failed
56
+ *
57
+ * Channel related errors
58
+ * 'api:channel:join:failed' // Channel join failed
59
+ * 'permission:channel:access:denied' // Channel access denied
60
+ *
61
+ * Authentication related errors
62
+ * 'auth:token:expired' // Auth token expired
63
+ * 'auth:token:invalid' // Invalid auth token
64
+ *
65
+ * Network related errors
66
+ * 'network:request:timeout' // Request timed out
67
+ * 'network:request:failed' // Network request failed
68
+ *
69
+ * Rate limiting
70
+ * 'rate:limit:exceeded' // Rate limit exceeded
71
+ *
72
+ * System errors
73
+ * 'system:internal:error' // Internal system error
74
+ * 'system:resource:unavailable'; // System resource unavailable
75
+ */
76
+ type?: string;
77
+ /** Optional timestamp when notification should expire */
78
+ expiresAt?: number;
37
79
  /** Optional metadata to attach to the notification */
38
80
  metadata?: Record<string, unknown>;
81
+ /** In case of error notification the instance of the originally thrown error */
82
+ originalError?: Error;
39
83
  };
40
84
  /** Configuration options when creating a notification */
41
- export type NotificationOptions = {
42
- /** The severity level. Defaults to 'info' */
43
- severity?: NotificationSeverity;
44
- /** How long notification should display in milliseconds */
85
+ export type NotificationOptions = Partial<Pick<Notification, 'type' | 'severity' | 'actions' | 'metadata' | 'originalError'>> & {
86
+ /** How long a notification should be displayed in milliseconds */
45
87
  duration?: number;
46
- /** Whether notification should auto-close after duration. Defaults to true */
47
- autoClose?: boolean;
48
- /** Array of action buttons for the notification */
49
- actions?: NotificationAction[];
50
- /** Optional metadata to attach to the notification */
51
- metadata?: Record<string, unknown>;
52
88
  };
53
- /** State shape for the notification store */
89
+ /**
90
+ * State shape for the notification store
91
+ * @deprcated use NotificationManagerState
92
+ */
54
93
  export type NotificationState = {
55
94
  /** Array of current notification objects */
56
95
  notifications: Notification[];
57
96
  };
97
+ /** State shape for the notification store */
98
+ export type NotificationManagerState = NotificationState;
58
99
  export type NotificationManagerConfig = {
59
100
  durations: Record<NotificationSeverity, number>;
60
101
  };
61
- export type AddNotificationPayload = {
62
- message: string;
63
- origin: NotificationOrigin;
102
+ export type AddNotificationPayload = Pick<Notification, 'message' | 'origin'> & {
64
103
  options?: NotificationOptions;
65
104
  };
@@ -11,7 +11,7 @@ export declare class PollManager extends WithSubscriptions {
11
11
  get data(): Map<string, Poll>;
12
12
  fromState: (id: string) => Poll | undefined;
13
13
  registerSubscriptions: () => void;
14
- createPoll: (poll: CreatePollData) => Promise<Poll>;
14
+ createPoll: (poll: CreatePollData) => Promise<Poll | undefined>;
15
15
  getPoll: (id: string) => Promise<Poll | undefined>;
16
16
  queryPolls: (filter: QueryPollsFilters, sort?: PollSort, options?: QueryPollsOptions) => Promise<{
17
17
  polls: (Poll | undefined)[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stream-chat",
3
- "version": "9.5.1",
3
+ "version": "9.6.0",
4
4
  "description": "JS SDK for the Stream Chat API",
5
5
  "homepage": "https://getstream.io/chat/",
6
6
  "author": {
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from './insights';
10
10
  export * from './messageComposer';
11
11
  export * from './middleware';
12
12
  export * from './moderation';
13
+ export * from './notifications';
13
14
  export * from './pagination';
14
15
  export * from './permissions';
15
16
  export * from './poll';
@@ -386,12 +386,11 @@ export class AttachmentManager {
386
386
  this.client.notifications.addError({
387
387
  message: 'File is required for upload attachment',
388
388
  origin: { emitter: 'AttachmentManager', context: { attachment } },
389
+ options: { type: 'validation:attachment:file:missing' },
389
390
  });
390
391
  return;
391
392
  }
392
393
 
393
- // todo: document this
394
- // the following is substitute for: if (noFiles && !isImage) return att
395
394
  if (!this.fileUploadFilter(attachment)) return;
396
395
 
397
396
  const newAttachment = await this.fileToLocalUploadAttachment(
@@ -453,8 +452,17 @@ export class AttachmentManager {
453
452
  if (localAttachment.localMetadata.uploadState === 'blocked') {
454
453
  this.upsertAttachments([localAttachment]);
455
454
  this.client.notifications.addError({
456
- message: 'Error uploading attachment',
457
- origin: { emitter: 'AttachmentManager', context: { attachment } },
455
+ message: `The attachment upload was blocked`,
456
+ origin: {
457
+ emitter: 'AttachmentManager',
458
+ context: { attachment, blockedAttachment: localAttachment },
459
+ },
460
+ options: {
461
+ type: 'validation:attachment:upload:blocked',
462
+ metadata: {
463
+ reason: localAttachment.localMetadata.uploadPermissionCheck?.reason,
464
+ },
465
+ },
458
466
  });
459
467
  return localAttachment;
460
468
  }
@@ -473,21 +481,7 @@ export class AttachmentManager {
473
481
  try {
474
482
  response = await this.doUploadRequest(localAttachment.localMetadata.file);
475
483
  } catch (error) {
476
- let finalError: Error = {
477
- message: 'Error uploading attachment',
478
- name: 'Error',
479
- };
480
- if (typeof (error as Error).message === 'string') {
481
- finalError = error as Error;
482
- } else if (typeof error === 'object') {
483
- finalError = Object.assign(finalError, error);
484
- }
485
-
486
- this.client.notifications.addError({
487
- message: finalError.message,
488
- origin: { emitter: 'AttachmentManager', context: { attachment } },
489
- });
490
-
484
+ const reason = error instanceof Error ? error.message : 'unknown error';
491
485
  const failedAttachment: LocalUploadAttachment = {
492
486
  ...attachment,
493
487
  localMetadata: {
@@ -496,6 +490,19 @@ export class AttachmentManager {
496
490
  },
497
491
  };
498
492
 
493
+ this.client.notifications.addError({
494
+ message: 'Error uploading attachment',
495
+ origin: {
496
+ emitter: 'AttachmentManager',
497
+ context: { attachment, failedAttachment },
498
+ },
499
+ options: {
500
+ type: 'api:attachment:upload:failed',
501
+ metadata: { reason },
502
+ originalError: error instanceof Error ? error : undefined,
503
+ },
504
+ });
505
+
499
506
  this.updateAttachment(failedAttachment);
500
507
  return failedAttachment;
501
508
  }
@@ -42,9 +42,10 @@ export type CompositionContext = Channel | Thread | LocalMessageWithLegacyThread
42
42
 
43
43
  export type MessageComposerState = {
44
44
  id: string;
45
- quotedMessage: LocalMessageBase | null;
46
- pollId: string | null;
47
45
  draftId: string | null;
46
+ pollId: string | null;
47
+ quotedMessage: LocalMessageBase | null;
48
+ showReplyInChannel: boolean;
48
49
  };
49
50
 
50
51
  export type MessageComposerOptions = {
@@ -82,10 +83,11 @@ const initState = (
82
83
  ): MessageComposerState => {
83
84
  if (!composition) {
84
85
  return {
86
+ draftId: null,
85
87
  id: MessageComposer.generateId(),
86
- quotedMessage: null,
87
88
  pollId: null,
88
- draftId: null,
89
+ quotedMessage: null,
90
+ showReplyInChannel: false,
89
91
  };
90
92
  }
91
93
 
@@ -104,10 +106,11 @@ const initState = (
104
106
  return {
105
107
  draftId,
106
108
  id,
109
+ pollId: message.poll_id ?? null,
107
110
  quotedMessage: quotedMessage
108
111
  ? formatMessage(quotedMessage as MessageResponseBase)
109
112
  : null,
110
- pollId: message.poll_id ?? null,
113
+ showReplyInChannel: false,
111
114
  };
112
115
  };
113
116
 
@@ -274,6 +277,10 @@ export class MessageComposer extends WithSubscriptions {
274
277
  return this.state.getLatestValue().pollId;
275
278
  }
276
279
 
280
+ get showReplyInChannel() {
281
+ return this.state.getLatestValue().showReplyInChannel;
282
+ }
283
+
277
284
  get hasSendableData() {
278
285
  return !!(
279
286
  (!this.attachmentManager.uploadsInProgressCount &&
@@ -580,6 +587,10 @@ export class MessageComposer extends WithSubscriptions {
580
587
  this.state.partialNext({ quotedMessage });
581
588
  };
582
589
 
590
+ toggleShowReplyInChannel = () => {
591
+ this.state.partialNext({ showReplyInChannel: !this.showReplyInChannel });
592
+ };
593
+
583
594
  clear = () => {
584
595
  this.initState();
585
596
  };
@@ -664,16 +675,22 @@ export class MessageComposer extends WithSubscriptions {
664
675
  const composition = await this.pollComposer.compose();
665
676
  if (!composition || !composition.data.id) return;
666
677
  try {
667
- const { poll } = await this.client.createPoll(composition.data);
668
- this.state.partialNext({ pollId: poll.id });
669
- this.pollComposer.initState();
678
+ const poll = await this.client.polls.createPoll(composition.data);
679
+ this.state.partialNext({ pollId: poll?.id });
670
680
  } catch (error) {
671
- this.client.notifications.add({
681
+ this.client.notifications.addError({
672
682
  message: 'Failed to create the poll',
673
683
  origin: {
674
684
  emitter: 'MessageComposer',
675
685
  context: { composer: this },
676
686
  },
687
+ options: {
688
+ type: 'api:poll:create:failed',
689
+ metadata: {
690
+ reason: (error as Error).message,
691
+ },
692
+ originalError: error instanceof Error ? error : undefined,
693
+ },
677
694
  });
678
695
  throw error;
679
696
  }
@@ -30,6 +30,7 @@ import {
30
30
  createCustomDataCompositionMiddleware,
31
31
  createDraftCustomDataCompositionMiddleware,
32
32
  } from './customData';
33
+ import { createUserDataInjectionMiddleware } from './userDataInjection';
33
34
  import { createPollOnlyCompositionMiddleware } from './pollOnly';
34
35
 
35
36
  export class MessageComposerMiddlewareExecutor extends MiddlewareExecutor<
@@ -41,6 +42,7 @@ export class MessageComposerMiddlewareExecutor extends MiddlewareExecutor<
41
42
  // todo: document how to add custom data to a composed message using middleware
42
43
  // or adding custom composer components (apart from AttachmentsManager, TextComposer etc.)
43
44
  this.use([
45
+ createUserDataInjectionMiddleware(composer),
44
46
  createPollOnlyCompositionMiddleware(composer),
45
47
  createTextComposerCompositionMiddleware(composer),
46
48
  createAttachmentsCompositionMiddleware(composer),
@@ -29,7 +29,6 @@ export const createCompositionDataCleanupMiddleware = (
29
29
  ...composer.editedMessage,
30
30
  ...state.localMessage,
31
31
  ...common,
32
- user: composer.client.user,
33
32
  }),
34
33
  message: {
35
34
  ...editedMessagePayloadToBeSent,
@@ -17,7 +17,10 @@ export const createMessageComposerStateCompositionMiddleware = (
17
17
  state,
18
18
  next,
19
19
  }: MiddlewareHandlerParams<MessageComposerMiddlewareState>) => {
20
- const payload: Pick<LocalMessage, 'poll_id' | 'quoted_message_id'> = {};
20
+ const payload: Pick<
21
+ LocalMessage,
22
+ 'poll_id' | 'quoted_message_id' | 'show_in_channel'
23
+ > = {};
21
24
  if (composer.quotedMessage) {
22
25
  payload.quoted_message_id = composer.quotedMessage.id;
23
26
  }
@@ -25,6 +28,10 @@ export const createMessageComposerStateCompositionMiddleware = (
25
28
  payload.poll_id = composer.pollId;
26
29
  }
27
30
 
31
+ if (composer.showReplyInChannel) {
32
+ payload.show_in_channel = true;
33
+ }
34
+
28
35
  return next({
29
36
  ...state,
30
37
  localMessage: {
@@ -50,7 +57,10 @@ export const createDraftMessageComposerStateCompositionMiddleware = (
50
57
  state,
51
58
  next,
52
59
  }: MiddlewareHandlerParams<MessageDraftComposerMiddlewareValueState>) => {
53
- const payload: Pick<LocalMessage, 'poll_id' | 'quoted_message_id'> = {};
60
+ const payload: Pick<
61
+ LocalMessage,
62
+ 'poll_id' | 'quoted_message_id' | 'show_in_channel'
63
+ > = {};
54
64
  if (composer.quotedMessage) {
55
65
  payload.quoted_message_id = composer.quotedMessage.id;
56
66
  }
@@ -58,6 +68,10 @@ export const createDraftMessageComposerStateCompositionMiddleware = (
58
68
  payload.poll_id = composer.pollId;
59
69
  }
60
70
 
71
+ if (composer.showReplyInChannel) {
72
+ payload.show_in_channel = true;
73
+ }
74
+
61
75
  return next({
62
76
  ...state,
63
77
  draft: {
@@ -0,0 +1,43 @@
1
+ import type { MessageComposer } from '../../messageComposer';
2
+ import type {
3
+ MessageComposerMiddlewareState,
4
+ MessageCompositionMiddleware,
5
+ } from './types';
6
+ import type { MiddlewareHandlerParams } from '../../../middleware';
7
+ import type { OwnUserResponse } from '../../../types';
8
+
9
+ export const createUserDataInjectionMiddleware = (
10
+ composer: MessageComposer,
11
+ ): MessageCompositionMiddleware => ({
12
+ id: 'stream-io/message-composer-middleware/user-data-injection',
13
+ handlers: {
14
+ compose: ({
15
+ state,
16
+ next,
17
+ forward,
18
+ }: MiddlewareHandlerParams<MessageComposerMiddlewareState>) => {
19
+ if (!composer.client.user) {
20
+ return forward();
21
+ }
22
+ // Exclude the following properties from client.user as they can be large objects
23
+ // that provide no value for localMessage (and will never exist within message.user).
24
+ // This way we make sure that our localMessage is enriched with data as close as
25
+ // possible to the actual user.
26
+ // The reason why we need to explicitly cast is because OwnUserResponse only takes
27
+ // precedence after we connectUser the first time and we get the connection health
28
+ // check event. Due to how liberal the type of client.user is, we have to do it this
29
+ // way to maintain type safety.
30
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
31
+ const { channel_mutes, devices, mutes, ...messageUser } = composer.client
32
+ .user as OwnUserResponse;
33
+ return next({
34
+ ...state,
35
+ localMessage: {
36
+ ...state.localMessage,
37
+ user: messageUser,
38
+ user_id: messageUser.id,
39
+ },
40
+ });
41
+ },
42
+ },
43
+ });
@@ -6,13 +6,8 @@ import type {
6
6
  NotificationManagerConfig,
7
7
  NotificationState,
8
8
  } from './types';
9
-
10
- const DURATIONS: NotificationManagerConfig['durations'] = {
11
- error: 10000,
12
- warning: 5000,
13
- info: 3000,
14
- success: 3000,
15
- } as const;
9
+ import { mergeWith } from '../utils/mergeWith';
10
+ import { DEFAULT_NOTIFICATION_MANAGER_CONFIG } from './configuration';
16
11
 
17
12
  export class NotificationManager {
18
13
  store: StateStore<NotificationState>;
@@ -21,10 +16,7 @@ export class NotificationManager {
21
16
 
22
17
  constructor(config: Partial<NotificationManagerConfig> = {}) {
23
18
  this.store = new StateStore<NotificationState>({ notifications: [] });
24
- this.config = {
25
- ...config,
26
- durations: config.durations || DURATIONS,
27
- };
19
+ this.config = mergeWith(DEFAULT_NOTIFICATION_MANAGER_CONFIG, config);
28
20
  }
29
21
 
30
22
  get notifications() {
@@ -50,24 +42,27 @@ export class NotificationManager {
50
42
  add({ message, origin, options = {} }: AddNotificationPayload): string {
51
43
  const id = generateUUIDv4();
52
44
  const now = Date.now();
45
+ const severity = options.severity || 'info';
46
+ const duration = options.duration ?? this.config.durations[severity];
53
47
 
54
48
  const notification: Notification = {
55
49
  id,
56
50
  message,
57
51
  origin,
58
- severity: options.severity || 'info',
52
+ type: options?.type,
53
+ severity,
59
54
  createdAt: now,
60
- expiresAt: options.duration ? now + options.duration : undefined,
61
- autoClose: options.autoClose ?? true,
55
+ expiresAt: now + duration,
62
56
  actions: options.actions,
63
57
  metadata: options.metadata,
58
+ originalError: options.originalError,
64
59
  };
65
60
 
66
61
  this.store.partialNext({
67
62
  notifications: [...this.store.getLatestValue().notifications, notification],
68
63
  });
69
64
 
70
- if (notification.autoClose && notification.expiresAt) {
65
+ if (notification.expiresAt) {
71
66
  const timeout = setTimeout(() => {
72
67
  this.remove(id);
73
68
  }, options.duration || this.config.durations[notification.severity]);
@@ -0,0 +1,12 @@
1
+ import type { NotificationManagerConfig } from './types';
2
+
3
+ const DURATION_MS = 3000 as const;
4
+
5
+ export const DEFAULT_NOTIFICATION_MANAGER_CONFIG: NotificationManagerConfig = {
6
+ durations: {
7
+ error: DURATION_MS,
8
+ info: DURATION_MS,
9
+ success: DURATION_MS,
10
+ warning: DURATION_MS,
11
+ },
12
+ };
@@ -33,42 +33,84 @@ export type Notification = {
33
33
  * The identifier then can be recognized by notification consumers to act upon specific origin values.
34
34
  */
35
35
  origin: NotificationOrigin;
36
- /** Optional timestamp when notification should expire */
37
- expiresAt?: number;
38
- /** Whether notification should automatically close after duration. Defaults to true */
39
- autoClose?: boolean;
40
36
  /** Array of action buttons for the notification */
41
37
  actions?: NotificationAction[];
38
+ /**
39
+ * Optional code that can be used to group the notifications of the same type, e.g. attachment-upload-blocked.
40
+ * Format: domain:entity:operation:result
41
+ * domain: where the error occurred (api, validation, permission, etc)
42
+ * entity: what was being operated on (poll, attachment, message, etc)
43
+ * operation: what was being attempted (create, upload, validate, etc)
44
+ * result: what happened (failed, blocked, invalid, etc)
45
+ *
46
+ * Poll related errors
47
+ * 'api:poll:create:failed' // API call to create poll failed
48
+ * 'validation:poll:create:invalid' // Poll creation validation failed
49
+ *
50
+ * Attachment related errors
51
+ * 'validation:attachment:file:missing' // Required file is missing
52
+ * 'permission:attachment:upload:blocked' // Upload blocked due to permissions
53
+ * 'api:attachment:upload:failed' // API upload call failed
54
+ * 'validation:attachment:type:unsupported' // Unsupported file type
55
+ * 'validation:attachment:size:exceeded' // File size too large
56
+ * 'validation:attachment:count:exceeded' // Too many attachments
57
+ *
58
+ * Message related errors
59
+ * 'api:message:send:failed' // Message send failed
60
+ * 'validation:message:content:empty' // Message content validation failed
61
+ *
62
+ * Channel related errors
63
+ * 'api:channel:join:failed' // Channel join failed
64
+ * 'permission:channel:access:denied' // Channel access denied
65
+ *
66
+ * Authentication related errors
67
+ * 'auth:token:expired' // Auth token expired
68
+ * 'auth:token:invalid' // Invalid auth token
69
+ *
70
+ * Network related errors
71
+ * 'network:request:timeout' // Request timed out
72
+ * 'network:request:failed' // Network request failed
73
+ *
74
+ * Rate limiting
75
+ * 'rate:limit:exceeded' // Rate limit exceeded
76
+ *
77
+ * System errors
78
+ * 'system:internal:error' // Internal system error
79
+ * 'system:resource:unavailable'; // System resource unavailable
80
+ */
81
+ type?: string;
82
+ /** Optional timestamp when notification should expire */
83
+ expiresAt?: number;
42
84
  /** Optional metadata to attach to the notification */
43
85
  metadata?: Record<string, unknown>;
86
+ /** In case of error notification the instance of the originally thrown error */
87
+ originalError?: Error;
44
88
  };
45
89
 
46
90
  /** Configuration options when creating a notification */
47
- export type NotificationOptions = {
48
- /** The severity level. Defaults to 'info' */
49
- severity?: NotificationSeverity;
50
- /** How long notification should display in milliseconds */
91
+ export type NotificationOptions = Partial<
92
+ Pick<Notification, 'type' | 'severity' | 'actions' | 'metadata' | 'originalError'>
93
+ > & {
94
+ /** How long a notification should be displayed in milliseconds */
51
95
  duration?: number;
52
- /** Whether notification should auto-close after duration. Defaults to true */
53
- autoClose?: boolean;
54
- /** Array of action buttons for the notification */
55
- actions?: NotificationAction[];
56
- /** Optional metadata to attach to the notification */
57
- metadata?: Record<string, unknown>;
58
96
  };
59
97
 
60
- /** State shape for the notification store */
98
+ /**
99
+ * State shape for the notification store
100
+ * @deprcated use NotificationManagerState
101
+ */
61
102
  export type NotificationState = {
62
103
  /** Array of current notification objects */
63
104
  notifications: Notification[];
64
105
  };
65
106
 
107
+ /** State shape for the notification store */
108
+ export type NotificationManagerState = NotificationState;
109
+
66
110
  export type NotificationManagerConfig = {
67
111
  durations: Record<NotificationSeverity, number>;
68
112
  };
69
113
 
70
- export type AddNotificationPayload = {
71
- message: string;
72
- origin: NotificationOrigin;
114
+ export type AddNotificationPayload = Pick<Notification, 'message' | 'origin'> & {
73
115
  options?: NotificationOptions;
74
116
  };
@@ -49,7 +49,13 @@ export class PollManager extends WithSubscriptions {
49
49
  public createPoll = async (poll: CreatePollData) => {
50
50
  const { poll: createdPoll } = await this.client.createPoll(poll);
51
51
 
52
- return new Poll({ client: this.client, poll: createdPoll });
52
+ if (!createdPoll.vote_counts_by_option) {
53
+ createdPoll.vote_counts_by_option = {};
54
+ }
55
+
56
+ this.setOrOverwriteInCache(createdPoll);
57
+
58
+ return this.fromState(createdPoll.id);
53
59
  };
54
60
 
55
61
  public getPoll = async (id: string) => {