@facteurjs/core 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/api/handlers/notifications.js +77 -0
  2. package/dist/api/handlers/preferences.js +43 -0
  3. package/dist/api/index.d.ts +16 -0
  4. package/dist/api/index.js +21 -0
  5. package/dist/api/types.d.ts +22 -0
  6. package/dist/api/types.js +0 -0
  7. package/dist/channels/discord/channel.d.ts +18 -0
  8. package/dist/channels/discord/channel.js +15 -0
  9. package/dist/channels/discord/index.d.ts +3 -0
  10. package/dist/channels/discord/index.js +4 -0
  11. package/dist/channels/discord/message.d.ts +147 -0
  12. package/dist/channels/discord/message.js +176 -0
  13. package/dist/channels/discord/types.d.ts +52 -0
  14. package/dist/channels/discord/types.js +0 -0
  15. package/dist/channels/fcm/channel.d.ts +22 -0
  16. package/dist/channels/fcm/channel.js +44 -0
  17. package/dist/channels/fcm/index.d.ts +3 -0
  18. package/dist/channels/fcm/index.js +4 -0
  19. package/dist/channels/fcm/message.d.ts +64 -0
  20. package/dist/channels/fcm/message.js +122 -0
  21. package/dist/channels/fcm/types.d.ts +29 -0
  22. package/dist/channels/fcm/types.js +0 -0
  23. package/dist/channels/slack/channel.d.ts +18 -0
  24. package/dist/channels/slack/channel.js +15 -0
  25. package/dist/channels/slack/index.d.ts +3 -0
  26. package/dist/channels/slack/index.js +4 -0
  27. package/dist/channels/slack/message.d.ts +209 -0
  28. package/dist/channels/slack/message.js +390 -0
  29. package/dist/channels/slack/types.d.ts +7 -0
  30. package/dist/channels/slack/types.js +0 -0
  31. package/dist/channels/transmit/channel.d.ts +21 -0
  32. package/dist/channels/transmit/channel.js +27 -0
  33. package/dist/channels/transmit/index.d.ts +3 -0
  34. package/dist/channels/transmit/index.js +4 -0
  35. package/dist/channels/transmit/message.d.ts +11 -0
  36. package/dist/channels/transmit/message.js +17 -0
  37. package/dist/channels/transmit/types.d.ts +11 -0
  38. package/dist/channels/transmit/types.js +0 -0
  39. package/dist/channels/twilio/channel.d.ts +21 -0
  40. package/dist/channels/twilio/channel.js +56 -0
  41. package/dist/channels/twilio/index.d.ts +4 -0
  42. package/dist/channels/twilio/index.js +4 -0
  43. package/dist/channels/twilio/message.d.ts +86 -0
  44. package/dist/channels/twilio/message.js +152 -0
  45. package/dist/channels/twilio/types.d.ts +51 -0
  46. package/dist/channels/twilio/types.js +0 -0
  47. package/dist/channels/webhook/exceptions.d.ts +18 -0
  48. package/dist/channels/webhook/exceptions.js +24 -0
  49. package/dist/channels/webhook/index.d.ts +4 -0
  50. package/dist/channels/webhook/index.js +5 -0
  51. package/dist/channels/webhook/message.d.ts +24 -0
  52. package/dist/channels/webhook/message.js +40 -0
  53. package/dist/channels/webhook/provider.d.ts +19 -0
  54. package/dist/channels/webhook/provider.js +63 -0
  55. package/dist/channels/webhook/types.d.ts +15 -0
  56. package/dist/channels/webhook/types.js +0 -0
  57. package/dist/channels/webpush/channel.d.ts +26 -0
  58. package/dist/channels/webpush/channel.js +55 -0
  59. package/dist/channels/webpush/index.d.ts +3 -0
  60. package/dist/channels/webpush/index.js +4 -0
  61. package/dist/channels/webpush/message.d.ts +90 -0
  62. package/dist/channels/webpush/message.js +174 -0
  63. package/dist/channels/webpush/types.d.ts +50 -0
  64. package/dist/channels/webpush/types.js +0 -0
  65. package/dist/database/adapters/knex.d.ts +6 -0
  66. package/dist/database/adapters/knex.js +116 -0
  67. package/dist/database/adapters/kysely.d.ts +6 -0
  68. package/dist/database/adapters/kysely.js +101 -0
  69. package/dist/database/channel.d.ts +24 -0
  70. package/dist/database/channel.js +42 -0
  71. package/dist/database/database.d.ts +18 -0
  72. package/dist/database/database.js +89 -0
  73. package/dist/database/index.d.ts +3 -0
  74. package/dist/database/index.js +4 -0
  75. package/dist/database/message.d.ts +26 -0
  76. package/dist/database/message.js +51 -0
  77. package/dist/database/types.d.ts +147 -0
  78. package/dist/database/types.js +0 -0
  79. package/dist/debug.js +7 -0
  80. package/dist/errors/duplicate_notification_exception.d.ts +13 -0
  81. package/dist/errors/duplicate_notification_exception.js +21 -0
  82. package/dist/errors/http_error.d.ts +16 -0
  83. package/dist/errors/http_error.js +30 -0
  84. package/dist/errors/index.d.ts +38 -0
  85. package/dist/errors/index.js +39 -0
  86. package/dist/events/events.d.ts +90 -0
  87. package/dist/events/events.js +83 -0
  88. package/dist/facteur.d.ts +37 -0
  89. package/dist/facteur.js +83 -0
  90. package/dist/fake.d.ts +47 -0
  91. package/dist/fake.js +85 -0
  92. package/dist/index.d.ts +15 -0
  93. package/dist/index.js +16 -0
  94. package/dist/notifications/channel_resolver.js +91 -0
  95. package/dist/notifications/notification_discoverer.d.ts +40 -0
  96. package/dist/notifications/notification_discoverer.js +113 -0
  97. package/dist/notifications/notification_sender.js +210 -0
  98. package/dist/options.d.ts +22 -0
  99. package/dist/options.js +57 -0
  100. package/dist/types/channel.d.ts +18 -0
  101. package/dist/types/channel.js +5 -0
  102. package/dist/types/events.d.ts +27 -0
  103. package/dist/types/extend.d.ts +25 -0
  104. package/dist/types/index.d.ts +8 -0
  105. package/dist/types/index.js +4 -0
  106. package/dist/types/notifications.d.ts +60 -0
  107. package/dist/types/notifications.js +38 -0
  108. package/dist/types/options.d.ts +157 -0
  109. package/dist/types/preferences.d.ts +40 -0
  110. package/dist/types/queue.d.ts +11 -0
  111. package/package.json +65 -0
