@lyxa.ai/core 1.3.257 → 1.3.258

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.
@@ -1,4 +1,5 @@
1
1
  import { App } from 'firebase-admin/app';
2
+ import { AppPlatform, AuthEntityType } from '../../utilities/enum';
2
3
  export interface FCMNotificationPayload {
3
4
  title?: string;
4
5
  body?: string;
@@ -6,6 +7,13 @@ export interface FCMNotificationPayload {
6
7
  sound?: string;
7
8
  invisible?: boolean;
8
9
  }
10
+ export interface NotificationTokenTarget {
11
+ token: string;
12
+ entityType: AuthEntityType;
13
+ entityId: string;
14
+ platform?: AppPlatform;
15
+ appVersion?: string;
16
+ }
9
17
  export declare const FCMServiceForUser = "fcm.service.user";
10
18
  export declare const FCMServiceForRider = "fcm.service.rider";
11
19
  export declare const FCMServiceForShop = "fcm.service.shop";
@@ -14,8 +22,12 @@ export declare class FCMService {
14
22
  private static readonly MAX_BATCH;
15
23
  constructor(app?: App);
16
24
  private chunkArray;
25
+ private compareVersions;
26
+ private isNotifiableEntityType;
27
+ private shouldUseNewStructure;
17
28
  private buildMessage;
18
- sendNotification(payload: FCMNotificationPayload, tokens: string[]): Promise<{
29
+ private buildOldMessage;
30
+ sendNotification(payload: FCMNotificationPayload, targets: NotificationTokenTarget[]): Promise<{
19
31
  successTokens: string[];
20
32
  failedTokens: string[];
21
33
  }>;
@@ -14,6 +14,21 @@ exports.FCMService = exports.FCMServiceForShop = exports.FCMServiceForRider = ex
14
14
  const firebase_admin_1 = require("firebase-admin");
15
15
  const __1 = require("../..");
16
16
  const typedi_1 = require("typedi");
17
+ const enum_1 = require("../../utilities/enum");
18
+ const NotificationVersionRules = {
19
+ [enum_1.AuthEntityType.DELIVERY_BOY]: {
20
+ [enum_1.AppPlatform.IOS]: '1.0.4',
21
+ [enum_1.AppPlatform.ANDROID]: '1.2.1',
22
+ },
23
+ [enum_1.AuthEntityType.USER]: {
24
+ [enum_1.AppPlatform.IOS]: '1.0.2',
25
+ [enum_1.AppPlatform.ANDROID]: '1.1.3',
26
+ },
27
+ [enum_1.AuthEntityType.SELLER]: {
28
+ [enum_1.AppPlatform.IOS]: '1.1.0',
29
+ [enum_1.AppPlatform.ANDROID]: '1.0.9',
30
+ },
31
+ };
17
32
  exports.FCMServiceForUser = 'fcm.service.user';
18
33
  exports.FCMServiceForRider = 'fcm.service.rider';
19
34
  exports.FCMServiceForShop = 'fcm.service.shop';
@@ -32,6 +47,29 @@ let FCMService = class FCMService {
32
47
  }
33
48
  return chunks;
34
49
  }
50
+ compareVersions(v1, v2) {
51
+ const a = v1.split('.').map(Number);
52
+ const b = v2.split('.').map(Number);
53
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
54
+ const diff = (a[i] || 0) - (b[i] || 0);
55
+ if (diff !== 0)
56
+ return diff;
57
+ }
58
+ return 0;
59
+ }
60
+ isNotifiableEntityType(type) {
61
+ return (type === enum_1.AuthEntityType.USER || type === enum_1.AuthEntityType.SELLER || type === enum_1.AuthEntityType.DELIVERY_BOY);
62
+ }
63
+ shouldUseNewStructure(entityType, platform, version) {
64
+ if (!this.isNotifiableEntityType(entityType))
65
+ return false;
66
+ if (!platform || !version)
67
+ return false;
68
+ const minVersion = NotificationVersionRules?.[entityType]?.[platform];
69
+ if (!minVersion)
70
+ return false;
71
+ return this.compareVersions(version, minVersion) >= 0;
72
+ }
35
73
  buildMessage(payload, batchTokens) {
36
74
  const isInvisible = payload.invisible === true;
37
75
  const channelId = payload.sound ? 'order_alert' : 'default';
@@ -62,15 +100,60 @@ let FCMService = class FCMService {
62
100
  };
63
101
  return message;
64
102
  }
65
- async sendNotification(payload, tokens) {
66
- if (!tokens.length)
103
+ buildOldMessage(payload, batchTokens) {
104
+ const isInvisible = payload.invisible === true;
105
+ const message = {
106
+ data: payload.data || {},
107
+ notification: {
108
+ title: payload.title,
109
+ body: payload.body,
110
+ },
111
+ tokens: batchTokens,
112
+ android: { priority: 'high' },
113
+ apns: {
114
+ payload: {
115
+ aps: {
116
+ sound: payload.sound || 'default',
117
+ contentAvailable: true,
118
+ },
119
+ },
120
+ },
121
+ };
122
+ if (!isInvisible) {
123
+ message.notification = {
124
+ title: payload.title,
125
+ body: payload.body,
126
+ };
127
+ message.android = {
128
+ priority: 'high',
129
+ notification: {
130
+ channelId: payload.sound ? 'android_Push_Notification_Channel_1' : 'default',
131
+ },
132
+ };
133
+ }
134
+ return message;
135
+ }
136
+ async sendNotification(payload, targets) {
137
+ if (!targets.length) {
67
138
  return { successTokens: [], failedTokens: [] };
68
- const tokenChunks = this.chunkArray(tokens, FCMService_1.MAX_BATCH);
69
- let successTokens = [];
70
- let failedTokens = [];
71
- try {
139
+ }
140
+ const oldTokens = [];
141
+ const newTokens = [];
142
+ for (const t of targets) {
143
+ const useNew = this.shouldUseNewStructure(t.entityType, t.platform, t.appVersion);
144
+ if (useNew)
145
+ newTokens.push(t.token);
146
+ else
147
+ oldTokens.push(t.token);
148
+ }
149
+ const sendGroup = async (groupTokens, groupPayload, oldNotificationStructure = false) => {
150
+ const tokenChunks = this.chunkArray(groupTokens, FCMService_1.MAX_BATCH);
151
+ const success = [];
152
+ const failed = [];
72
153
  const results = await Promise.all(tokenChunks.map(async (chunk) => {
73
- const message = this.buildMessage(payload, chunk);
154
+ const message = oldNotificationStructure
155
+ ? this.buildOldMessage(groupPayload, chunk)
156
+ : this.buildMessage(groupPayload, chunk);
74
157
  const response = await this.messaging.sendEachForMulticast(message);
75
158
  const batchSuccess = [];
76
159
  const batchFailed = [];
@@ -90,15 +173,24 @@ let FCMService = class FCMService {
90
173
  return { batchSuccess, batchFailed };
91
174
  }));
92
175
  for (const r of results) {
93
- successTokens.push(...r.batchSuccess);
94
- failedTokens.push(...r.batchFailed);
176
+ success.push(...r.batchSuccess);
177
+ failed.push(...r.batchFailed);
95
178
  }
96
- return { successTokens, failedTokens };
179
+ return { success, failed };
180
+ };
181
+ let successTokens = [];
182
+ let failedTokens = [];
183
+ if (oldTokens.length) {
184
+ const r = await sendGroup(oldTokens, payload, true);
185
+ successTokens.push(...r.success);
186
+ failedTokens.push(...r.failed);
97
187
  }
98
- catch (err) {
99
- console.error('FCM sendNotification error:', err);
100
- throw err;
188
+ if (newTokens.length) {
189
+ const r = await sendGroup(newTokens, payload, false);
190
+ successTokens.push(...r.success);
191
+ failedTokens.push(...r.failed);
101
192
  }
193
+ return { successTokens, failedTokens };
102
194
  }
103
195
  };
104
196
  exports.FCMService = FCMService;
@@ -1 +1 @@
1
- {"version":3,"file":"fcmService.js","sourceRoot":"/","sources":["libraries/firebase/fcmService.ts"],"names":[],"mappings":"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","sourcesContent":["import { messaging } from 'firebase-admin';\nimport { getLibraries } from '../..';\nimport { Service } from 'typedi';\nimport { App } from 'firebase-admin/app';\n\nexport interface FCMNotificationPayload {\n\ttitle?: string;\n\tbody?: string;\n\tdata?: Record<string, string>;\n\tsound?: string;\n\tinvisible?: boolean;\n}\n\nexport const FCMServiceForUser = 'fcm.service.user';\nexport const FCMServiceForRider = 'fcm.service.rider';\nexport const FCMServiceForShop = 'fcm.service.shop';\n\n@Service()\nexport class FCMService {\n\tprivate messaging!: messaging.Messaging;\n\tprivate static readonly MAX_BATCH = 500;\n\n\tconstructor(app?: App) {\n\t\tconst firebaseApp = app ?? getLibraries().getFirebaseService().superApp;\n\t\tthis.messaging = messaging(firebaseApp);\n\t}\n\n\t/**\n\t * Split array into chunks of fixed size\n\t */\n\tprivate chunkArray<T>(arr: T[], size: number): T[][] {\n\t\tconst chunks: T[][] = [];\n\t\tfor (let i = 0; i < arr.length; i += size) {\n\t\t\tchunks.push(arr.slice(i, i + size));\n\t\t}\n\t\treturn chunks;\n\t}\n\n\t/**\n\t * Build multicast message safely\n\t */\n\tprivate buildMessage(payload: FCMNotificationPayload, batchTokens: string[]): messaging.MulticastMessage {\n\t\tconst isInvisible = payload.invisible === true;\n\t\tconst channelId = payload.sound ? 'order_alert' : 'default';\n\n\t\tconst message: messaging.MulticastMessage = {\n\t\t\tdata: { ...payload.data, channelId: isInvisible ? 'silent' : channelId },\n\t\t\tnotification: {\n\t\t\t\ttitle: payload.title,\n\t\t\t\tbody: payload.body,\n\t\t\t},\n\t\t\ttokens: batchTokens,\n\t\t\tandroid: {\n\t\t\t\tpriority: 'high',\n\t\t\t\tnotification: {\n\t\t\t\t\tchannelId: 'silent',\n\t\t\t\t},\n\t\t\t},\n\t\t\tapns: {\n\t\t\t\tpayload: {\n\t\t\t\t\taps: {\n\t\t\t\t\t\tsound: payload.sound || 'default',\n\t\t\t\t\t\tcontentAvailable: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\theaders: {\n\t\t\t\t\t'apns-push-type': 'background',\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\treturn message;\n\t}\n\n\t/**\n\t * Sends a push notification (visible or invisible) to multiple device tokens\n\t * @param payload - Notification title, body, and optional data\n\t * @param tokens - FCM device tokens\n\t * @returns Object containing success and invalid tokens\n\t */\n\tasync sendNotification(\n\t\tpayload: FCMNotificationPayload,\n\t\ttokens: string[]\n\t): Promise<{ successTokens: string[]; failedTokens: string[] }> {\n\t\tif (!tokens.length) return { successTokens: [], failedTokens: [] };\n\n\t\tconst tokenChunks = this.chunkArray(tokens, FCMService.MAX_BATCH);\n\n\t\tlet successTokens: string[] = [];\n\t\tlet failedTokens: string[] = [];\n\n\t\ttry {\n\t\t\t// Parallel batch sending (safe & fast)\n\t\t\tconst results = await Promise.all(\n\t\t\t\ttokenChunks.map(async chunk => {\n\t\t\t\t\tconst message = this.buildMessage(payload, chunk);\n\t\t\t\t\tconst response = await this.messaging.sendEachForMulticast(message);\n\n\t\t\t\t\tconst batchSuccess: string[] = [];\n\t\t\t\t\tconst batchFailed: string[] = [];\n\n\t\t\t\t\tresponse.responses.forEach((resp, idx) => {\n\t\t\t\t\t\tif (resp.success) {\n\t\t\t\t\t\t\tbatchSuccess.push(chunk[idx]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst errorCode = resp.error?.code;\n\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\terrorCode === 'messaging/invalid-argument' ||\n\t\t\t\t\t\t\t\terrorCode === 'messaging/registration-token-not-registered'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tbatchFailed.push(chunk[idx]);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconsole.warn(`FCM Error for token ${chunk[idx]}:`, resp.error?.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\treturn { batchSuccess, batchFailed };\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tfor (const r of results) {\n\t\t\t\tsuccessTokens.push(...r.batchSuccess);\n\t\t\t\tfailedTokens.push(...r.batchFailed);\n\t\t\t}\n\n\t\t\treturn { successTokens, failedTokens };\n\t\t} catch (err) {\n\t\t\tconsole.error('FCM sendNotification error:', err);\n\t\t\tthrow err;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"fcmService.js","sourceRoot":"/","sources":["libraries/firebase/fcmService.ts"],"names":[],"mappings":"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","sourcesContent":["import { messaging } from 'firebase-admin';\nimport { getLibraries } from '../..';\nimport { Service } from 'typedi';\nimport { App } from 'firebase-admin/app';\nimport { AppPlatform, AuthEntityType } from '../../utilities/enum';\n\nexport interface FCMNotificationPayload {\n\ttitle?: string;\n\tbody?: string;\n\tdata?: Record<string, string>;\n\tsound?: string;\n\tinvisible?: boolean;\n}\n\nexport interface NotificationTokenTarget {\n\ttoken: string;\n\tentityType: AuthEntityType;\n\tentityId: string;\n\tplatform?: AppPlatform;\n\tappVersion?: string;\n}\n\ntype NotifiableEntityType = AuthEntityType.USER | AuthEntityType.SELLER | AuthEntityType.DELIVERY_BOY;\n\nconst NotificationVersionRules: Record<NotifiableEntityType, Partial<Record<AppPlatform, string>>> = {\n\t[AuthEntityType.DELIVERY_BOY]: {\n\t\t[AppPlatform.IOS]: '1.0.4',\n\t\t[AppPlatform.ANDROID]: '1.2.1',\n\t},\n\n\t[AuthEntityType.USER]: {\n\t\t[AppPlatform.IOS]: '1.0.2',\n\t\t[AppPlatform.ANDROID]: '1.1.3',\n\t},\n\n\t[AuthEntityType.SELLER]: {\n\t\t[AppPlatform.IOS]: '1.1.0',\n\t\t[AppPlatform.ANDROID]: '1.0.9',\n\t},\n};\n\nexport const FCMServiceForUser = 'fcm.service.user';\nexport const FCMServiceForRider = 'fcm.service.rider';\nexport const FCMServiceForShop = 'fcm.service.shop';\n\n@Service()\nexport class FCMService {\n\tprivate messaging!: messaging.Messaging;\n\tprivate static readonly MAX_BATCH = 500;\n\n\tconstructor(app?: App) {\n\t\tconst firebaseApp = app ?? getLibraries().getFirebaseService().superApp;\n\t\tthis.messaging = messaging(firebaseApp);\n\t}\n\n\t/**\n\t * Split array into chunks of fixed size\n\t */\n\tprivate chunkArray<T>(arr: T[], size: number): T[][] {\n\t\tconst chunks: T[][] = [];\n\t\tfor (let i = 0; i < arr.length; i += size) {\n\t\t\tchunks.push(arr.slice(i, i + size));\n\t\t}\n\t\treturn chunks;\n\t}\n\n\tprivate compareVersions(v1: string, v2: string): number {\n\t\tconst a = v1.split('.').map(Number);\n\t\tconst b = v2.split('.').map(Number);\n\n\t\tfor (let i = 0; i < Math.max(a.length, b.length); i++) {\n\t\t\tconst diff = (a[i] || 0) - (b[i] || 0);\n\t\t\tif (diff !== 0) return diff;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tprivate isNotifiableEntityType(type: AuthEntityType): type is NotifiableEntityType {\n\t\treturn (\n\t\t\ttype === AuthEntityType.USER || type === AuthEntityType.SELLER || type === AuthEntityType.DELIVERY_BOY\n\t\t);\n\t}\n\n\tprivate shouldUseNewStructure(\n\t\tentityType: AuthEntityType,\n\t\tplatform?: AppPlatform,\n\t\tversion?: string\n\t): boolean {\n\t\tif (!this.isNotifiableEntityType(entityType)) return false;\n\n\t\tif (!platform || !version) return false;\n\n\t\tconst minVersion = NotificationVersionRules?.[entityType]?.[platform];\n\n\t\tif (!minVersion) return false;\n\n\t\treturn this.compareVersions(version, minVersion) >= 0;\n\t}\n\n\t/**\n\t * Build multicast message safely\n\t */\n\tprivate buildMessage(payload: FCMNotificationPayload, batchTokens: string[]): messaging.MulticastMessage {\n\t\tconst isInvisible = payload.invisible === true;\n\t\tconst channelId = payload.sound ? 'order_alert' : 'default';\n\n\t\tconst message: messaging.MulticastMessage = {\n\t\t\tdata: { ...payload.data, channelId: isInvisible ? 'silent' : channelId },\n\t\t\tnotification: {\n\t\t\t\ttitle: payload.title,\n\t\t\t\tbody: payload.body,\n\t\t\t},\n\t\t\ttokens: batchTokens,\n\t\t\tandroid: {\n\t\t\t\tpriority: 'high',\n\t\t\t\tnotification: {\n\t\t\t\t\tchannelId: 'silent',\n\t\t\t\t},\n\t\t\t},\n\t\t\tapns: {\n\t\t\t\tpayload: {\n\t\t\t\t\taps: {\n\t\t\t\t\t\tsound: payload.sound || 'default',\n\t\t\t\t\t\tcontentAvailable: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\theaders: {\n\t\t\t\t\t'apns-push-type': 'background',\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\treturn message;\n\t}\n\n\t/**\n\t * Build multicast message safely for old apps\n\t */\n\tprivate buildOldMessage(\n\t\tpayload: FCMNotificationPayload,\n\t\tbatchTokens: string[]\n\t): messaging.MulticastMessage {\n\t\tconst isInvisible = payload.invisible === true;\n\n\t\tconst message: messaging.MulticastMessage = {\n\t\t\tdata: payload.data || {},\n\t\t\tnotification: {\n\t\t\t\ttitle: payload.title,\n\t\t\t\tbody: payload.body,\n\t\t\t},\n\t\t\ttokens: batchTokens,\n\t\t\tandroid: { priority: 'high' },\n\t\t\tapns: {\n\t\t\t\tpayload: {\n\t\t\t\t\taps: {\n\t\t\t\t\t\tsound: payload.sound || 'default',\n\t\t\t\t\t\tcontentAvailable: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tif (!isInvisible) {\n\t\t\tmessage.notification = {\n\t\t\t\ttitle: payload.title,\n\t\t\t\tbody: payload.body,\n\t\t\t};\n\n\t\t\tmessage.android = {\n\t\t\t\tpriority: 'high',\n\t\t\t\tnotification: {\n\t\t\t\t\tchannelId: payload.sound ? 'android_Push_Notification_Channel_1' : 'default',\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn message;\n\t}\n\n\t/**\n\t * Sends a push notification (visible or invisible) to multiple device tokens\n\t * @param payload - Notification title, body, and optional data\n\t * @param tokens - FCM device tokens\n\t * @returns Object containing success and invalid tokens\n\t */\n\tasync sendNotification(\n\t\tpayload: FCMNotificationPayload,\n\t\ttargets: NotificationTokenTarget[]\n\t): Promise<{ successTokens: string[]; failedTokens: string[] }> {\n\t\tif (!targets.length) {\n\t\t\treturn { successTokens: [], failedTokens: [] };\n\t\t}\n\n\t\tconst oldTokens: string[] = [];\n\t\tconst newTokens: string[] = [];\n\n\t\tfor (const t of targets) {\n\t\t\tconst useNew = this.shouldUseNewStructure(t.entityType, t.platform, t.appVersion);\n\n\t\t\tif (useNew) newTokens.push(t.token);\n\t\t\telse oldTokens.push(t.token);\n\t\t}\n\n\t\tconst sendGroup = async (\n\t\t\tgroupTokens: string[],\n\t\t\tgroupPayload: FCMNotificationPayload,\n\t\t\toldNotificationStructure: boolean = false\n\t\t) => {\n\t\t\tconst tokenChunks = this.chunkArray(groupTokens, FCMService.MAX_BATCH);\n\n\t\t\tconst success: string[] = [];\n\t\t\tconst failed: string[] = [];\n\n\t\t\tconst results = await Promise.all(\n\t\t\t\ttokenChunks.map(async chunk => {\n\t\t\t\t\tconst message = oldNotificationStructure\n\t\t\t\t\t\t? this.buildOldMessage(groupPayload, chunk)\n\t\t\t\t\t\t: this.buildMessage(groupPayload, chunk);\n\n\t\t\t\t\tconst response = await this.messaging.sendEachForMulticast(message);\n\n\t\t\t\t\tconst batchSuccess: string[] = [];\n\t\t\t\t\tconst batchFailed: string[] = [];\n\n\t\t\t\t\tresponse.responses.forEach((resp, idx) => {\n\t\t\t\t\t\tif (resp.success) {\n\t\t\t\t\t\t\tbatchSuccess.push(chunk[idx]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst errorCode = resp.error?.code;\n\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\terrorCode === 'messaging/invalid-argument' ||\n\t\t\t\t\t\t\t\terrorCode === 'messaging/registration-token-not-registered'\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tbatchFailed.push(chunk[idx]);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconsole.warn(`FCM Error for token ${chunk[idx]}:`, resp.error?.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\treturn { batchSuccess, batchFailed };\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tfor (const r of results) {\n\t\t\t\tsuccess.push(...r.batchSuccess);\n\t\t\t\tfailed.push(...r.batchFailed);\n\t\t\t}\n\n\t\t\treturn { success, failed };\n\t\t};\n\n\t\tlet successTokens: string[] = [];\n\t\tlet failedTokens: string[] = [];\n\n\t\t// send old payload\n\t\tif (oldTokens.length) {\n\t\t\tconst r = await sendGroup(oldTokens, payload, true);\n\n\t\t\tsuccessTokens.push(...r.success);\n\t\t\tfailedTokens.push(...r.failed);\n\t\t}\n\n\t\t// send new payload\n\t\tif (newTokens.length) {\n\t\t\tconst r = await sendGroup(newTokens, payload, false);\n\n\t\t\tsuccessTokens.push(...r.success);\n\t\t\tfailedTokens.push(...r.failed);\n\t\t}\n\n\t\treturn { successTokens, failedTokens };\n\t}\n}\n"]}
@@ -5,10 +5,10 @@ interface AuthOptions {
5
5
  autoRefresh?: boolean;
6
6
  }
7
7
  export declare function createAuthenticatedProcedure(options: AuthOptions): import("@trpc/server").TRPCProcedureBuilder<import("../context").LyxaHTTPContext, object, {
8
- req: import("http").IncomingMessage;
9
8
  res: import("http").ServerResponse<import("http").IncomingMessage>;
10
9
  tokenType: TokenType | undefined;
11
10
  entity: import("../context").EntityContext | undefined;
11
+ req: import("http").IncomingMessage;
12
12
  requestId: string | undefined;
13
13
  usedRefreshToken: boolean | undefined;
14
14
  tokenRenewed: boolean | undefined;
@@ -4,10 +4,10 @@ interface RoleProtectedOptions {
4
4
  allowedRoles: string[];
5
5
  }
6
6
  export declare function createRoleProtectedProcedure(options: RoleProtectedOptions): import("@trpc/server").TRPCProcedureBuilder<import("../context").LyxaHTTPContext, object, {
7
- req: import("http").IncomingMessage;
8
7
  res: import("http").ServerResponse<import("http").IncomingMessage>;
9
8
  tokenType: import("../../auth").TokenType | undefined;
10
9
  entity: import("../context").EntityContext | undefined;
10
+ req: import("http").IncomingMessage;
11
11
  requestId: string | undefined;
12
12
  usedRefreshToken: boolean | undefined;
13
13
  tokenRenewed: boolean | undefined;
@@ -3,10 +3,10 @@ interface PhoneVerifiedOptions {
3
3
  entityTypes: AuthEntityType[];
4
4
  }
5
5
  export declare function createPhoneVerifiedProcedure(options: PhoneVerifiedOptions): import("@trpc/server").TRPCProcedureBuilder<import("../context").LyxaHTTPContext, object, {
6
- req: import("http").IncomingMessage;
7
6
  res: import("http").ServerResponse<import("http").IncomingMessage>;
8
7
  tokenType: import("../../auth").TokenType | undefined;
9
8
  entity: import("../context").EntityContext | undefined;
9
+ req: import("http").IncomingMessage;
10
10
  requestId: string | undefined;
11
11
  usedRefreshToken: boolean | undefined;
12
12
  tokenRenewed: boolean | undefined;
@@ -5,10 +5,10 @@ interface AuthOptions {
5
5
  autoRefresh?: boolean;
6
6
  }
7
7
  export declare function publicUserDecoder(options: AuthOptions): import("@trpc/server").TRPCProcedureBuilder<import("../context").LyxaHTTPContext, object, {
8
- req: import("http").IncomingMessage;
9
8
  res: import("http").ServerResponse<import("http").IncomingMessage>;
10
9
  tokenType: TokenType | undefined;
11
10
  entity: import("../context").EntityContext | undefined;
11
+ req: import("http").IncomingMessage;
12
12
  requestId: string | undefined;
13
13
  usedRefreshToken: boolean | undefined;
14
14
  tokenRenewed: boolean | undefined;
@@ -22,7 +22,7 @@ Perfect for sharing types between frontend and backend applications.
22
22
 
23
23
  ## Version
24
24
 
25
- Version: 1.3.257
25
+ Version: 1.3.258
26
26
 
27
27
  ## Dependencies
28
28
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyxa.ai/types",
3
- "version": "1.3.257",
3
+ "version": "1.3.258",
4
4
  "description": "Lyxa type definitions and validation schemas for both frontend and backend",
5
5
  "author": "elie <42282499+Internalizable@users.noreply.github.com>",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyxa.ai/core",
3
- "version": "1.3.257",
3
+ "version": "1.3.258",
4
4
  "description": "The Core system of the Lyxa services.",
5
5
  "author": "elie <42282499+Internalizable@users.noreply.github.com>",
6
6
  "license": "MIT",