@@ -0,0 +1,77 @@
1
+ import { defineRoute } from "../index.js";
2
+
3
+ //#region src/api/handlers/notifications.ts
4
+ /**
5
+ * Get all notifications for a given user
6
+ */
7
+ const getNotificationRoute = defineRoute(({ facteur }) => ({
8
+ method: "get",
9
+ route: "/notifications/notifiable/:notifiableId/notifications",
10
+ handler: async (request) => {
11
+ const userId = request.params.notifiableId;
12
+ const notifications = await facteur.db.getNotifications({
13
+ notifiableId: userId,
14
+ tenantId: request.query.tenantId,
15
+ page: request.query.page,
16
+ limit: request.query.limit,
17
+ status: request.query.status
18
+ });
19
+ return {
20
+ status: 200,
21
+ body: notifications || []
22
+ };
23
+ }
24
+ }));
25
+ /**
26
+ * Mark a specific notification as read or seen
27
+ */
28
+ const markNotificationAsRoute = defineRoute(({ facteur }) => ({
29
+ method: "post",
30
+ route: "/notifications/notifiable/:notifiableId/mark-as",
31
+ handler: async (request) => {
32
+ const notificationId = request.body.notificationId;
33
+ const status = request.body.status;
34
+ if (!status) return {
35
+ status: 400,
36
+ body: { error: "Status is required" }
37
+ };
38
+ if (!notificationId) return {
39
+ status: 400,
40
+ body: { error: "Notification ID is required" }
41
+ };
42
+ await facteur.db.updateNotification({
43
+ id: notificationId,
44
+ status
45
+ });
46
+ return {
47
+ status: 204,
48
+ body: {}
49
+ };
50
+ }
51
+ }));
52
+ /**
53
+ * Mark all notifications for a user as read or seen
54
+ */
55
+ const markAllNotificationsAsRoute = defineRoute(({ facteur }) => ({
56
+ method: "post",
57
+ route: "/notifications/notifiable/:notifiableId/mark-all",
58
+ handler: async (request) => {
59
+ const status = request.body.status;
60
+ if (!status) return {
61
+ status: 400,
62
+ body: { error: "Status is required" }
63
+ };
64
+ await facteur.db.updateAllNotifications({
65
+ notifiableId: request.params.notifiableId,
66
+ tenantId: request.body.tenantId,
67
+ status
68
+ });
69
+ return {
70
+ status: 204,
71
+ body: {}
72
+ };
73
+ }
74
+ }));
75
+
76
+ //#endregion
77
+ export { getNotificationRoute, markAllNotificationsAsRoute, markNotificationAsRoute };
@@ -0,0 +1,43 @@
1
+ import { defineRoute } from "../index.js";
2
+
3
+ //#region src/api/handlers/preferences.ts
4
+ const getPreferencesRoute = defineRoute(({ facteur }) => ({
5
+ method: "get",
6
+ route: "/notifications/notifiable/:notifiableId/preferences",
7
+ handler: async (request) => {
8
+ const userId = request.params.notifiableId;
9
+ const preferences = await facteur.db.getPreferences({
10
+ notifiableId: userId,
11
+ tenantId: request.query.tenantId
12
+ });
13
+ return {
14
+ status: 200,
15
+ body: preferences || {}
16
+ };
17
+ }
18
+ }));
19
+ const updatePreferencesRoute = defineRoute(({ facteur }) => ({
20
+ method: "post",
21
+ route: "/notifications/notifiable/:notifiableId/preferences",
22
+ handler: async (request) => {
23
+ const userId = request.params.notifiableId;
24
+ const preferences = request.body.preferences;
25
+ if (!preferences) return {
26
+ status: 400,
27
+ body: { error: "Preferences are required" }
28
+ };
29
+ await facteur.db.updatePreferences({
30
+ notifiableId: userId,
31
+ tenantId: request.body.tenantId,
32
+ notificationName: request.body.notificationName,
33
+ channelPreferences: preferences
34
+ });
35
+ return {
36
+ status: 204,
37
+ body: {}
38
+ };
39
+ }
40
+ }));
41
+
42
+ //#endregion
43
+ export { getPreferencesRoute, updatePreferencesRoute };
@@ -0,0 +1,16 @@
1
+ import { Facteur } from "../facteur.js";
2
+ import { RouteDefinition, ServerAdapter } from "./types.js";
3
+
4
+ //#region src/api/index.d.ts
5
+ declare function defineRoute(routeDefinition: (options: {
6
+ facteur: Facteur<any, any>;
7
+ }) => RouteDefinition): (options: {
8
+ facteur: Facteur<any, any>;
9
+ }) => RouteDefinition;
10
+ declare const routes: (facteur: Facteur<any, any>) => RouteDefinition[];
11
+ declare function createFacteurServer(options: {
12
+ adapter: ServerAdapter;
13
+ facteur: Facteur<any, any>;
14
+ }): void;
15
+ //#endregion
16
+ export { createFacteurServer, defineRoute, routes };
@@ -0,0 +1,21 @@
1
+ import { getPreferencesRoute, updatePreferencesRoute } from "./handlers/preferences.js";
2
+ import { getNotificationRoute, markAllNotificationsAsRoute, markNotificationAsRoute } from "./handlers/notifications.js";
3
+
4
+ //#region src/api/index.ts
5
+ function defineRoute(routeDefinition) {
6
+ return routeDefinition;
7
+ }
8
+ const routes = (facteur) => [
9
+ getNotificationRoute({ facteur }),
10
+ markNotificationAsRoute({ facteur }),
11
+ markAllNotificationsAsRoute({ facteur }),
12
+ getPreferencesRoute({ facteur }),
13
+ updatePreferencesRoute({ facteur })
14
+ ];
15
+ function createFacteurServer(options) {
16
+ const { adapter, facteur } = options;
17
+ adapter.setRoutes(routes(facteur));
18
+ }
19
+
20
+ //#endregion
21
+ export { createFacteurServer, defineRoute, routes };
@@ -0,0 +1,22 @@
1
+ //#region src/api/types.d.ts
2
+ type HTTPMethod = 'get' | 'post' | 'put' | 'patch';
3
+ interface HTTPRequest {
4
+ body: Record<string, any>;
5
+ params: Record<string, any>;
6
+ query: Record<string, any>;
7
+ headers: Record<string, string | undefined>;
8
+ }
9
+ interface HTTPResponse {
10
+ status: number;
11
+ body: Record<string, any>;
12
+ }
13
+ interface RouteDefinition {
14
+ method: HTTPMethod;
15
+ route: string;
16
+ handler: (request: HTTPRequest) => Promise<HTTPResponse>;
17
+ }
18
+ interface ServerAdapter {
19
+ setRoutes(routes: RouteDefinition[]): void;
20
+ }
21
+ //#endregion
22
+ export { HTTPMethod, HTTPRequest, HTTPResponse, RouteDefinition, ServerAdapter };
File without changes
@@ -0,0 +1,18 @@
1
+ import { Channel } from "../../types/channel.js";
2
+ import { WebhookOptions, WebhookTargets } from "../webhook/types.js";
3
+ import { DiscordResponse } from "./types.js";
4
+ import { DiscordMessage } from "./message.js";
5
+ import { WebhookChannel } from "../webhook/provider.js";
6
+
7
+ //#region src/channels/discord/channel.d.ts
8
+ declare function discordWebhookChannel<Options extends WebhookOptions<any>>(options: Options): DiscordProvider<Options>;
9
+ declare class DiscordProvider<T extends WebhookOptions<any>> extends WebhookChannel<T> implements Channel<T, DiscordMessage, DiscordResponse, WebhookTargets<T>> {
10
+ name: any;
11
+ }
12
+ declare module '@facteurjs/core/types' {
13
+ interface Notification {
14
+ asDiscordMessage(): DiscordMessage;
15
+ }
16
+ }
17
+ //#endregion
18
+ export { DiscordProvider, discordWebhookChannel };
@@ -0,0 +1,15 @@
1
+ import { WebhookChannel } from "../webhook/provider.js";
2
+
3
+ //#region src/channels/discord/channel.ts
4
+ function discordWebhookChannel(options) {
5
+ return new DiscordProvider({
6
+ name: "discord",
7
+ ...options
8
+ });
9
+ }
10
+ var DiscordProvider = class extends WebhookChannel {
11
+ name = "discord";
12
+ };
13
+
14
+ //#endregion
15
+ export { DiscordProvider, discordWebhookChannel };
@@ -0,0 +1,3 @@
1
+ import { DiscordMessage } from "./message.js";
2
+ import { DiscordProvider, discordWebhookChannel } from "./channel.js";
3
+ export { DiscordMessage, DiscordProvider, discordWebhookChannel };
@@ -0,0 +1,4 @@
1
+ import { DiscordMessage } from "./message.js";
2
+ import { DiscordProvider, discordWebhookChannel } from "./channel.js";
3
+
4
+ export { DiscordMessage, DiscordProvider, discordWebhookChannel };
@@ -0,0 +1,147 @@
1
+ import { WebhookMessage } from "../webhook/message.js";
2
+ import { DiscordEmbedField, HexadecimalColor } from "./types.js";
3
+
4
+ //#region src/channels/discord/message.d.ts
5
+ declare class DiscordMessage extends WebhookMessage {
6
+ #private;
7
+ /**
8
+ * Creates a new instance of DiscordMessage.
9
+ */
10
+ static create(): DiscordMessage;
11
+ /**
12
+ * Sets the content of the message.
13
+ */
14
+ setBody(body: string): this;
15
+ /**
16
+ * Sets the username of the bot that will send the message.
17
+ */
18
+ setBotUsername(username: string): this;
19
+ /**
20
+ * Sets the avatar URL for the bot that will send the message.
21
+ */
22
+ setBotAvatar(avatarUrl: string): this;
23
+ /**
24
+ * Adds an embed to the message using a callback function to configure it.
25
+ */
26
+ addEmbed(callback: (embed: DiscordEmbed) => void): this;
27
+ /**
28
+ * Whether or not this notification should be read as text to speech.
29
+ */
30
+ setTTS(flag: boolean): this;
31
+ serialize(): {
32
+ body: {
33
+ embeds: {
34
+ title: string | undefined;
35
+ description: string | undefined;
36
+ url: string | undefined;
37
+ color: number | undefined;
38
+ fields: DiscordEmbedField[] | undefined;
39
+ image: {
40
+ url: string;
41
+ } | undefined;
42
+ thumbnail: {
43
+ url: string;
44
+ } | undefined;
45
+ author: {
46
+ name: string | undefined;
47
+ url: string | undefined;
48
+ icon_url: string | undefined;
49
+ } | undefined;
50
+ footer: {
51
+ text: string;
52
+ icon_url: string | undefined;
53
+ } | undefined;
54
+ timestamp: string | undefined;
55
+ }[];
56
+ avatar_url?: string;
57
+ username?: string;
58
+ tts: boolean;
59
+ content: string;
60
+ };
61
+ headers: Record<string, string>;
62
+ queryParameters: Record<string, string>;
63
+ };
64
+ }
65
+ declare class DiscordEmbed {
66
+ #private;
67
+ /**
68
+ * Sets the author information for the embed.
69
+ */
70
+ setAuthor(options: {
71
+ name?: string;
72
+ url?: string;
73
+ iconUrl?: string;
74
+ }): this;
75
+ /**
76
+ * Sets the title of the embed.
77
+ */
78
+ setTitle(title: string): this;
79
+ /**
80
+ * Sets the description text for the embed.
81
+ */
82
+ setDescription(description: string): this;
83
+ /**
84
+ * Sets the URL for the embed title to link to.
85
+ */
86
+ setUrl(url: string): this;
87
+ /**
88
+ * Sets the color of the embed's left border. Accepts a hexadecimal color code.
89
+ */
90
+ setColor(color: HexadecimalColor): this;
91
+ /**
92
+ * Adds a field to the embed with a name and value.
93
+ *
94
+ * Inline fields will be displayed next to each other, rather than each on their own line.
95
+ */
96
+ addField(options: {
97
+ name: string;
98
+ value: string;
99
+ inline?: boolean;
100
+ }): this;
101
+ /**
102
+ * Sets the footer information for the embed, including optional timestamp.
103
+ */
104
+ setFooter(options: {
105
+ text: string;
106
+ iconUrl?: string;
107
+ }): this;
108
+ /**
109
+ * Set a date that will be displayed in the footer.
110
+ */
111
+ setTimestamp(timestamp: Date): this;
112
+ /**
113
+ * Adds an image to the embed.
114
+ * You can add up to 4 images when you have an URL in the Embed body. ( `embed.setUrl()` )
115
+ * Otherwise, only one image will be displayed.
116
+ */
117
+ setImage(image: string): this;
118
+ /**
119
+ * Sets the thumbnail image for the embed.
120
+ */
121
+ setThumbnail(url: string): this;
122
+ serialize(): {
123
+ title: string | undefined;
124
+ description: string | undefined;
125
+ url: string | undefined;
126
+ color: number | undefined;
127
+ fields: DiscordEmbedField[] | undefined;
128
+ image: {
129
+ url: string;
130
+ } | undefined;
131
+ thumbnail: {
132
+ url: string;
133
+ } | undefined;
134
+ author: {
135
+ name: string | undefined;
136
+ url: string | undefined;
137
+ icon_url: string | undefined;
138
+ } | undefined;
139
+ footer: {
140
+ text: string;
141
+ icon_url: string | undefined;
142
+ } | undefined;
143
+ timestamp: string | undefined;
144
+ };
145
+ }
146
+ //#endregion
147
+ export { DiscordMessage };
@@ -0,0 +1,176 @@
1
+ import { WebhookMessage } from "../webhook/message.js";
2
+
3
+ //#region src/channels/discord/message.ts
4
+ var DiscordMessage = class DiscordMessage extends WebhookMessage {
5
+ #body = "";
6
+ #username = "";
7
+ #avatarUrl = "";
8
+ #tts = false;
9
+ #embeds = [];
10
+ /**
11
+ * Creates a new instance of DiscordMessage.
12
+ */
13
+ static create() {
14
+ return new DiscordMessage().setQueryParameters({ wait: "true" });
15
+ }
16
+ /**
17
+ * Sets the content of the message.
18
+ */
19
+ setBody(body) {
20
+ this.#body = body;
21
+ return this;
22
+ }
23
+ /**
24
+ * Sets the username of the bot that will send the message.
25
+ */
26
+ setBotUsername(username) {
27
+ this.#username = username;
28
+ return this;
29
+ }
30
+ /**
31
+ * Sets the avatar URL for the bot that will send the message.
32
+ */
33
+ setBotAvatar(avatarUrl) {
34
+ this.#avatarUrl = avatarUrl;
35
+ return this;
36
+ }
37
+ /**
38
+ * Adds an embed to the message using a callback function to configure it.
39
+ */
40
+ addEmbed(callback) {
41
+ const embed = new DiscordEmbed();
42
+ callback(embed);
43
+ this.#embeds.push(embed);
44
+ return this;
45
+ }
46
+ /**
47
+ * Whether or not this notification should be read as text to speech.
48
+ */
49
+ setTTS(flag) {
50
+ this.#tts = flag;
51
+ return this;
52
+ }
53
+ serialize() {
54
+ return {
55
+ ...super.serialize(),
56
+ body: {
57
+ tts: this.#tts,
58
+ content: this.#body,
59
+ ...this.#username ? { username: this.#username } : {},
60
+ ...this.#avatarUrl ? { avatar_url: this.#avatarUrl } : {},
61
+ embeds: this.#embeds.map((embed) => embed.serialize())
62
+ }
63
+ };
64
+ }
65
+ };
66
+ var DiscordEmbed = class {
67
+ #title;
68
+ #description;
69
+ #url;
70
+ #color;
71
+ #fields;
72
+ #author;
73
+ #footer;
74
+ #image;
75
+ #timestamp;
76
+ #thumbnail;
77
+ /**
78
+ * Sets the author information for the embed.
79
+ */
80
+ setAuthor(options) {
81
+ this.#author = options;
82
+ return this;
83
+ }
84
+ /**
85
+ * Sets the title of the embed.
86
+ */
87
+ setTitle(title) {
88
+ this.#title = title;
89
+ return this;
90
+ }
91
+ /**
92
+ * Sets the description text for the embed.
93
+ */
94
+ setDescription(description) {
95
+ this.#description = description;
96
+ return this;
97
+ }
98
+ /**
99
+ * Sets the URL for the embed title to link to.
100
+ */
101
+ setUrl(url) {
102
+ this.#url = url;
103
+ return this;
104
+ }
105
+ /**
106
+ * Sets the color of the embed's left border. Accepts a hexadecimal color code.
107
+ */
108
+ setColor(color) {
109
+ this.#color = Number.parseInt(color.slice(1), 16);
110
+ return this;
111
+ }
112
+ /**
113
+ * Adds a field to the embed with a name and value.
114
+ *
115
+ * Inline fields will be displayed next to each other, rather than each on their own line.
116
+ */
117
+ addField(options) {
118
+ this.#fields = this.#fields || [];
119
+ this.#fields.push(options);
120
+ return this;
121
+ }
122
+ /**
123
+ * Sets the footer information for the embed, including optional timestamp.
124
+ */
125
+ setFooter(options) {
126
+ this.#footer = options;
127
+ return this;
128
+ }
129
+ /**
130
+ * Set a date that will be displayed in the footer.
131
+ */
132
+ setTimestamp(timestamp) {
133
+ this.#timestamp = timestamp;
134
+ return this;
135
+ }
136
+ /**
137
+ * Adds an image to the embed.
138
+ * You can add up to 4 images when you have an URL in the Embed body. ( `embed.setUrl()` )
139
+ * Otherwise, only one image will be displayed.
140
+ */
141
+ setImage(image) {
142
+ this.#image = image;
143
+ return this;
144
+ }
145
+ /**
146
+ * Sets the thumbnail image for the embed.
147
+ */
148
+ setThumbnail(url) {
149
+ this.#thumbnail = url;
150
+ return this;
151
+ }
152
+ serialize() {
153
+ return {
154
+ title: this.#title,
155
+ description: this.#description,
156
+ url: this.#url,
157
+ color: this.#color,
158
+ fields: this.#fields,
159
+ image: this.#image ? { url: this.#image } : void 0,
160
+ thumbnail: this.#thumbnail ? { url: this.#thumbnail } : void 0,
161
+ author: this.#author ? {
162
+ name: this.#author.name,
163
+ url: this.#author.url,
164
+ icon_url: this.#author.iconUrl
165
+ } : void 0,
166
+ footer: this.#footer ? {
167
+ text: this.#footer.text,
168
+ icon_url: this.#footer.iconUrl
169
+ } : void 0,
170
+ timestamp: this.#timestamp?.toISOString()
171
+ };
172
+ }
173
+ };
174
+
175
+ //#endregion
176
+ export { DiscordMessage };
@@ -0,0 +1,52 @@
1
+ import { WebhookOptions, WebhookTargets } from "../webhook/types.js";
2
+
3
+ //#region src/channels/discord/types.d.ts
4
+ type DiscordOptions<WebhooksNames extends string> = WebhookOptions<WebhooksNames>;
5
+ type DiscordTargets<Options extends DiscordOptions<any>> = WebhookTargets<Options>;
6
+ type HexadecimalColor = `#${string}`;
7
+ interface DiscordResponse {
8
+ type: number;
9
+ content: string;
10
+ mentions: string[];
11
+ mention_roles: string[];
12
+ attachments: string[];
13
+ embeds: string[];
14
+ timestamp: string;
15
+ edited_timestamp: string | null;
16
+ flags: number;
17
+ components: string[];
18
+ id: string;
19
+ channel_id: string;
20
+ author: {
21
+ id: string;
22
+ username: string;
23
+ avatar: string | null;
24
+ discriminator: string;
25
+ public_flags: number;
26
+ flags: number;
27
+ bot: boolean;
28
+ global_name: string | null;
29
+ clan: string | null;
30
+ primary_guild: string | null;
31
+ };
32
+ pinned: boolean;
33
+ mention_everyone: boolean;
34
+ tts: boolean;
35
+ webhook_id: string;
36
+ }
37
+ interface DiscordEmbedField {
38
+ name: string;
39
+ value: string;
40
+ inline?: boolean;
41
+ }
42
+ interface DiscordEmbedAuthor {
43
+ name?: string;
44
+ url?: string;
45
+ iconUrl?: string;
46
+ }
47
+ interface DiscordEmbedFooter {
48
+ text: string;
49
+ iconUrl?: string;
50
+ }
51
+ //#endregion
52
+ export { DiscordEmbedAuthor, DiscordEmbedField, DiscordEmbedFooter, DiscordOptions, DiscordResponse, DiscordTargets, HexadecimalColor };
File without changes
@@ -0,0 +1,22 @@
1
+ import { Channel, ChannelSendParams, kTargetSymbol } from "../../types/channel.js";
2
+ import { FcmMessage } from "./message.js";
3
+ import { FcmConfig, FcmTargets } from "./types.js";
4
+
5
+ //#region src/channels/fcm/channel.d.ts
6
+ declare function fcmChannel(config: FcmConfig): FcmChannel;
7
+ declare class FcmChannel implements Channel<FcmConfig, FcmMessage, any, FcmTargets> {
8
+ #private;
9
+ private config;
10
+ name: "fcm";
11
+ [kTargetSymbol]: FcmTargets;
12
+ private messaging;
13
+ constructor(config: FcmConfig);
14
+ send(options: ChannelSendParams<FcmMessage, FcmTargets>): Promise<string>;
15
+ }
16
+ declare module '@facteurjs/core/types' {
17
+ interface Notification {
18
+ asFcmMessage(): FcmMessage;
19
+ }
20
+ }
21
+ //#endregion
22
+ export { FcmChannel, fcmChannel };
@@ -0,0 +1,44 @@
1
+ import { errors } from "../../errors/index.js";
2
+ import { kTargetSymbol } from "../../types/channel.js";
3
+ import "../../types/index.js";
4
+ import { cert, initializeApp } from "firebase-admin/app";
5
+ import { getMessaging } from "firebase-admin/messaging";
6
+
7
+ //#region src/channels/fcm/channel.ts
8
+ function fcmChannel(config) {
9
+ return new FcmChannel(config);
10
+ }
11
+ var FcmChannel = class {
12
+ name = "fcm";
13
+ [kTargetSymbol] = null;
14
+ messaging;
15
+ constructor(config) {
16
+ this.config = config;
17
+ const app = initializeApp({
18
+ ...config,
19
+ ...config.serviceAccountKeyPath ? { credential: cert(config.serviceAccountKeyPath) } : {}
20
+ });
21
+ this.messaging = getMessaging(app);
22
+ }
23
+ #resolveTargets(options) {
24
+ if (options.targets) return options.targets;
25
+ throw new errors.E_UNAVAILABLE_TARGETS(["FCM"]);
26
+ }
27
+ #handleError(error) {
28
+ if (error.code === "messaging/registration-token-not-registered") throw new Error(`FCM token is no longer valid: ${error.message}`);
29
+ if (error.code === "messaging/invalid-argument") throw new Error(`Invalid FCM message format: ${error.message}`);
30
+ throw error;
31
+ }
32
+ async send(options) {
33
+ const message = options.message.serialize();
34
+ const targets = this.#resolveTargets(options);
35
+ if (this.config.debugToken) message.token = this.config.debugToken;
36
+ if (targets.token) message.token = this.config.debugToken || targets.token;
37
+ else if (targets.topic) message.topic = targets.topic;
38
+ else if (targets.condition) message.condition = targets.condition;
39
+ return await this.messaging.send(message).catch((error) => this.#handleError(error));
40
+ }
41
+ };
42
+
43
+ //#endregion
44
+ export { FcmChannel, fcmChannel };
@@ -0,0 +1,3 @@
1
+ import { FcmMessage } from "./message.js";
2
+ import { FcmChannel, fcmChannel } from "./channel.js";
3
+ export { FcmChannel, FcmMessage, fcmChannel };
@@ -0,0 +1,4 @@
1
+ import { FcmMessage } from "./message.js";
2
+ import { FcmChannel, fcmChannel } from "./channel.js";
3
+
4
+ export { FcmChannel, FcmMessage, fcmChannel };