@k-msg/channel 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,2417 @@
1
+ // src/types/channel.types.ts
2
+ import { z } from "zod";
3
+ var ChannelType = /* @__PURE__ */ ((ChannelType2) => {
4
+ ChannelType2["KAKAO_ALIMTALK"] = "KAKAO_ALIMTALK";
5
+ ChannelType2["KAKAO_FRIENDTALK"] = "KAKAO_FRIENDTALK";
6
+ ChannelType2["SMS"] = "SMS";
7
+ ChannelType2["LMS"] = "LMS";
8
+ ChannelType2["MMS"] = "MMS";
9
+ return ChannelType2;
10
+ })(ChannelType || {});
11
+ var ChannelStatus = /* @__PURE__ */ ((ChannelStatus2) => {
12
+ ChannelStatus2["PENDING"] = "PENDING";
13
+ ChannelStatus2["VERIFYING"] = "VERIFYING";
14
+ ChannelStatus2["ACTIVE"] = "ACTIVE";
15
+ ChannelStatus2["SUSPENDED"] = "SUSPENDED";
16
+ ChannelStatus2["BLOCKED"] = "BLOCKED";
17
+ ChannelStatus2["DELETED"] = "DELETED";
18
+ return ChannelStatus2;
19
+ })(ChannelStatus || {});
20
+ var SenderNumberStatus = /* @__PURE__ */ ((SenderNumberStatus2) => {
21
+ SenderNumberStatus2["PENDING"] = "PENDING";
22
+ SenderNumberStatus2["VERIFYING"] = "VERIFYING";
23
+ SenderNumberStatus2["VERIFIED"] = "VERIFIED";
24
+ SenderNumberStatus2["REJECTED"] = "REJECTED";
25
+ SenderNumberStatus2["BLOCKED"] = "BLOCKED";
26
+ return SenderNumberStatus2;
27
+ })(SenderNumberStatus || {});
28
+ var SenderNumberCategory = /* @__PURE__ */ ((SenderNumberCategory3) => {
29
+ SenderNumberCategory3["BUSINESS"] = "BUSINESS";
30
+ SenderNumberCategory3["PERSONAL"] = "PERSONAL";
31
+ SenderNumberCategory3["GOVERNMENT"] = "GOVERNMENT";
32
+ SenderNumberCategory3["NON_PROFIT"] = "NON_PROFIT";
33
+ return SenderNumberCategory3;
34
+ })(SenderNumberCategory || {});
35
+ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
36
+ VerificationStatus2["NOT_REQUIRED"] = "NOT_REQUIRED";
37
+ VerificationStatus2["PENDING"] = "PENDING";
38
+ VerificationStatus2["UNDER_REVIEW"] = "UNDER_REVIEW";
39
+ VerificationStatus2["VERIFIED"] = "VERIFIED";
40
+ VerificationStatus2["REJECTED"] = "REJECTED";
41
+ return VerificationStatus2;
42
+ })(VerificationStatus || {});
43
+ var DocumentType = /* @__PURE__ */ ((DocumentType2) => {
44
+ DocumentType2["BUSINESS_REGISTRATION"] = "BUSINESS_REGISTRATION";
45
+ DocumentType2["BUSINESS_LICENSE"] = "BUSINESS_LICENSE";
46
+ DocumentType2["ID_CARD"] = "ID_CARD";
47
+ DocumentType2["AUTHORIZATION_LETTER"] = "AUTHORIZATION_LETTER";
48
+ DocumentType2["OTHER"] = "OTHER";
49
+ return DocumentType2;
50
+ })(DocumentType || {});
51
+ var DocumentStatus = /* @__PURE__ */ ((DocumentStatus2) => {
52
+ DocumentStatus2["UPLOADED"] = "UPLOADED";
53
+ DocumentStatus2["VERIFIED"] = "VERIFIED";
54
+ DocumentStatus2["REJECTED"] = "REJECTED";
55
+ return DocumentStatus2;
56
+ })(DocumentStatus || {});
57
+ var ChannelCreateRequestSchema = z.object({
58
+ name: z.string().min(1).max(100),
59
+ type: z.nativeEnum(ChannelType),
60
+ provider: z.string().min(1),
61
+ profileKey: z.string().min(1),
62
+ businessInfo: z.object({
63
+ name: z.string().min(1),
64
+ registrationNumber: z.string().min(1),
65
+ category: z.string().min(1),
66
+ contactPerson: z.string().min(1),
67
+ contactEmail: z.string().email(),
68
+ contactPhone: z.string().regex(/^[0-9-+\s()]+$/)
69
+ }).optional(),
70
+ kakaoInfo: z.object({
71
+ plusFriendId: z.string().min(1),
72
+ brandName: z.string().min(1),
73
+ logoUrl: z.string().url().optional(),
74
+ description: z.string().max(500).optional()
75
+ }).optional()
76
+ });
77
+ var SenderNumberCreateRequestSchema = z.object({
78
+ phoneNumber: z.string().regex(/^[0-9]{10,11}$/),
79
+ category: z.nativeEnum(SenderNumberCategory),
80
+ businessInfo: z.object({
81
+ businessName: z.string().min(1),
82
+ businessRegistrationNumber: z.string().min(1),
83
+ contactPerson: z.string().min(1),
84
+ contactEmail: z.string().email()
85
+ }).optional()
86
+ });
87
+ var ChannelFiltersSchema = z.object({
88
+ provider: z.string().optional(),
89
+ type: z.nativeEnum(ChannelType).optional(),
90
+ status: z.nativeEnum(ChannelStatus).optional(),
91
+ verified: z.boolean().optional(),
92
+ createdAfter: z.date().optional(),
93
+ createdBefore: z.date().optional()
94
+ });
95
+ var SenderNumberFiltersSchema = z.object({
96
+ channelId: z.string().optional(),
97
+ status: z.nativeEnum(SenderNumberStatus).optional(),
98
+ category: z.nativeEnum(SenderNumberCategory).optional(),
99
+ verified: z.boolean().optional()
100
+ });
101
+
102
+ // src/kakao/channel.ts
103
+ var KakaoChannelManager = class {
104
+ constructor() {
105
+ this.channels = /* @__PURE__ */ new Map();
106
+ }
107
+ async createChannel(request) {
108
+ this.validateKakaoChannelRequest(request);
109
+ const channelId = this.generateChannelId();
110
+ const channel = {
111
+ id: channelId,
112
+ name: request.name,
113
+ provider: request.provider,
114
+ type: request.type,
115
+ status: "PENDING" /* PENDING */,
116
+ profileKey: request.profileKey,
117
+ senderNumbers: [],
118
+ metadata: {
119
+ businessInfo: request.businessInfo,
120
+ kakaoInfo: request.kakaoInfo,
121
+ limits: {
122
+ dailyMessageLimit: 1e4,
123
+ monthlyMessageLimit: 3e5,
124
+ rateLimit: 10
125
+ // 10 messages per second
126
+ },
127
+ features: {
128
+ supportsBulkSending: true,
129
+ supportsScheduling: true,
130
+ supportsButtons: true,
131
+ maxButtonCount: 5
132
+ }
133
+ },
134
+ verification: {
135
+ status: request.businessInfo ? "PENDING" /* PENDING */ : "NOT_REQUIRED" /* NOT_REQUIRED */,
136
+ documents: []
137
+ },
138
+ createdAt: /* @__PURE__ */ new Date(),
139
+ updatedAt: /* @__PURE__ */ new Date()
140
+ };
141
+ this.channels.set(channelId, channel);
142
+ if (request.businessInfo) {
143
+ await this.initiateBusinessVerification(channel);
144
+ }
145
+ return channel;
146
+ }
147
+ validateKakaoChannelRequest(request) {
148
+ if (request.type !== "KAKAO_ALIMTALK" /* KAKAO_ALIMTALK */ && request.type !== "KAKAO_FRIENDTALK" /* KAKAO_FRIENDTALK */) {
149
+ throw new Error("Invalid channel type for Kakao channel");
150
+ }
151
+ if (!request.kakaoInfo?.plusFriendId) {
152
+ throw new Error("Plus Friend ID is required for Kakao channels");
153
+ }
154
+ if (!request.kakaoInfo?.brandName) {
155
+ throw new Error("Brand name is required for Kakao channels");
156
+ }
157
+ if (!this.isValidPlusFriendId(request.kakaoInfo.plusFriendId)) {
158
+ throw new Error("Invalid Plus Friend ID format");
159
+ }
160
+ }
161
+ isValidPlusFriendId(plusFriendId) {
162
+ const regex = /^@[a-zA-Z0-9_-]{3,30}$/;
163
+ return regex.test(plusFriendId);
164
+ }
165
+ async initiateBusinessVerification(channel) {
166
+ channel.verification.status = "UNDER_REVIEW" /* UNDER_REVIEW */;
167
+ channel.status = "VERIFYING" /* VERIFYING */;
168
+ channel.updatedAt = /* @__PURE__ */ new Date();
169
+ setTimeout(() => {
170
+ this.completeVerification(channel.id, true);
171
+ }, 5e3);
172
+ }
173
+ async completeVerification(channelId, approved, rejectionReason) {
174
+ const channel = this.channels.get(channelId);
175
+ if (!channel) {
176
+ throw new Error("Channel not found");
177
+ }
178
+ if (approved) {
179
+ channel.verification.status = "VERIFIED" /* VERIFIED */;
180
+ channel.verification.verifiedAt = /* @__PURE__ */ new Date();
181
+ channel.status = "ACTIVE" /* ACTIVE */;
182
+ } else {
183
+ channel.verification.status = "REJECTED" /* REJECTED */;
184
+ channel.verification.rejectedAt = /* @__PURE__ */ new Date();
185
+ channel.verification.rejectionReason = rejectionReason || "Verification failed";
186
+ channel.status = "SUSPENDED" /* SUSPENDED */;
187
+ }
188
+ channel.updatedAt = /* @__PURE__ */ new Date();
189
+ }
190
+ async getChannel(channelId) {
191
+ return this.channels.get(channelId) || null;
192
+ }
193
+ async updateChannel(channelId, updates) {
194
+ const channel = this.channels.get(channelId);
195
+ if (!channel) {
196
+ throw new Error("Channel not found");
197
+ }
198
+ if (updates.metadata?.kakaoInfo?.plusFriendId && !this.isValidPlusFriendId(updates.metadata.kakaoInfo.plusFriendId)) {
199
+ throw new Error("Invalid Plus Friend ID format");
200
+ }
201
+ Object.assign(channel, updates, { updatedAt: /* @__PURE__ */ new Date() });
202
+ return channel;
203
+ }
204
+ async deleteChannel(channelId) {
205
+ const channel = this.channels.get(channelId);
206
+ if (!channel) {
207
+ return false;
208
+ }
209
+ channel.status = "DELETED" /* DELETED */;
210
+ channel.updatedAt = /* @__PURE__ */ new Date();
211
+ return true;
212
+ }
213
+ async listChannels(filters) {
214
+ let channels = Array.from(this.channels.values());
215
+ if (filters) {
216
+ if (filters.status) {
217
+ channels = channels.filter((c) => c.status === filters.status);
218
+ }
219
+ if (filters.type) {
220
+ channels = channels.filter((c) => c.type === filters.type);
221
+ }
222
+ if (filters.verified !== void 0) {
223
+ const verifiedStatus = filters.verified ? "VERIFIED" /* VERIFIED */ : "PENDING" /* PENDING */;
224
+ channels = channels.filter((c) => c.verification.status === verifiedStatus);
225
+ }
226
+ }
227
+ return channels.filter((c) => c.status !== "DELETED" /* DELETED */);
228
+ }
229
+ async suspendChannel(channelId, reason) {
230
+ const channel = this.channels.get(channelId);
231
+ if (!channel) {
232
+ throw new Error("Channel not found");
233
+ }
234
+ channel.status = "SUSPENDED" /* SUSPENDED */;
235
+ channel.updatedAt = /* @__PURE__ */ new Date();
236
+ console.log(`Channel ${channelId} suspended: ${reason}`);
237
+ }
238
+ async reactivateChannel(channelId) {
239
+ const channel = this.channels.get(channelId);
240
+ if (!channel) {
241
+ throw new Error("Channel not found");
242
+ }
243
+ if (channel.verification.status !== "VERIFIED" /* VERIFIED */) {
244
+ throw new Error("Channel must be verified before reactivation");
245
+ }
246
+ channel.status = "ACTIVE" /* ACTIVE */;
247
+ channel.updatedAt = /* @__PURE__ */ new Date();
248
+ }
249
+ async checkChannelHealth(channelId) {
250
+ const channel = this.channels.get(channelId);
251
+ if (!channel) {
252
+ throw new Error("Channel not found");
253
+ }
254
+ const issues = [];
255
+ const recommendations = [];
256
+ if (channel.status !== "ACTIVE" /* ACTIVE */) {
257
+ issues.push(`Channel status is ${channel.status}`);
258
+ }
259
+ if (channel.verification.status !== "VERIFIED" /* VERIFIED */ && channel.verification.status !== "NOT_REQUIRED" /* NOT_REQUIRED */) {
260
+ issues.push(`Channel verification is ${channel.verification.status}`);
261
+ }
262
+ if (channel.senderNumbers.length === 0) {
263
+ recommendations.push("Add at least one verified sender number");
264
+ }
265
+ if (!channel.metadata.businessInfo) {
266
+ recommendations.push("Complete business information for better deliverability");
267
+ }
268
+ return {
269
+ isHealthy: issues.length === 0,
270
+ issues,
271
+ recommendations
272
+ };
273
+ }
274
+ generateChannelId() {
275
+ return `kakao_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
276
+ }
277
+ };
278
+
279
+ // src/kakao/sender-number.ts
280
+ var KakaoSenderNumberManager = class {
281
+ constructor() {
282
+ this.senderNumbers = /* @__PURE__ */ new Map();
283
+ this.verificationCodes = /* @__PURE__ */ new Map();
284
+ }
285
+ async addSenderNumber(channelId, request) {
286
+ this.validatePhoneNumber(request.phoneNumber);
287
+ const existingNumber = this.findSenderNumberByPhone(request.phoneNumber);
288
+ if (existingNumber) {
289
+ throw new Error("Phone number is already registered");
290
+ }
291
+ const senderNumberId = this.generateSenderNumberId();
292
+ const senderNumber = {
293
+ id: senderNumberId,
294
+ phoneNumber: request.phoneNumber,
295
+ status: "PENDING" /* PENDING */,
296
+ category: request.category,
297
+ metadata: {
298
+ businessName: request.businessInfo?.businessName,
299
+ businessRegistrationNumber: request.businessInfo?.businessRegistrationNumber,
300
+ contactPerson: request.businessInfo?.contactPerson,
301
+ contactEmail: request.businessInfo?.contactEmail
302
+ },
303
+ createdAt: /* @__PURE__ */ new Date(),
304
+ updatedAt: /* @__PURE__ */ new Date()
305
+ };
306
+ this.senderNumbers.set(senderNumberId, senderNumber);
307
+ await this.initiateVerification(senderNumber);
308
+ return senderNumber;
309
+ }
310
+ validatePhoneNumber(phoneNumber) {
311
+ const regex = /^(010|011|016|017|018|019)[0-9]{7,8}$/;
312
+ if (!regex.test(phoneNumber)) {
313
+ throw new Error("Invalid Korean phone number format");
314
+ }
315
+ }
316
+ findSenderNumberByPhone(phoneNumber) {
317
+ return Array.from(this.senderNumbers.values()).find((sn) => sn.phoneNumber === phoneNumber);
318
+ }
319
+ async initiateVerification(senderNumber) {
320
+ const verificationCode = this.generateVerificationCode();
321
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3);
322
+ this.verificationCodes.set(senderNumber.id, {
323
+ code: verificationCode,
324
+ expiresAt
325
+ });
326
+ senderNumber.status = "VERIFYING" /* VERIFYING */;
327
+ senderNumber.verificationCode = verificationCode;
328
+ senderNumber.updatedAt = /* @__PURE__ */ new Date();
329
+ console.log(`Verification code for ${senderNumber.phoneNumber}: ${verificationCode}`);
330
+ await this.sendVerificationSMS(senderNumber.phoneNumber, verificationCode);
331
+ }
332
+ async sendVerificationSMS(phoneNumber, code) {
333
+ console.log(`Sending SMS to ${phoneNumber}: Your verification code is ${code}`);
334
+ }
335
+ async verifySenderNumber(senderNumberId, code) {
336
+ const senderNumber = this.senderNumbers.get(senderNumberId);
337
+ if (!senderNumber) {
338
+ throw new Error("Sender number not found");
339
+ }
340
+ const verification = this.verificationCodes.get(senderNumberId);
341
+ if (!verification) {
342
+ throw new Error("No verification code found");
343
+ }
344
+ if (/* @__PURE__ */ new Date() > verification.expiresAt) {
345
+ throw new Error("Verification code has expired");
346
+ }
347
+ if (verification.code !== code) {
348
+ return false;
349
+ }
350
+ senderNumber.status = "VERIFIED" /* VERIFIED */;
351
+ senderNumber.verifiedAt = /* @__PURE__ */ new Date();
352
+ senderNumber.updatedAt = /* @__PURE__ */ new Date();
353
+ delete senderNumber.verificationCode;
354
+ this.verificationCodes.delete(senderNumberId);
355
+ return true;
356
+ }
357
+ async resendVerificationCode(senderNumberId) {
358
+ const senderNumber = this.senderNumbers.get(senderNumberId);
359
+ if (!senderNumber) {
360
+ throw new Error("Sender number not found");
361
+ }
362
+ if (senderNumber.status !== "VERIFYING" /* VERIFYING */) {
363
+ throw new Error("Sender number is not in verifying status");
364
+ }
365
+ const lastVerification = this.verificationCodes.get(senderNumberId);
366
+ if (lastVerification) {
367
+ const timeSinceLastCode = Date.now() - (lastVerification.expiresAt.getTime() - 5 * 60 * 1e3);
368
+ if (timeSinceLastCode < 60 * 1e3) {
369
+ throw new Error("Please wait before requesting a new verification code");
370
+ }
371
+ }
372
+ await this.initiateVerification(senderNumber);
373
+ }
374
+ async getSenderNumber(senderNumberId) {
375
+ return this.senderNumbers.get(senderNumberId) || null;
376
+ }
377
+ async listSenderNumbers(filters) {
378
+ let senderNumbers = Array.from(this.senderNumbers.values());
379
+ if (filters) {
380
+ if (filters.status) {
381
+ senderNumbers = senderNumbers.filter((sn) => sn.status === filters.status);
382
+ }
383
+ if (filters.category) {
384
+ senderNumbers = senderNumbers.filter((sn) => sn.category === filters.category);
385
+ }
386
+ if (filters.verified !== void 0) {
387
+ if (filters.verified) {
388
+ senderNumbers = senderNumbers.filter((sn) => sn.status === "VERIFIED" /* VERIFIED */);
389
+ } else {
390
+ senderNumbers = senderNumbers.filter((sn) => sn.status !== "VERIFIED" /* VERIFIED */);
391
+ }
392
+ }
393
+ }
394
+ return senderNumbers;
395
+ }
396
+ async updateSenderNumber(senderNumberId, updates) {
397
+ const senderNumber = this.senderNumbers.get(senderNumberId);
398
+ if (!senderNumber) {
399
+ throw new Error("Sender number not found");
400
+ }
401
+ const allowedUpdates = { ...updates };
402
+ delete allowedUpdates.id;
403
+ delete allowedUpdates.phoneNumber;
404
+ delete allowedUpdates.verifiedAt;
405
+ delete allowedUpdates.createdAt;
406
+ Object.assign(senderNumber, allowedUpdates, { updatedAt: /* @__PURE__ */ new Date() });
407
+ return senderNumber;
408
+ }
409
+ async deleteSenderNumber(senderNumberId) {
410
+ const senderNumber = this.senderNumbers.get(senderNumberId);
411
+ if (!senderNumber) {
412
+ return false;
413
+ }
414
+ if (await this.isSenderNumberInUse(senderNumberId)) {
415
+ throw new Error("Cannot delete sender number that is currently in use");
416
+ }
417
+ this.senderNumbers.delete(senderNumberId);
418
+ this.verificationCodes.delete(senderNumberId);
419
+ return true;
420
+ }
421
+ async isSenderNumberInUse(senderNumberId) {
422
+ return false;
423
+ }
424
+ async blockSenderNumber(senderNumberId, reason) {
425
+ const senderNumber = this.senderNumbers.get(senderNumberId);
426
+ if (!senderNumber) {
427
+ throw new Error("Sender number not found");
428
+ }
429
+ senderNumber.status = "BLOCKED" /* BLOCKED */;
430
+ senderNumber.updatedAt = /* @__PURE__ */ new Date();
431
+ console.log(`Sender number ${senderNumberId} blocked: ${reason}`);
432
+ }
433
+ async unblockSenderNumber(senderNumberId) {
434
+ const senderNumber = this.senderNumbers.get(senderNumberId);
435
+ if (!senderNumber) {
436
+ throw new Error("Sender number not found");
437
+ }
438
+ if (senderNumber.status !== "BLOCKED" /* BLOCKED */) {
439
+ throw new Error("Sender number is not blocked");
440
+ }
441
+ senderNumber.status = senderNumber.verifiedAt ? "VERIFIED" /* VERIFIED */ : "PENDING" /* PENDING */;
442
+ senderNumber.updatedAt = /* @__PURE__ */ new Date();
443
+ }
444
+ async validateSenderNumberForSending(senderNumberId) {
445
+ const senderNumber = this.senderNumbers.get(senderNumberId);
446
+ const errors = [];
447
+ if (!senderNumber) {
448
+ errors.push("Sender number not found");
449
+ return { isValid: false, errors };
450
+ }
451
+ if (senderNumber.status !== "VERIFIED" /* VERIFIED */) {
452
+ errors.push(`Sender number status is ${senderNumber.status}, must be verified`);
453
+ }
454
+ if (!senderNumber.verifiedAt) {
455
+ errors.push("Sender number has not been verified");
456
+ }
457
+ if (senderNumber.verifiedAt) {
458
+ const oneYearAgo = new Date(Date.now() - 365 * 24 * 60 * 60 * 1e3);
459
+ if (senderNumber.verifiedAt < oneYearAgo) {
460
+ errors.push("Sender number verification has expired");
461
+ }
462
+ }
463
+ return {
464
+ isValid: errors.length === 0,
465
+ errors
466
+ };
467
+ }
468
+ generateSenderNumberId() {
469
+ return `sn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
470
+ }
471
+ generateVerificationCode() {
472
+ return Math.floor(1e5 + Math.random() * 9e5).toString();
473
+ }
474
+ // Cleanup expired verification codes
475
+ cleanup() {
476
+ const now = /* @__PURE__ */ new Date();
477
+ for (const [id, verification] of this.verificationCodes) {
478
+ if (now > verification.expiresAt) {
479
+ this.verificationCodes.delete(id);
480
+ const senderNumber = this.senderNumbers.get(id);
481
+ if (senderNumber && senderNumber.status === "VERIFYING" /* VERIFYING */) {
482
+ senderNumber.status = "PENDING" /* PENDING */;
483
+ delete senderNumber.verificationCode;
484
+ senderNumber.updatedAt = /* @__PURE__ */ new Date();
485
+ }
486
+ }
487
+ }
488
+ }
489
+ };
490
+
491
+ // src/management/crud.ts
492
+ import { EventEmitter } from "events";
493
+ var ChannelCRUD = class extends EventEmitter {
494
+ constructor(options = {}) {
495
+ super();
496
+ this.options = options;
497
+ this.channels = /* @__PURE__ */ new Map();
498
+ this.senderNumbers = /* @__PURE__ */ new Map();
499
+ this.auditLogs = [];
500
+ this.defaultOptions = {
501
+ enableAuditLog: true,
502
+ enableEventEmission: true,
503
+ defaultPageSize: 20,
504
+ maxPageSize: 100,
505
+ enableSoftDelete: true,
506
+ autoCleanup: true,
507
+ cleanupInterval: 36e5
508
+ // 1 hour
509
+ };
510
+ this.options = { ...this.defaultOptions, ...options };
511
+ if (this.options.autoCleanup) {
512
+ this.startAutoCleanup();
513
+ }
514
+ }
515
+ // Channel CRUD Operations
516
+ async createChannel(request, userId) {
517
+ const channelId = this.generateChannelId();
518
+ const channel = {
519
+ id: channelId,
520
+ name: request.name,
521
+ provider: request.provider,
522
+ type: request.type,
523
+ status: "PENDING" /* PENDING */,
524
+ profileKey: request.profileKey,
525
+ senderNumbers: [],
526
+ metadata: {
527
+ businessInfo: request.businessInfo,
528
+ kakaoInfo: request.kakaoInfo,
529
+ limits: this.getDefaultLimits(request.type),
530
+ features: this.getDefaultFeatures(request.type)
531
+ },
532
+ verification: {
533
+ status: request.businessInfo ? "PENDING" /* PENDING */ : "NOT_REQUIRED" /* NOT_REQUIRED */,
534
+ documents: []
535
+ },
536
+ createdAt: /* @__PURE__ */ new Date(),
537
+ updatedAt: /* @__PURE__ */ new Date()
538
+ };
539
+ this.channels.set(channelId, channel);
540
+ if (this.options.enableAuditLog) {
541
+ this.addAuditLog("channel", channelId, "create", userId, void 0, channel);
542
+ }
543
+ if (this.options.enableEventEmission) {
544
+ this.emit("channel:created", { channel, userId });
545
+ }
546
+ return channel;
547
+ }
548
+ async getChannel(channelId, userId) {
549
+ const channel = this.channels.get(channelId);
550
+ if (channel && this.options.enableAuditLog) {
551
+ this.addAuditLog("channel", channelId, "read", userId);
552
+ }
553
+ return channel || null;
554
+ }
555
+ async updateChannel(channelId, updates, userId) {
556
+ const channel = this.channels.get(channelId);
557
+ if (!channel) {
558
+ throw new Error(`Channel ${channelId} not found`);
559
+ }
560
+ const before = this.options.enableAuditLog ? { ...channel } : void 0;
561
+ const updatedChannel = {
562
+ ...channel,
563
+ ...updates,
564
+ id: channelId,
565
+ // Ensure ID doesn't change
566
+ updatedAt: /* @__PURE__ */ new Date()
567
+ };
568
+ this.channels.set(channelId, updatedChannel);
569
+ if (this.options.enableAuditLog) {
570
+ this.addAuditLog("channel", channelId, "update", userId, before, updatedChannel);
571
+ }
572
+ if (this.options.enableEventEmission) {
573
+ this.emit("channel:updated", {
574
+ channel: updatedChannel,
575
+ previousChannel: channel,
576
+ userId
577
+ });
578
+ }
579
+ return updatedChannel;
580
+ }
581
+ async deleteChannel(channelId, userId) {
582
+ const channel = this.channels.get(channelId);
583
+ if (!channel) {
584
+ return false;
585
+ }
586
+ if (this.options.enableSoftDelete) {
587
+ channel.status = "DELETED" /* DELETED */;
588
+ channel.updatedAt = /* @__PURE__ */ new Date();
589
+ } else {
590
+ this.channels.delete(channelId);
591
+ for (const [id, senderNumber] of this.senderNumbers) {
592
+ }
593
+ }
594
+ if (this.options.enableAuditLog) {
595
+ this.addAuditLog("channel", channelId, "delete", userId, channel);
596
+ }
597
+ if (this.options.enableEventEmission) {
598
+ this.emit("channel:deleted", { channel, userId });
599
+ }
600
+ return true;
601
+ }
602
+ async listChannels(filters = {}, pagination = { page: 1, limit: this.options.defaultPageSize }) {
603
+ let channels = Array.from(this.channels.values());
604
+ if (filters.provider) {
605
+ channels = channels.filter((c) => c.provider === filters.provider);
606
+ }
607
+ if (filters.type) {
608
+ channels = channels.filter((c) => c.type === filters.type);
609
+ }
610
+ if (filters.status) {
611
+ channels = channels.filter((c) => c.status === filters.status);
612
+ }
613
+ if (filters.verified !== void 0) {
614
+ const targetStatus = filters.verified ? "VERIFIED" /* VERIFIED */ : "PENDING" /* PENDING */;
615
+ channels = channels.filter((c) => c.verification.status === targetStatus);
616
+ }
617
+ if (filters.createdAfter) {
618
+ channels = channels.filter((c) => c.createdAt >= filters.createdAfter);
619
+ }
620
+ if (filters.createdBefore) {
621
+ channels = channels.filter((c) => c.createdAt <= filters.createdBefore);
622
+ }
623
+ if (!filters.status || filters.status !== "DELETED" /* DELETED */) {
624
+ channels = channels.filter((c) => c.status !== "DELETED" /* DELETED */);
625
+ }
626
+ const sortBy = pagination.sortBy || "createdAt";
627
+ const sortOrder = pagination.sortOrder || "desc";
628
+ channels.sort((a, b) => {
629
+ let aValue, bValue;
630
+ switch (sortBy) {
631
+ case "name":
632
+ aValue = a.name;
633
+ bValue = b.name;
634
+ break;
635
+ case "createdAt":
636
+ aValue = a.createdAt.getTime();
637
+ bValue = b.createdAt.getTime();
638
+ break;
639
+ case "updatedAt":
640
+ aValue = a.updatedAt.getTime();
641
+ bValue = b.updatedAt.getTime();
642
+ break;
643
+ default:
644
+ aValue = a.createdAt.getTime();
645
+ bValue = b.createdAt.getTime();
646
+ }
647
+ if (sortOrder === "asc") {
648
+ return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
649
+ } else {
650
+ return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
651
+ }
652
+ });
653
+ const total = channels.length;
654
+ const limit = Math.min(pagination.limit, this.options.maxPageSize);
655
+ const page = Math.max(1, pagination.page);
656
+ const offset = (page - 1) * limit;
657
+ const paginatedChannels = channels.slice(offset, offset + limit);
658
+ return {
659
+ data: paginatedChannels,
660
+ total,
661
+ page,
662
+ limit,
663
+ totalPages: Math.ceil(total / limit),
664
+ hasNext: offset + limit < total,
665
+ hasPrev: page > 1
666
+ };
667
+ }
668
+ // Sender Number CRUD Operations
669
+ async createSenderNumber(channelId, request, userId) {
670
+ const channel = this.channels.get(channelId);
671
+ if (!channel) {
672
+ throw new Error(`Channel ${channelId} not found`);
673
+ }
674
+ const senderNumberId = this.generateSenderNumberId();
675
+ const senderNumber = {
676
+ id: senderNumberId,
677
+ phoneNumber: request.phoneNumber,
678
+ status: "PENDING" /* PENDING */,
679
+ category: request.category,
680
+ metadata: {
681
+ businessName: request.businessInfo?.businessName,
682
+ businessRegistrationNumber: request.businessInfo?.businessRegistrationNumber,
683
+ contactPerson: request.businessInfo?.contactPerson,
684
+ contactEmail: request.businessInfo?.contactEmail
685
+ },
686
+ createdAt: /* @__PURE__ */ new Date(),
687
+ updatedAt: /* @__PURE__ */ new Date()
688
+ };
689
+ this.senderNumbers.set(senderNumberId, senderNumber);
690
+ channel.senderNumbers.push(senderNumber);
691
+ channel.updatedAt = /* @__PURE__ */ new Date();
692
+ if (this.options.enableAuditLog) {
693
+ this.addAuditLog("senderNumber", senderNumberId, "create", userId, void 0, senderNumber);
694
+ }
695
+ if (this.options.enableEventEmission) {
696
+ this.emit("senderNumber:created", { senderNumber, channelId, userId });
697
+ }
698
+ return senderNumber;
699
+ }
700
+ async getSenderNumber(senderNumberId, userId) {
701
+ const senderNumber = this.senderNumbers.get(senderNumberId);
702
+ if (senderNumber && this.options.enableAuditLog) {
703
+ this.addAuditLog("senderNumber", senderNumberId, "read", userId);
704
+ }
705
+ return senderNumber || null;
706
+ }
707
+ async updateSenderNumber(senderNumberId, updates, userId) {
708
+ const senderNumber = this.senderNumbers.get(senderNumberId);
709
+ if (!senderNumber) {
710
+ throw new Error(`Sender number ${senderNumberId} not found`);
711
+ }
712
+ const before = this.options.enableAuditLog ? { ...senderNumber } : void 0;
713
+ const updatedSenderNumber = {
714
+ ...senderNumber,
715
+ ...updates,
716
+ id: senderNumberId,
717
+ // Ensure ID doesn't change
718
+ updatedAt: /* @__PURE__ */ new Date()
719
+ };
720
+ this.senderNumbers.set(senderNumberId, updatedSenderNumber);
721
+ for (const channel of this.channels.values()) {
722
+ const index = channel.senderNumbers.findIndex((sn) => sn.id === senderNumberId);
723
+ if (index !== -1) {
724
+ channel.senderNumbers[index] = updatedSenderNumber;
725
+ channel.updatedAt = /* @__PURE__ */ new Date();
726
+ break;
727
+ }
728
+ }
729
+ if (this.options.enableAuditLog) {
730
+ this.addAuditLog("senderNumber", senderNumberId, "update", userId, before, updatedSenderNumber);
731
+ }
732
+ if (this.options.enableEventEmission) {
733
+ this.emit("senderNumber:updated", {
734
+ senderNumber: updatedSenderNumber,
735
+ previousSenderNumber: senderNumber,
736
+ userId
737
+ });
738
+ }
739
+ return updatedSenderNumber;
740
+ }
741
+ async deleteSenderNumber(senderNumberId, userId) {
742
+ const senderNumber = this.senderNumbers.get(senderNumberId);
743
+ if (!senderNumber) {
744
+ return false;
745
+ }
746
+ this.senderNumbers.delete(senderNumberId);
747
+ for (const channel of this.channels.values()) {
748
+ const index = channel.senderNumbers.findIndex((sn) => sn.id === senderNumberId);
749
+ if (index !== -1) {
750
+ channel.senderNumbers.splice(index, 1);
751
+ channel.updatedAt = /* @__PURE__ */ new Date();
752
+ break;
753
+ }
754
+ }
755
+ if (this.options.enableAuditLog) {
756
+ this.addAuditLog("senderNumber", senderNumberId, "delete", userId, senderNumber);
757
+ }
758
+ if (this.options.enableEventEmission) {
759
+ this.emit("senderNumber:deleted", { senderNumber, userId });
760
+ }
761
+ return true;
762
+ }
763
+ async listSenderNumbers(filters = {}, pagination = { page: 1, limit: this.options.defaultPageSize }) {
764
+ let senderNumbers = Array.from(this.senderNumbers.values());
765
+ if (filters.channelId) {
766
+ const channel = this.channels.get(filters.channelId);
767
+ if (channel) {
768
+ senderNumbers = channel.senderNumbers;
769
+ } else {
770
+ senderNumbers = [];
771
+ }
772
+ }
773
+ if (filters.status) {
774
+ senderNumbers = senderNumbers.filter((sn) => sn.status === filters.status);
775
+ }
776
+ if (filters.category) {
777
+ senderNumbers = senderNumbers.filter((sn) => sn.category === filters.category);
778
+ }
779
+ if (filters.verified !== void 0) {
780
+ if (filters.verified) {
781
+ senderNumbers = senderNumbers.filter((sn) => sn.status === "VERIFIED" /* VERIFIED */);
782
+ } else {
783
+ senderNumbers = senderNumbers.filter((sn) => sn.status !== "VERIFIED" /* VERIFIED */);
784
+ }
785
+ }
786
+ const sortBy = pagination.sortBy || "createdAt";
787
+ const sortOrder = pagination.sortOrder || "desc";
788
+ senderNumbers.sort((a, b) => {
789
+ let aValue, bValue;
790
+ switch (sortBy) {
791
+ case "phoneNumber":
792
+ aValue = a.phoneNumber;
793
+ bValue = b.phoneNumber;
794
+ break;
795
+ case "createdAt":
796
+ aValue = a.createdAt.getTime();
797
+ bValue = b.createdAt.getTime();
798
+ break;
799
+ case "updatedAt":
800
+ aValue = a.updatedAt.getTime();
801
+ bValue = b.updatedAt.getTime();
802
+ break;
803
+ default:
804
+ aValue = a.createdAt.getTime();
805
+ bValue = b.createdAt.getTime();
806
+ }
807
+ if (sortOrder === "asc") {
808
+ return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
809
+ } else {
810
+ return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
811
+ }
812
+ });
813
+ const total = senderNumbers.length;
814
+ const limit = Math.min(pagination.limit, this.options.maxPageSize);
815
+ const page = Math.max(1, pagination.page);
816
+ const offset = (page - 1) * limit;
817
+ const paginatedSenderNumbers = senderNumbers.slice(offset, offset + limit);
818
+ return {
819
+ data: paginatedSenderNumbers,
820
+ total,
821
+ page,
822
+ limit,
823
+ totalPages: Math.ceil(total / limit),
824
+ hasNext: offset + limit < total,
825
+ hasPrev: page > 1
826
+ };
827
+ }
828
+ // Audit and Analytics
829
+ getAuditLogs(entityType, entityId, limit = 100) {
830
+ let logs = [...this.auditLogs];
831
+ if (entityType) {
832
+ logs = logs.filter((log) => log.entityType === entityType);
833
+ }
834
+ if (entityId) {
835
+ logs = logs.filter((log) => log.entityId === entityId);
836
+ }
837
+ return logs.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()).slice(0, limit);
838
+ }
839
+ getStatistics() {
840
+ const channels = Array.from(this.channels.values());
841
+ const senderNumbers = Array.from(this.senderNumbers.values());
842
+ const channelsByStatus = {};
843
+ const channelsByType = {};
844
+ const channelsByProvider = {};
845
+ channels.forEach((channel) => {
846
+ channelsByStatus[channel.status] = (channelsByStatus[channel.status] || 0) + 1;
847
+ channelsByType[channel.type] = (channelsByType[channel.type] || 0) + 1;
848
+ channelsByProvider[channel.provider] = (channelsByProvider[channel.provider] || 0) + 1;
849
+ });
850
+ const senderNumbersByStatus = {};
851
+ const senderNumbersByCategory = {};
852
+ senderNumbers.forEach((senderNumber) => {
853
+ senderNumbersByStatus[senderNumber.status] = (senderNumbersByStatus[senderNumber.status] || 0) + 1;
854
+ senderNumbersByCategory[senderNumber.category] = (senderNumbersByCategory[senderNumber.category] || 0) + 1;
855
+ });
856
+ return {
857
+ channels: {
858
+ total: channels.length,
859
+ byStatus: channelsByStatus,
860
+ byType: channelsByType,
861
+ byProvider: channelsByProvider
862
+ },
863
+ senderNumbers: {
864
+ total: senderNumbers.length,
865
+ byStatus: senderNumbersByStatus,
866
+ byCategory: senderNumbersByCategory
867
+ }
868
+ };
869
+ }
870
+ // Cleanup and Maintenance
871
+ cleanup() {
872
+ let deletedChannels = 0;
873
+ let expiredAuditLogs = 0;
874
+ const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
875
+ for (const [id, channel] of this.channels) {
876
+ if (channel.status === "DELETED" /* DELETED */ && channel.updatedAt < thirtyDaysAgo) {
877
+ this.channels.delete(id);
878
+ deletedChannels++;
879
+ }
880
+ }
881
+ const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3);
882
+ const originalLogCount = this.auditLogs.length;
883
+ this.auditLogs = this.auditLogs.filter((log) => log.timestamp >= ninetyDaysAgo);
884
+ expiredAuditLogs = originalLogCount - this.auditLogs.length;
885
+ return { deletedChannels, expiredAuditLogs };
886
+ }
887
+ destroy() {
888
+ if (this.cleanupTimer) {
889
+ clearInterval(this.cleanupTimer);
890
+ this.cleanupTimer = void 0;
891
+ }
892
+ this.removeAllListeners();
893
+ this.channels.clear();
894
+ this.senderNumbers.clear();
895
+ this.auditLogs = [];
896
+ }
897
+ addAuditLog(entityType, entityId, action, userId, before, after) {
898
+ const auditLog = {
899
+ id: this.generateAuditLogId(),
900
+ entityType,
901
+ entityId,
902
+ action,
903
+ userId,
904
+ timestamp: /* @__PURE__ */ new Date(),
905
+ changes: before || after ? { before, after } : void 0
906
+ };
907
+ this.auditLogs.push(auditLog);
908
+ if (this.auditLogs.length > 1e4) {
909
+ this.auditLogs = this.auditLogs.slice(-1e4);
910
+ }
911
+ }
912
+ getDefaultLimits(channelType) {
913
+ switch (channelType) {
914
+ case "KAKAO_ALIMTALK" /* KAKAO_ALIMTALK */:
915
+ return {
916
+ dailyMessageLimit: 1e4,
917
+ monthlyMessageLimit: 3e5,
918
+ rateLimit: 10
919
+ };
920
+ case "KAKAO_FRIENDTALK" /* KAKAO_FRIENDTALK */:
921
+ return {
922
+ dailyMessageLimit: 1e3,
923
+ monthlyMessageLimit: 3e4,
924
+ rateLimit: 5
925
+ };
926
+ case "SMS" /* SMS */:
927
+ case "LMS" /* LMS */:
928
+ case "MMS" /* MMS */:
929
+ return {
930
+ dailyMessageLimit: 1e3,
931
+ monthlyMessageLimit: 3e4,
932
+ rateLimit: 3
933
+ };
934
+ default:
935
+ return {
936
+ dailyMessageLimit: 1e3,
937
+ monthlyMessageLimit: 3e4,
938
+ rateLimit: 1
939
+ };
940
+ }
941
+ }
942
+ getDefaultFeatures(channelType) {
943
+ switch (channelType) {
944
+ case "KAKAO_ALIMTALK" /* KAKAO_ALIMTALK */:
945
+ return {
946
+ supportsBulkSending: true,
947
+ supportsScheduling: true,
948
+ supportsButtons: true,
949
+ maxButtonCount: 5
950
+ };
951
+ case "KAKAO_FRIENDTALK" /* KAKAO_FRIENDTALK */:
952
+ return {
953
+ supportsBulkSending: true,
954
+ supportsScheduling: true,
955
+ supportsButtons: false,
956
+ maxButtonCount: 0
957
+ };
958
+ default:
959
+ return {
960
+ supportsBulkSending: false,
961
+ supportsScheduling: false,
962
+ supportsButtons: false,
963
+ maxButtonCount: 0
964
+ };
965
+ }
966
+ }
967
+ startAutoCleanup() {
968
+ this.cleanupTimer = setInterval(() => {
969
+ this.cleanup();
970
+ }, this.options.cleanupInterval);
971
+ }
972
+ generateChannelId() {
973
+ return `ch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
974
+ }
975
+ generateSenderNumberId() {
976
+ return `sn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
977
+ }
978
+ generateAuditLogId() {
979
+ return `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
980
+ }
981
+ };
982
+
983
+ // src/management/permissions.ts
984
+ import { EventEmitter as EventEmitter2 } from "events";
985
+ var ResourceType = /* @__PURE__ */ ((ResourceType2) => {
986
+ ResourceType2["CHANNEL"] = "channel";
987
+ ResourceType2["SENDER_NUMBER"] = "senderNumber";
988
+ ResourceType2["TEMPLATE"] = "template";
989
+ ResourceType2["MESSAGE"] = "message";
990
+ ResourceType2["USER"] = "user";
991
+ ResourceType2["ROLE"] = "role";
992
+ ResourceType2["AUDIT_LOG"] = "auditLog";
993
+ ResourceType2["ANALYTICS"] = "analytics";
994
+ return ResourceType2;
995
+ })(ResourceType || {});
996
+ var ActionType = /* @__PURE__ */ ((ActionType2) => {
997
+ ActionType2["CREATE"] = "create";
998
+ ActionType2["READ"] = "read";
999
+ ActionType2["UPDATE"] = "update";
1000
+ ActionType2["DELETE"] = "delete";
1001
+ ActionType2["VERIFY"] = "verify";
1002
+ ActionType2["SUSPEND"] = "suspend";
1003
+ ActionType2["ACTIVATE"] = "activate";
1004
+ ActionType2["SEND"] = "send";
1005
+ ActionType2["MANAGE"] = "manage";
1006
+ return ActionType2;
1007
+ })(ActionType || {});
1008
+ var PermissionScope = /* @__PURE__ */ ((PermissionScope2) => {
1009
+ PermissionScope2["GLOBAL"] = "global";
1010
+ PermissionScope2["ORGANIZATION"] = "organization";
1011
+ PermissionScope2["TEAM"] = "team";
1012
+ PermissionScope2["PERSONAL"] = "personal";
1013
+ return PermissionScope2;
1014
+ })(PermissionScope || {});
1015
+ var PermissionManager = class extends EventEmitter2 {
1016
+ // 5 minutes
1017
+ constructor() {
1018
+ super();
1019
+ this.users = /* @__PURE__ */ new Map();
1020
+ this.roles = /* @__PURE__ */ new Map();
1021
+ this.userRoleCache = /* @__PURE__ */ new Map();
1022
+ this.permissionCache = /* @__PURE__ */ new Map();
1023
+ this.cacheExpiry = /* @__PURE__ */ new Map();
1024
+ this.CACHE_DURATION = 5 * 60 * 1e3;
1025
+ this.initializeSystemRoles();
1026
+ }
1027
+ // User Management
1028
+ async createUser(userData) {
1029
+ const userId = this.generateUserId();
1030
+ const user = {
1031
+ ...userData,
1032
+ id: userId,
1033
+ createdAt: /* @__PURE__ */ new Date(),
1034
+ updatedAt: /* @__PURE__ */ new Date()
1035
+ };
1036
+ this.users.set(userId, user);
1037
+ this.updateUserRoleCache(userId, user.roles.map((r) => r.id));
1038
+ this.emit("user:created", { user });
1039
+ return user;
1040
+ }
1041
+ async getUser(userId) {
1042
+ return this.users.get(userId) || null;
1043
+ }
1044
+ async updateUser(userId, updates) {
1045
+ const user = this.users.get(userId);
1046
+ if (!user) {
1047
+ throw new Error("User not found");
1048
+ }
1049
+ const updatedUser = {
1050
+ ...user,
1051
+ ...updates,
1052
+ id: userId,
1053
+ updatedAt: /* @__PURE__ */ new Date()
1054
+ };
1055
+ this.users.set(userId, updatedUser);
1056
+ if (updates.roles) {
1057
+ this.updateUserRoleCache(userId, updates.roles.map((r) => r.id));
1058
+ }
1059
+ this.clearUserPermissionCache(userId);
1060
+ this.emit("user:updated", { user: updatedUser, previousUser: user });
1061
+ return updatedUser;
1062
+ }
1063
+ async deleteUser(userId) {
1064
+ const user = this.users.get(userId);
1065
+ if (!user) {
1066
+ return false;
1067
+ }
1068
+ this.users.delete(userId);
1069
+ this.userRoleCache.delete(userId);
1070
+ this.clearUserPermissionCache(userId);
1071
+ this.emit("user:deleted", { user });
1072
+ return true;
1073
+ }
1074
+ // Role Management
1075
+ async createRole(roleData) {
1076
+ const roleId = this.generateRoleId();
1077
+ const role = {
1078
+ ...roleData,
1079
+ id: roleId,
1080
+ createdAt: /* @__PURE__ */ new Date(),
1081
+ updatedAt: /* @__PURE__ */ new Date()
1082
+ };
1083
+ this.roles.set(roleId, role);
1084
+ this.emit("role:created", { role });
1085
+ return role;
1086
+ }
1087
+ async getRole(roleId) {
1088
+ return this.roles.get(roleId) || null;
1089
+ }
1090
+ async updateRole(roleId, updates) {
1091
+ const role = this.roles.get(roleId);
1092
+ if (!role) {
1093
+ throw new Error("Role not found");
1094
+ }
1095
+ if (role.isSystem && updates.permissions) {
1096
+ throw new Error("Cannot modify permissions of system roles");
1097
+ }
1098
+ const updatedRole = {
1099
+ ...role,
1100
+ ...updates,
1101
+ id: roleId,
1102
+ updatedAt: /* @__PURE__ */ new Date()
1103
+ };
1104
+ this.roles.set(roleId, updatedRole);
1105
+ this.clearRolePermissionCache(roleId);
1106
+ this.emit("role:updated", { role: updatedRole, previousRole: role });
1107
+ return updatedRole;
1108
+ }
1109
+ async deleteRole(roleId) {
1110
+ const role = this.roles.get(roleId);
1111
+ if (!role) {
1112
+ return false;
1113
+ }
1114
+ if (role.isSystem) {
1115
+ throw new Error("Cannot delete system roles");
1116
+ }
1117
+ const usersWithRole = Array.from(this.users.values()).filter((user) => user.roles.some((r) => r.id === roleId));
1118
+ if (usersWithRole.length > 0) {
1119
+ throw new Error("Cannot delete role that is assigned to users");
1120
+ }
1121
+ this.roles.delete(roleId);
1122
+ this.emit("role:deleted", { role });
1123
+ return true;
1124
+ }
1125
+ // Permission Management
1126
+ async assignRoleToUser(userId, roleId) {
1127
+ const user = this.users.get(userId);
1128
+ const role = this.roles.get(roleId);
1129
+ if (!user) {
1130
+ throw new Error("User not found");
1131
+ }
1132
+ if (!role) {
1133
+ throw new Error("Role not found");
1134
+ }
1135
+ if (user.roles.some((r) => r.id === roleId)) {
1136
+ return;
1137
+ }
1138
+ user.roles.push(role);
1139
+ user.updatedAt = /* @__PURE__ */ new Date();
1140
+ this.updateUserRoleCache(userId, user.roles.map((r) => r.id));
1141
+ this.clearUserPermissionCache(userId);
1142
+ this.emit("role:assigned", { userId, roleId });
1143
+ }
1144
+ async removeRoleFromUser(userId, roleId) {
1145
+ const user = this.users.get(userId);
1146
+ if (!user) {
1147
+ throw new Error("User not found");
1148
+ }
1149
+ const roleIndex = user.roles.findIndex((r) => r.id === roleId);
1150
+ if (roleIndex === -1) {
1151
+ return;
1152
+ }
1153
+ user.roles.splice(roleIndex, 1);
1154
+ user.updatedAt = /* @__PURE__ */ new Date();
1155
+ this.updateUserRoleCache(userId, user.roles.map((r) => r.id));
1156
+ this.clearUserPermissionCache(userId);
1157
+ this.emit("role:removed", { userId, roleId });
1158
+ }
1159
+ // Permission Checking
1160
+ async checkPermission(check) {
1161
+ const cacheKey = this.getCacheKey(check);
1162
+ const cached = this.getFromCache(cacheKey);
1163
+ if (cached) {
1164
+ return cached;
1165
+ }
1166
+ const result = await this.performPermissionCheck(check);
1167
+ this.setCache(cacheKey, result);
1168
+ return result;
1169
+ }
1170
+ async hasPermission(userId, resource, action, resourceId, context) {
1171
+ const result = await this.checkPermission({
1172
+ userId,
1173
+ resource,
1174
+ action,
1175
+ resourceId,
1176
+ context
1177
+ });
1178
+ return result.granted;
1179
+ }
1180
+ async requirePermission(userId, resource, action, resourceId, context) {
1181
+ const hasAccess = await this.hasPermission(userId, resource, action, resourceId, context);
1182
+ if (!hasAccess) {
1183
+ throw new Error(`Access denied: ${action} on ${resource}`);
1184
+ }
1185
+ }
1186
+ // Utility Methods
1187
+ async getUserPermissions(userId) {
1188
+ const user = this.users.get(userId);
1189
+ if (!user) {
1190
+ return [];
1191
+ }
1192
+ const permissions = [];
1193
+ for (const role of user.roles) {
1194
+ permissions.push(...role.permissions);
1195
+ }
1196
+ const uniquePermissions = permissions.filter(
1197
+ (permission, index, self) => index === self.findIndex((p) => p.id === permission.id)
1198
+ );
1199
+ return uniquePermissions;
1200
+ }
1201
+ async getUserRoles(userId) {
1202
+ const user = this.users.get(userId);
1203
+ return user ? user.roles : [];
1204
+ }
1205
+ listUsers(filters) {
1206
+ let users = Array.from(this.users.values());
1207
+ if (filters?.isActive !== void 0) {
1208
+ users = users.filter((u) => u.isActive === filters.isActive);
1209
+ }
1210
+ if (filters?.roleId) {
1211
+ users = users.filter((u) => u.roles.some((r) => r.id === filters.roleId));
1212
+ }
1213
+ return users;
1214
+ }
1215
+ listRoles() {
1216
+ return Array.from(this.roles.values());
1217
+ }
1218
+ // Private Methods
1219
+ async performPermissionCheck(check) {
1220
+ const user = this.users.get(check.userId);
1221
+ if (!user) {
1222
+ return {
1223
+ granted: false,
1224
+ reason: "User not found",
1225
+ matchedPermissions: [],
1226
+ deniedReasons: ["User not found"]
1227
+ };
1228
+ }
1229
+ if (!user.isActive) {
1230
+ return {
1231
+ granted: false,
1232
+ reason: "User is inactive",
1233
+ matchedPermissions: [],
1234
+ deniedReasons: ["User is inactive"]
1235
+ };
1236
+ }
1237
+ const matchedPermissions = [];
1238
+ const deniedReasons = [];
1239
+ for (const role of user.roles) {
1240
+ for (const permission of role.permissions) {
1241
+ if (this.doesPermissionMatch(permission, check)) {
1242
+ if (await this.checkConditions(permission, check)) {
1243
+ matchedPermissions.push(permission);
1244
+ } else {
1245
+ deniedReasons.push(`Conditions not met for permission ${permission.id}`);
1246
+ }
1247
+ }
1248
+ }
1249
+ }
1250
+ const granted = matchedPermissions.length > 0;
1251
+ return {
1252
+ granted,
1253
+ reason: granted ? void 0 : "No matching permissions found",
1254
+ matchedPermissions,
1255
+ deniedReasons: granted ? [] : deniedReasons
1256
+ };
1257
+ }
1258
+ doesPermissionMatch(permission, check) {
1259
+ return permission.resource === check.resource && permission.action === check.action;
1260
+ }
1261
+ async checkConditions(permission, check) {
1262
+ if (!permission.conditions || permission.conditions.length === 0) {
1263
+ return true;
1264
+ }
1265
+ for (const condition of permission.conditions) {
1266
+ if (!await this.evaluateCondition(condition, check)) {
1267
+ return false;
1268
+ }
1269
+ }
1270
+ return true;
1271
+ }
1272
+ async evaluateCondition(condition, check) {
1273
+ let actualValue;
1274
+ switch (condition.field) {
1275
+ case "userId":
1276
+ actualValue = check.userId;
1277
+ break;
1278
+ case "organizationId":
1279
+ actualValue = check.context?.organizationId;
1280
+ break;
1281
+ case "teamId":
1282
+ actualValue = check.context?.teamId;
1283
+ break;
1284
+ case "resourceOwnerId":
1285
+ actualValue = check.context?.resourceOwnerId;
1286
+ break;
1287
+ default:
1288
+ actualValue = check.context?.metadata?.[condition.field];
1289
+ }
1290
+ switch (condition.operator) {
1291
+ case "equals":
1292
+ return actualValue === condition.value;
1293
+ case "not_equals":
1294
+ return actualValue !== condition.value;
1295
+ case "in":
1296
+ return Array.isArray(condition.value) && condition.value.includes(actualValue);
1297
+ case "not_in":
1298
+ return Array.isArray(condition.value) && !condition.value.includes(actualValue);
1299
+ case "contains":
1300
+ return String(actualValue).includes(String(condition.value));
1301
+ case "starts_with":
1302
+ return String(actualValue).startsWith(String(condition.value));
1303
+ default:
1304
+ return false;
1305
+ }
1306
+ }
1307
+ initializeSystemRoles() {
1308
+ const superAdminRole = {
1309
+ id: "super-admin",
1310
+ name: "Super Admin",
1311
+ description: "Full system access",
1312
+ isSystem: true,
1313
+ permissions: [
1314
+ // Global permissions for all resources and actions
1315
+ ...Object.values(ResourceType).flatMap(
1316
+ (resource) => Object.values(ActionType).map((action) => ({
1317
+ id: `super-admin-${resource}-${action}`,
1318
+ resource,
1319
+ action,
1320
+ scope: "global" /* GLOBAL */
1321
+ }))
1322
+ )
1323
+ ],
1324
+ createdAt: /* @__PURE__ */ new Date(),
1325
+ updatedAt: /* @__PURE__ */ new Date()
1326
+ };
1327
+ const channelAdminRole = {
1328
+ id: "channel-admin",
1329
+ name: "Channel Admin",
1330
+ description: "Manage channels and sender numbers",
1331
+ isSystem: true,
1332
+ permissions: [
1333
+ {
1334
+ id: "channel-admin-channel-manage",
1335
+ resource: "channel" /* CHANNEL */,
1336
+ action: "manage" /* MANAGE */,
1337
+ scope: "organization" /* ORGANIZATION */
1338
+ },
1339
+ {
1340
+ id: "channel-admin-sender-manage",
1341
+ resource: "senderNumber" /* SENDER_NUMBER */,
1342
+ action: "manage" /* MANAGE */,
1343
+ scope: "organization" /* ORGANIZATION */
1344
+ }
1345
+ ],
1346
+ createdAt: /* @__PURE__ */ new Date(),
1347
+ updatedAt: /* @__PURE__ */ new Date()
1348
+ };
1349
+ const messageSenderRole = {
1350
+ id: "message-sender",
1351
+ name: "Message Sender",
1352
+ description: "Send messages using configured channels",
1353
+ isSystem: true,
1354
+ permissions: [
1355
+ {
1356
+ id: "message-sender-channel-read",
1357
+ resource: "channel" /* CHANNEL */,
1358
+ action: "read" /* READ */,
1359
+ scope: "organization" /* ORGANIZATION */
1360
+ },
1361
+ {
1362
+ id: "message-sender-message-send",
1363
+ resource: "message" /* MESSAGE */,
1364
+ action: "send" /* SEND */,
1365
+ scope: "organization" /* ORGANIZATION */
1366
+ }
1367
+ ],
1368
+ createdAt: /* @__PURE__ */ new Date(),
1369
+ updatedAt: /* @__PURE__ */ new Date()
1370
+ };
1371
+ const viewerRole = {
1372
+ id: "viewer",
1373
+ name: "Viewer",
1374
+ description: "Read-only access",
1375
+ isSystem: true,
1376
+ permissions: [
1377
+ {
1378
+ id: "viewer-channel-read",
1379
+ resource: "channel" /* CHANNEL */,
1380
+ action: "read" /* READ */,
1381
+ scope: "organization" /* ORGANIZATION */
1382
+ },
1383
+ {
1384
+ id: "viewer-sender-read",
1385
+ resource: "senderNumber" /* SENDER_NUMBER */,
1386
+ action: "read" /* READ */,
1387
+ scope: "organization" /* ORGANIZATION */
1388
+ },
1389
+ {
1390
+ id: "viewer-analytics-read",
1391
+ resource: "analytics" /* ANALYTICS */,
1392
+ action: "read" /* READ */,
1393
+ scope: "organization" /* ORGANIZATION */
1394
+ }
1395
+ ],
1396
+ createdAt: /* @__PURE__ */ new Date(),
1397
+ updatedAt: /* @__PURE__ */ new Date()
1398
+ };
1399
+ this.roles.set(superAdminRole.id, superAdminRole);
1400
+ this.roles.set(channelAdminRole.id, channelAdminRole);
1401
+ this.roles.set(messageSenderRole.id, messageSenderRole);
1402
+ this.roles.set(viewerRole.id, viewerRole);
1403
+ }
1404
+ updateUserRoleCache(userId, roleIds) {
1405
+ this.userRoleCache.set(userId, new Set(roleIds));
1406
+ }
1407
+ clearUserPermissionCache(userId) {
1408
+ const keysToDelete = [];
1409
+ for (const key of this.permissionCache.keys()) {
1410
+ if (key.startsWith(`${userId}:`)) {
1411
+ keysToDelete.push(key);
1412
+ }
1413
+ }
1414
+ keysToDelete.forEach((key) => {
1415
+ this.permissionCache.delete(key);
1416
+ this.cacheExpiry.delete(key);
1417
+ });
1418
+ }
1419
+ clearRolePermissionCache(roleId) {
1420
+ for (const [userId, roleIds] of this.userRoleCache) {
1421
+ if (roleIds.has(roleId)) {
1422
+ this.clearUserPermissionCache(userId);
1423
+ }
1424
+ }
1425
+ }
1426
+ getCacheKey(check) {
1427
+ const contextKey = check.context ? JSON.stringify(check.context) : "";
1428
+ return `${check.userId}:${check.resource}:${check.action}:${check.resourceId || ""}:${contextKey}`;
1429
+ }
1430
+ getFromCache(key) {
1431
+ const expiry = this.cacheExpiry.get(key);
1432
+ if (!expiry || expiry < Date.now()) {
1433
+ this.permissionCache.delete(key);
1434
+ this.cacheExpiry.delete(key);
1435
+ return null;
1436
+ }
1437
+ return this.permissionCache.get(key) || null;
1438
+ }
1439
+ setCache(key, result) {
1440
+ this.permissionCache.set(key, result);
1441
+ this.cacheExpiry.set(key, Date.now() + this.CACHE_DURATION);
1442
+ }
1443
+ generateUserId() {
1444
+ return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1445
+ }
1446
+ generateRoleId() {
1447
+ return `role_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1448
+ }
1449
+ };
1450
+
1451
+ // src/verification/business.verify.ts
1452
+ import { EventEmitter as EventEmitter3 } from "events";
1453
+ var BusinessVerifier = class extends EventEmitter3 {
1454
+ constructor(options = {}) {
1455
+ super();
1456
+ this.options = options;
1457
+ this.verificationRequests = /* @__PURE__ */ new Map();
1458
+ this.documentValidators = /* @__PURE__ */ new Map();
1459
+ this.defaultOptions = {
1460
+ enableAutoVerification: true,
1461
+ requiredDocuments: ["BUSINESS_REGISTRATION" /* BUSINESS_REGISTRATION */],
1462
+ autoApprovalThreshold: 80,
1463
+ requireManualReview: false,
1464
+ documentRetentionDays: 365,
1465
+ enableExternalAPIs: false
1466
+ };
1467
+ this.options = { ...this.defaultOptions, ...options };
1468
+ this.initializeDocumentValidators();
1469
+ }
1470
+ /**
1471
+ * Submit business verification request
1472
+ */
1473
+ async submitVerification(channelId, businessInfo, documents) {
1474
+ const requestId = this.generateRequestId();
1475
+ this.validateRequiredDocuments(documents);
1476
+ const verificationRequest = {
1477
+ id: requestId,
1478
+ channelId,
1479
+ businessInfo,
1480
+ documents: documents.map((doc) => ({
1481
+ ...doc,
1482
+ status: "UPLOADED" /* UPLOADED */
1483
+ })),
1484
+ status: "PENDING" /* PENDING */,
1485
+ submittedAt: /* @__PURE__ */ new Date()
1486
+ };
1487
+ this.verificationRequests.set(requestId, verificationRequest);
1488
+ this.emit("verification:submitted", { verificationRequest });
1489
+ if (this.options.enableAutoVerification) {
1490
+ await this.processAutoVerification(requestId);
1491
+ }
1492
+ return verificationRequest;
1493
+ }
1494
+ /**
1495
+ * Get verification request by ID
1496
+ */
1497
+ getVerificationRequest(requestId) {
1498
+ return this.verificationRequests.get(requestId) || null;
1499
+ }
1500
+ /**
1501
+ * Get verification request by channel ID
1502
+ */
1503
+ getVerificationByChannelId(channelId) {
1504
+ for (const request of this.verificationRequests.values()) {
1505
+ if (request.channelId === channelId) {
1506
+ return request;
1507
+ }
1508
+ }
1509
+ return null;
1510
+ }
1511
+ /**
1512
+ * Manually approve verification
1513
+ */
1514
+ async approveVerification(requestId, reviewerId, notes) {
1515
+ const request = this.verificationRequests.get(requestId);
1516
+ if (!request) {
1517
+ throw new Error("Verification request not found");
1518
+ }
1519
+ request.status = "VERIFIED" /* VERIFIED */;
1520
+ request.reviewedAt = /* @__PURE__ */ new Date();
1521
+ request.reviewedBy = reviewerId;
1522
+ request.reviewNotes = notes;
1523
+ request.documents.forEach((doc) => {
1524
+ doc.status = "VERIFIED" /* VERIFIED */;
1525
+ });
1526
+ this.emit("verification:approved", { verificationRequest: request, reviewerId });
1527
+ return request;
1528
+ }
1529
+ /**
1530
+ * Manually reject verification
1531
+ */
1532
+ async rejectVerification(requestId, reviewerId, reason) {
1533
+ const request = this.verificationRequests.get(requestId);
1534
+ if (!request) {
1535
+ throw new Error("Verification request not found");
1536
+ }
1537
+ request.status = "REJECTED" /* REJECTED */;
1538
+ request.reviewedAt = /* @__PURE__ */ new Date();
1539
+ request.reviewedBy = reviewerId;
1540
+ request.reviewNotes = reason;
1541
+ request.documents.forEach((doc) => {
1542
+ if (doc.status === "UPLOADED" /* UPLOADED */) {
1543
+ doc.status = "REJECTED" /* REJECTED */;
1544
+ }
1545
+ });
1546
+ this.emit("verification:rejected", { verificationRequest: request, reviewerId, reason });
1547
+ return request;
1548
+ }
1549
+ /**
1550
+ * Update verification request with additional documents
1551
+ */
1552
+ async addDocument(requestId, document) {
1553
+ const request = this.verificationRequests.get(requestId);
1554
+ if (!request) {
1555
+ throw new Error("Verification request not found");
1556
+ }
1557
+ if (request.status !== "PENDING" /* PENDING */ && request.status !== "UNDER_REVIEW" /* UNDER_REVIEW */) {
1558
+ throw new Error("Cannot add documents to completed verification");
1559
+ }
1560
+ document.status = "UPLOADED" /* UPLOADED */;
1561
+ request.documents.push(document);
1562
+ if (this.options.enableAutoVerification) {
1563
+ await this.processAutoVerification(requestId);
1564
+ }
1565
+ this.emit("verification:document_added", { verificationRequest: request, document });
1566
+ return request;
1567
+ }
1568
+ /**
1569
+ * List verification requests with filters
1570
+ */
1571
+ listVerificationRequests(filters) {
1572
+ let requests = Array.from(this.verificationRequests.values());
1573
+ if (filters?.status) {
1574
+ requests = requests.filter((r) => r.status === filters.status);
1575
+ }
1576
+ if (filters?.channelId) {
1577
+ requests = requests.filter((r) => r.channelId === filters.channelId);
1578
+ }
1579
+ if (filters?.submittedAfter) {
1580
+ requests = requests.filter((r) => r.submittedAt >= filters.submittedAfter);
1581
+ }
1582
+ if (filters?.submittedBefore) {
1583
+ requests = requests.filter((r) => r.submittedAt <= filters.submittedBefore);
1584
+ }
1585
+ return requests.sort((a, b) => b.submittedAt.getTime() - a.submittedAt.getTime());
1586
+ }
1587
+ /**
1588
+ * Get verification statistics
1589
+ */
1590
+ getVerificationStats() {
1591
+ const requests = Array.from(this.verificationRequests.values());
1592
+ const byStatus = {};
1593
+ let totalProcessingTime = 0;
1594
+ let processedCount = 0;
1595
+ let autoApprovedCount = 0;
1596
+ requests.forEach((request) => {
1597
+ byStatus[request.status] = (byStatus[request.status] || 0) + 1;
1598
+ if (request.reviewedAt) {
1599
+ const processingTime = request.reviewedAt.getTime() - request.submittedAt.getTime();
1600
+ totalProcessingTime += processingTime;
1601
+ processedCount++;
1602
+ if (!request.reviewedBy && request.status === "VERIFIED" /* VERIFIED */) {
1603
+ autoApprovedCount++;
1604
+ }
1605
+ }
1606
+ });
1607
+ return {
1608
+ total: requests.length,
1609
+ byStatus,
1610
+ averageProcessingTime: processedCount > 0 ? totalProcessingTime / processedCount : 0,
1611
+ autoApprovalRate: processedCount > 0 ? autoApprovedCount / processedCount * 100 : 0
1612
+ };
1613
+ }
1614
+ // Private Methods
1615
+ async processAutoVerification(requestId) {
1616
+ const request = this.verificationRequests.get(requestId);
1617
+ if (!request) return;
1618
+ request.status = "UNDER_REVIEW" /* UNDER_REVIEW */;
1619
+ this.emit("verification:auto_processing_started", { verificationRequest: request });
1620
+ const autoResults = [];
1621
+ try {
1622
+ const businessCheck = await this.verifyBusinessRegistration(request.businessInfo);
1623
+ autoResults.push(businessCheck);
1624
+ for (const document of request.documents) {
1625
+ const docValidation = await this.validateDocument(document);
1626
+ autoResults.push({
1627
+ checkType: "document_validation",
1628
+ status: docValidation.isValid ? "passed" : "failed",
1629
+ score: docValidation.confidence,
1630
+ details: `Document validation: ${docValidation.issues.length} issues found`,
1631
+ metadata: { documentId: document.id, issues: docValidation.issues }
1632
+ });
1633
+ }
1634
+ const addressCheck = await this.verifyAddress(request.businessInfo.address);
1635
+ autoResults.push(addressCheck);
1636
+ const phoneCheck = await this.verifyPhoneNumber(request.businessInfo.contactInfo.phoneNumber);
1637
+ autoResults.push(phoneCheck);
1638
+ request.autoVerificationResults = autoResults;
1639
+ const overallScore = autoResults.reduce((sum, result) => sum + result.score, 0) / autoResults.length;
1640
+ if (overallScore >= this.options.autoApprovalThreshold && !this.options.requireManualReview) {
1641
+ request.status = "VERIFIED" /* VERIFIED */;
1642
+ request.reviewedAt = /* @__PURE__ */ new Date();
1643
+ request.reviewNotes = `Auto-approved with score: ${overallScore.toFixed(1)}`;
1644
+ request.documents.forEach((doc) => {
1645
+ doc.status = "VERIFIED" /* VERIFIED */;
1646
+ });
1647
+ this.emit("verification:auto_approved", { verificationRequest: request, score: overallScore });
1648
+ } else {
1649
+ this.emit("verification:manual_review_required", { verificationRequest: request, score: overallScore });
1650
+ }
1651
+ } catch (error) {
1652
+ request.status = "PENDING" /* PENDING */;
1653
+ this.emit("verification:auto_processing_failed", {
1654
+ verificationRequest: request,
1655
+ error: error instanceof Error ? error.message : "Unknown error"
1656
+ });
1657
+ }
1658
+ }
1659
+ validateRequiredDocuments(documents) {
1660
+ const providedTypes = new Set(documents.map((doc) => doc.type));
1661
+ const missingTypes = this.options.requiredDocuments.filter((type) => !providedTypes.has(type));
1662
+ if (missingTypes.length > 0) {
1663
+ throw new Error(`Missing required documents: ${missingTypes.join(", ")}`);
1664
+ }
1665
+ }
1666
+ async verifyBusinessRegistration(businessInfo) {
1667
+ const score = this.calculateBusinessRegistrationScore(businessInfo);
1668
+ return {
1669
+ checkType: "business_registry",
1670
+ status: score >= 70 ? "passed" : score >= 50 ? "warning" : "failed",
1671
+ score,
1672
+ details: `Business registration verification completed`,
1673
+ metadata: {
1674
+ businessName: businessInfo.businessName,
1675
+ registrationNumber: businessInfo.businessRegistrationNumber
1676
+ }
1677
+ };
1678
+ }
1679
+ calculateBusinessRegistrationScore(businessInfo) {
1680
+ let score = 0;
1681
+ if (/^\d{3}-\d{2}-\d{5}$/.test(businessInfo.businessRegistrationNumber)) {
1682
+ score += 30;
1683
+ }
1684
+ if (businessInfo.businessName.length >= 2 && businessInfo.businessName.length <= 100) {
1685
+ score += 20;
1686
+ }
1687
+ const now = /* @__PURE__ */ new Date();
1688
+ const establishedDate = new Date(businessInfo.establishedDate);
1689
+ const yearsOld = (now.getTime() - establishedDate.getTime()) / (1e3 * 60 * 60 * 24 * 365);
1690
+ if (yearsOld >= 0 && yearsOld <= 100) {
1691
+ score += 20;
1692
+ }
1693
+ if (businessInfo.contactInfo.email && businessInfo.contactInfo.phoneNumber) {
1694
+ score += 15;
1695
+ }
1696
+ if (businessInfo.address.street && businessInfo.address.city && businessInfo.address.postalCode) {
1697
+ score += 15;
1698
+ }
1699
+ return Math.min(100, score);
1700
+ }
1701
+ async validateDocument(document) {
1702
+ const validator = this.documentValidators.get(document.type);
1703
+ if (!validator) {
1704
+ return {
1705
+ isValid: false,
1706
+ confidence: 0,
1707
+ issues: [{
1708
+ type: "format",
1709
+ severity: "critical",
1710
+ message: `No validator available for document type: ${document.type}`
1711
+ }]
1712
+ };
1713
+ }
1714
+ return await validator(document);
1715
+ }
1716
+ async verifyAddress(address) {
1717
+ let score = 0;
1718
+ if (address.street && address.city && address.postalCode) {
1719
+ score += 40;
1720
+ }
1721
+ if (/^\d{5}$/.test(address.postalCode)) {
1722
+ score += 30;
1723
+ }
1724
+ if (address.country === "KR" || address.country === "Korea") {
1725
+ score += 30;
1726
+ }
1727
+ return {
1728
+ checkType: "address_verification",
1729
+ status: score >= 70 ? "passed" : score >= 50 ? "warning" : "failed",
1730
+ score,
1731
+ details: "Address verification completed",
1732
+ metadata: { address }
1733
+ };
1734
+ }
1735
+ async verifyPhoneNumber(phoneNumber) {
1736
+ const isValidFormat = /^(010|011|016|017|018|019)[0-9]{7,8}$/.test(phoneNumber);
1737
+ const score = isValidFormat ? 100 : 0;
1738
+ return {
1739
+ checkType: "phone_verification",
1740
+ status: isValidFormat ? "passed" : "failed",
1741
+ score,
1742
+ details: isValidFormat ? "Phone number format is valid" : "Invalid phone number format",
1743
+ metadata: { phoneNumber }
1744
+ };
1745
+ }
1746
+ initializeDocumentValidators() {
1747
+ this.documentValidators.set("BUSINESS_REGISTRATION" /* BUSINESS_REGISTRATION */, async (doc) => {
1748
+ const issues = [];
1749
+ let confidence = 80;
1750
+ if (!doc.fileName.match(/\.(pdf|jpg|jpeg|png)$/i)) {
1751
+ issues.push({
1752
+ type: "format",
1753
+ severity: "medium",
1754
+ message: "Unsupported file format"
1755
+ });
1756
+ confidence -= 20;
1757
+ }
1758
+ return {
1759
+ isValid: issues.length === 0 || issues.every((i) => i.severity !== "critical"),
1760
+ confidence: Math.max(0, confidence),
1761
+ issues
1762
+ };
1763
+ });
1764
+ this.documentValidators.set("BUSINESS_LICENSE" /* BUSINESS_LICENSE */, async (doc) => {
1765
+ const issues = [];
1766
+ let confidence = 75;
1767
+ if (!doc.fileName.match(/\.(pdf|jpg|jpeg|png)$/i)) {
1768
+ issues.push({
1769
+ type: "format",
1770
+ severity: "medium",
1771
+ message: "Unsupported file format"
1772
+ });
1773
+ confidence -= 15;
1774
+ }
1775
+ return {
1776
+ isValid: issues.length === 0 || issues.every((i) => i.severity !== "critical"),
1777
+ confidence: Math.max(0, confidence),
1778
+ issues
1779
+ };
1780
+ });
1781
+ this.documentValidators.set("ID_CARD" /* ID_CARD */, async (doc) => {
1782
+ const issues = [];
1783
+ let confidence = 70;
1784
+ if (!doc.fileName.match(/\.(jpg|jpeg|png)$/i)) {
1785
+ issues.push({
1786
+ type: "format",
1787
+ severity: "high",
1788
+ message: "ID card should be an image file"
1789
+ });
1790
+ confidence -= 30;
1791
+ }
1792
+ return {
1793
+ isValid: issues.length === 0 || issues.every((i) => i.severity !== "critical"),
1794
+ confidence: Math.max(0, confidence),
1795
+ issues
1796
+ };
1797
+ });
1798
+ const defaultValidator = async (doc) => {
1799
+ const issues = [];
1800
+ let confidence = 60;
1801
+ if (!doc.fileName || doc.fileName.length === 0) {
1802
+ issues.push({
1803
+ type: "format",
1804
+ severity: "critical",
1805
+ message: "File name is required"
1806
+ });
1807
+ confidence = 0;
1808
+ }
1809
+ return {
1810
+ isValid: issues.length === 0 || issues.every((i) => i.severity !== "critical"),
1811
+ confidence: Math.max(0, confidence),
1812
+ issues
1813
+ };
1814
+ };
1815
+ this.documentValidators.set("AUTHORIZATION_LETTER" /* AUTHORIZATION_LETTER */, defaultValidator);
1816
+ this.documentValidators.set("OTHER" /* OTHER */, defaultValidator);
1817
+ }
1818
+ generateRequestId() {
1819
+ return `biz_verify_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1820
+ }
1821
+ };
1822
+
1823
+ // src/verification/number.verify.ts
1824
+ import { EventEmitter as EventEmitter4 } from "events";
1825
+ var VerificationType = /* @__PURE__ */ ((VerificationType2) => {
1826
+ VerificationType2["SMS"] = "sms";
1827
+ VerificationType2["VOICE_CALL"] = "voice_call";
1828
+ VerificationType2["HYBRID"] = "hybrid";
1829
+ return VerificationType2;
1830
+ })(VerificationType || {});
1831
+ var VerificationMethod = /* @__PURE__ */ ((VerificationMethod2) => {
1832
+ VerificationMethod2["SMS"] = "sms";
1833
+ VerificationMethod2["VOICE_CALL"] = "voice_call";
1834
+ VerificationMethod2["MISSED_CALL"] = "missed_call";
1835
+ return VerificationMethod2;
1836
+ })(VerificationMethod || {});
1837
+ var NumberVerifier = class extends EventEmitter4 {
1838
+ constructor(options = {}) {
1839
+ super();
1840
+ this.options = options;
1841
+ this.verificationRequests = /* @__PURE__ */ new Map();
1842
+ this.phoneNumberCache = /* @__PURE__ */ new Map();
1843
+ this.rateLimitTracker = /* @__PURE__ */ new Map();
1844
+ this.dailyAttemptTracker = /* @__PURE__ */ new Map();
1845
+ this.blockedNumbers = /* @__PURE__ */ new Set();
1846
+ this.defaultOptions = {
1847
+ codeLength: 6,
1848
+ codeExpiryMinutes: 5,
1849
+ maxAttempts: 3,
1850
+ maxDailyAttempts: 10,
1851
+ smsTemplate: "\uC778\uC99D\uBC88\uD638: {code}. {expiry}\uBD84 \uB0B4\uC5D0 \uC785\uB825\uD574\uC8FC\uC138\uC694.",
1852
+ voiceTemplate: "\uC778\uC99D\uBC88\uD638\uB294 {code}\uC785\uB2C8\uB2E4. \uB2E4\uC2DC \uD55C \uBC88, {code}\uC785\uB2C8\uB2E4.",
1853
+ rateLimitMinutes: 1,
1854
+ enableVoiceFallback: true,
1855
+ enableMissedCallVerification: false,
1856
+ blockedNumbers: [],
1857
+ allowedCountries: ["KR"]
1858
+ };
1859
+ this.options = { ...this.defaultOptions, ...options };
1860
+ this.options.blockedNumbers?.forEach((number) => {
1861
+ this.blockedNumbers.add(number);
1862
+ });
1863
+ }
1864
+ /**
1865
+ * Start phone number verification process
1866
+ */
1867
+ async startVerification(senderNumberId, phoneNumber, verificationType = "sms" /* SMS */, metadata = {}) {
1868
+ const phoneInfo = await this.getPhoneNumberInfo(phoneNumber);
1869
+ if (!phoneInfo.isValid) {
1870
+ throw new Error("Invalid phone number format");
1871
+ }
1872
+ if (this.isNumberBlocked(phoneNumber)) {
1873
+ throw new Error("Phone number is blocked");
1874
+ }
1875
+ if (this.isRateLimited(phoneNumber)) {
1876
+ throw new Error("Rate limit exceeded. Please try again later.");
1877
+ }
1878
+ if (this.isDailyLimitExceeded(phoneNumber)) {
1879
+ throw new Error("Daily verification attempt limit exceeded");
1880
+ }
1881
+ const requestId = this.generateRequestId();
1882
+ const verificationCode = this.generateVerificationCode();
1883
+ const expiresAt = new Date(Date.now() + this.options.codeExpiryMinutes * 60 * 1e3);
1884
+ const verificationRequest = {
1885
+ id: requestId,
1886
+ senderNumberId,
1887
+ phoneNumber,
1888
+ verificationType,
1889
+ verificationCode,
1890
+ status: "pending" /* PENDING */,
1891
+ attempts: [],
1892
+ expiresAt,
1893
+ createdAt: /* @__PURE__ */ new Date(),
1894
+ metadata
1895
+ };
1896
+ this.verificationRequests.set(requestId, verificationRequest);
1897
+ this.updateRateLimit(phoneNumber);
1898
+ this.updateDailyAttempts(phoneNumber);
1899
+ this.emit("verification:started", { verificationRequest, phoneInfo });
1900
+ await this.sendVerificationCode(verificationRequest, phoneInfo);
1901
+ return verificationRequest;
1902
+ }
1903
+ /**
1904
+ * Verify the provided code
1905
+ */
1906
+ async verifyCode(requestId, providedCode) {
1907
+ const request = this.verificationRequests.get(requestId);
1908
+ if (!request) {
1909
+ return {
1910
+ success: false,
1911
+ status: "failed" /* FAILED */,
1912
+ message: "Verification request not found"
1913
+ };
1914
+ }
1915
+ if (request.status === "verified" /* VERIFIED */) {
1916
+ return {
1917
+ success: true,
1918
+ status: "verified" /* VERIFIED */,
1919
+ message: "Already verified"
1920
+ };
1921
+ }
1922
+ if (/* @__PURE__ */ new Date() > request.expiresAt) {
1923
+ request.status = "expired" /* EXPIRED */;
1924
+ this.emit("verification:expired", { verificationRequest: request });
1925
+ return {
1926
+ success: false,
1927
+ status: "expired" /* EXPIRED */,
1928
+ message: "Verification code has expired"
1929
+ };
1930
+ }
1931
+ if (request.status === "blocked" /* BLOCKED */) {
1932
+ return {
1933
+ success: false,
1934
+ status: "blocked" /* BLOCKED */,
1935
+ message: "Verification blocked due to too many failed attempts"
1936
+ };
1937
+ }
1938
+ const isCodeValid = this.validateCode(request.verificationCode, providedCode);
1939
+ if (isCodeValid) {
1940
+ request.status = "verified" /* VERIFIED */;
1941
+ request.completedAt = /* @__PURE__ */ new Date();
1942
+ this.emit("verification:success", { verificationRequest: request });
1943
+ return {
1944
+ success: true,
1945
+ status: "verified" /* VERIFIED */,
1946
+ message: "Phone number verified successfully"
1947
+ };
1948
+ } else {
1949
+ const failedAttempt = {
1950
+ attemptNumber: request.attempts.length + 1,
1951
+ attemptedAt: /* @__PURE__ */ new Date(),
1952
+ method: "sms" /* SMS */,
1953
+ // Assuming SMS for verification attempts
1954
+ status: "failed"
1955
+ };
1956
+ request.attempts.push(failedAttempt);
1957
+ const failedAttempts = request.attempts.filter((a) => a.status === "failed").length;
1958
+ if (failedAttempts >= this.options.maxAttempts) {
1959
+ request.status = "blocked" /* BLOCKED */;
1960
+ this.emit("verification:blocked", { verificationRequest: request });
1961
+ return {
1962
+ success: false,
1963
+ status: "blocked" /* BLOCKED */,
1964
+ message: "Too many failed attempts. Verification blocked."
1965
+ };
1966
+ } else {
1967
+ request.status = "failed" /* FAILED */;
1968
+ this.emit("verification:failed_attempt", {
1969
+ verificationRequest: request,
1970
+ attemptsRemaining: this.options.maxAttempts - failedAttempts
1971
+ });
1972
+ return {
1973
+ success: false,
1974
+ status: "failed" /* FAILED */,
1975
+ message: `Invalid code. ${this.options.maxAttempts - failedAttempts} attempts remaining.`
1976
+ };
1977
+ }
1978
+ }
1979
+ }
1980
+ /**
1981
+ * Resend verification code
1982
+ */
1983
+ async resendCode(requestId, method) {
1984
+ const request = this.verificationRequests.get(requestId);
1985
+ if (!request) {
1986
+ throw new Error("Verification request not found");
1987
+ }
1988
+ if (request.status === "verified" /* VERIFIED */) {
1989
+ throw new Error("Verification already completed");
1990
+ }
1991
+ if (request.status === "blocked" /* BLOCKED */) {
1992
+ throw new Error("Verification is blocked");
1993
+ }
1994
+ if (this.isRateLimited(request.phoneNumber)) {
1995
+ throw new Error("Rate limit exceeded. Please wait before requesting a new code.");
1996
+ }
1997
+ request.verificationCode = this.generateVerificationCode();
1998
+ request.expiresAt = new Date(Date.now() + this.options.codeExpiryMinutes * 60 * 1e3);
1999
+ request.status = "pending" /* PENDING */;
2000
+ this.updateRateLimit(request.phoneNumber);
2001
+ const phoneInfo = await this.getPhoneNumberInfo(request.phoneNumber);
2002
+ if (method) {
2003
+ await this.sendVerificationByMethod(request, phoneInfo, method);
2004
+ } else {
2005
+ await this.sendVerificationCode(request, phoneInfo);
2006
+ }
2007
+ this.emit("verification:resent", { verificationRequest: request });
2008
+ return request;
2009
+ }
2010
+ /**
2011
+ * Get verification request status
2012
+ */
2013
+ getVerificationStatus(requestId) {
2014
+ return this.verificationRequests.get(requestId) || null;
2015
+ }
2016
+ /**
2017
+ * Cancel verification request
2018
+ */
2019
+ async cancelVerification(requestId) {
2020
+ const request = this.verificationRequests.get(requestId);
2021
+ if (!request) {
2022
+ return false;
2023
+ }
2024
+ if (request.status === "verified" /* VERIFIED */) {
2025
+ return false;
2026
+ }
2027
+ this.verificationRequests.delete(requestId);
2028
+ this.emit("verification:cancelled", { verificationRequest: request });
2029
+ return true;
2030
+ }
2031
+ /**
2032
+ * Block a phone number from verification
2033
+ */
2034
+ blockPhoneNumber(phoneNumber, reason) {
2035
+ this.blockedNumbers.add(phoneNumber);
2036
+ for (const [requestId, request] of this.verificationRequests) {
2037
+ if (request.phoneNumber === phoneNumber && request.status !== "verified" /* VERIFIED */) {
2038
+ request.status = "blocked" /* BLOCKED */;
2039
+ }
2040
+ }
2041
+ this.emit("phone:blocked", { phoneNumber, reason });
2042
+ }
2043
+ /**
2044
+ * Unblock a phone number
2045
+ */
2046
+ unblockPhoneNumber(phoneNumber) {
2047
+ this.blockedNumbers.delete(phoneNumber);
2048
+ this.emit("phone:unblocked", { phoneNumber });
2049
+ }
2050
+ /**
2051
+ * Get verification statistics
2052
+ */
2053
+ getVerificationStats() {
2054
+ const requests = Array.from(this.verificationRequests.values());
2055
+ const byStatus = {};
2056
+ const byMethod = {};
2057
+ let totalCompletionTime = 0;
2058
+ let completedCount = 0;
2059
+ requests.forEach((request) => {
2060
+ byStatus[request.status] = (byStatus[request.status] || 0) + 1;
2061
+ if (request.attempts.length > 0) {
2062
+ const primaryMethod = request.attempts[0].method;
2063
+ byMethod[primaryMethod] = (byMethod[primaryMethod] || 0) + 1;
2064
+ }
2065
+ if (request.completedAt) {
2066
+ const completionTime = request.completedAt.getTime() - request.createdAt.getTime();
2067
+ totalCompletionTime += completionTime;
2068
+ completedCount++;
2069
+ }
2070
+ });
2071
+ const successCount = byStatus["verified" /* VERIFIED */] || 0;
2072
+ const successRate = requests.length > 0 ? successCount / requests.length * 100 : 0;
2073
+ return {
2074
+ total: requests.length,
2075
+ byStatus,
2076
+ byMethod,
2077
+ successRate,
2078
+ averageCompletionTime: completedCount > 0 ? totalCompletionTime / completedCount : 0
2079
+ };
2080
+ }
2081
+ /**
2082
+ * Clean up expired verification requests
2083
+ */
2084
+ cleanup() {
2085
+ const now = /* @__PURE__ */ new Date();
2086
+ let cleanedCount = 0;
2087
+ for (const [requestId, request] of this.verificationRequests) {
2088
+ if (now > request.expiresAt && request.status !== "verified" /* VERIFIED */) {
2089
+ request.status = "expired" /* EXPIRED */;
2090
+ this.verificationRequests.delete(requestId);
2091
+ cleanedCount++;
2092
+ }
2093
+ }
2094
+ const rateWindow = this.options.rateLimitMinutes * 60 * 1e3;
2095
+ for (const [phoneNumber, timestamps] of this.rateLimitTracker) {
2096
+ const validTimestamps = timestamps.filter((ts) => now.getTime() - ts.getTime() < rateWindow);
2097
+ if (validTimestamps.length === 0) {
2098
+ this.rateLimitTracker.delete(phoneNumber);
2099
+ } else {
2100
+ this.rateLimitTracker.set(phoneNumber, validTimestamps);
2101
+ }
2102
+ }
2103
+ return cleanedCount;
2104
+ }
2105
+ // Private Methods
2106
+ async sendVerificationCode(request, phoneInfo) {
2107
+ let method;
2108
+ switch (request.verificationType) {
2109
+ case "sms" /* SMS */:
2110
+ method = "sms" /* SMS */;
2111
+ break;
2112
+ case "voice_call" /* VOICE_CALL */:
2113
+ method = "voice_call" /* VOICE_CALL */;
2114
+ break;
2115
+ case "hybrid" /* HYBRID */:
2116
+ method = phoneInfo.lineType === "landline" ? "voice_call" /* VOICE_CALL */ : "sms" /* SMS */;
2117
+ break;
2118
+ default:
2119
+ method = "sms" /* SMS */;
2120
+ }
2121
+ await this.sendVerificationByMethod(request, phoneInfo, method);
2122
+ }
2123
+ async sendVerificationByMethod(request, phoneInfo, method) {
2124
+ const attempt = {
2125
+ attemptNumber: request.attempts.length + 1,
2126
+ attemptedAt: /* @__PURE__ */ new Date(),
2127
+ method,
2128
+ status: "sent"
2129
+ };
2130
+ const startTime = Date.now();
2131
+ try {
2132
+ switch (method) {
2133
+ case "sms" /* SMS */:
2134
+ await this.sendSMS(request, phoneInfo);
2135
+ break;
2136
+ case "voice_call" /* VOICE_CALL */:
2137
+ await this.sendVoiceCall(request, phoneInfo);
2138
+ break;
2139
+ case "missed_call" /* MISSED_CALL */:
2140
+ await this.sendMissedCall(request, phoneInfo);
2141
+ break;
2142
+ }
2143
+ attempt.status = "delivered";
2144
+ attempt.responseTime = Date.now() - startTime;
2145
+ request.status = "code_sent" /* CODE_SENT */;
2146
+ } catch (error) {
2147
+ attempt.status = "failed";
2148
+ attempt.failureReason = error instanceof Error ? error.message : "Unknown error";
2149
+ attempt.responseTime = Date.now() - startTime;
2150
+ if (method === "sms" /* SMS */ && this.options.enableVoiceFallback && request.attempts.filter((a) => a.method === "voice_call" /* VOICE_CALL */).length === 0) {
2151
+ attempt.status = "failed";
2152
+ request.attempts.push(attempt);
2153
+ await this.sendVerificationByMethod(request, phoneInfo, "voice_call" /* VOICE_CALL */);
2154
+ return;
2155
+ }
2156
+ request.status = "failed" /* FAILED */;
2157
+ throw error;
2158
+ }
2159
+ request.attempts.push(attempt);
2160
+ }
2161
+ async sendSMS(request, phoneInfo) {
2162
+ if (!this.options.smsProvider) {
2163
+ throw new Error("SMS provider not configured");
2164
+ }
2165
+ const message = this.options.smsTemplate.replace("{code}", request.verificationCode).replace("{expiry}", this.options.codeExpiryMinutes.toString());
2166
+ const result = await this.options.smsProvider.sendSMS(request.phoneNumber, message);
2167
+ if (result.status === "failed") {
2168
+ throw new Error(result.error || "SMS sending failed");
2169
+ }
2170
+ }
2171
+ async sendVoiceCall(request, phoneInfo) {
2172
+ if (!this.options.voiceProvider) {
2173
+ throw new Error("Voice provider not configured");
2174
+ }
2175
+ const message = this.options.voiceTemplate.replace("{code}", request.verificationCode.split("").join(" "));
2176
+ const result = await this.options.voiceProvider.makeCall(request.phoneNumber, message);
2177
+ if (result.status === "failed") {
2178
+ throw new Error(result.error || "Voice call failed");
2179
+ }
2180
+ }
2181
+ async sendMissedCall(request, phoneInfo) {
2182
+ if (!this.options.voiceProvider?.makeMissedCall) {
2183
+ throw new Error("Missed call verification not supported");
2184
+ }
2185
+ const result = await this.options.voiceProvider.makeMissedCall(request.phoneNumber);
2186
+ if (result.status === "failed") {
2187
+ throw new Error(result.error || "Missed call failed");
2188
+ }
2189
+ if (result.missedCallNumber) {
2190
+ const codeFromNumber = result.missedCallNumber.slice(-this.options.codeLength);
2191
+ request.verificationCode = codeFromNumber;
2192
+ }
2193
+ }
2194
+ async getPhoneNumberInfo(phoneNumber) {
2195
+ if (this.phoneNumberCache.has(phoneNumber)) {
2196
+ return this.phoneNumberCache.get(phoneNumber);
2197
+ }
2198
+ const phoneInfo = this.parseKoreanPhoneNumber(phoneNumber);
2199
+ this.phoneNumberCache.set(phoneNumber, phoneInfo);
2200
+ return phoneInfo;
2201
+ }
2202
+ parseKoreanPhoneNumber(phoneNumber) {
2203
+ const cleaned = phoneNumber.replace(/\D/g, "");
2204
+ const mobilePattern = /^(010|011|016|017|018|019)(\d{7,8})$/;
2205
+ const landlinePattern = /^(02|031|032|033|041|042|043|044|051|052|053|054|055|061|062|063|064)(\d{7,8})$/;
2206
+ let isValid = false;
2207
+ let isPossible = false;
2208
+ let lineType = "unknown";
2209
+ let carrier;
2210
+ if (mobilePattern.test(cleaned)) {
2211
+ isValid = true;
2212
+ isPossible = true;
2213
+ lineType = "mobile";
2214
+ const prefix = cleaned.substring(0, 3);
2215
+ switch (prefix) {
2216
+ case "010":
2217
+ carrier = "Multiple carriers";
2218
+ break;
2219
+ case "011":
2220
+ carrier = "SK Telecom";
2221
+ break;
2222
+ case "016":
2223
+ carrier = "KT";
2224
+ break;
2225
+ case "017":
2226
+ carrier = "LG U+";
2227
+ break;
2228
+ case "018":
2229
+ carrier = "SK Telecom";
2230
+ break;
2231
+ case "019":
2232
+ carrier = "LG U+";
2233
+ break;
2234
+ }
2235
+ } else if (landlinePattern.test(cleaned)) {
2236
+ isValid = true;
2237
+ isPossible = true;
2238
+ lineType = "landline";
2239
+ } else if (cleaned.length >= 10 && cleaned.length <= 11) {
2240
+ isPossible = true;
2241
+ }
2242
+ return {
2243
+ phoneNumber: cleaned,
2244
+ countryCode: "82",
2245
+ nationalNumber: cleaned,
2246
+ carrier,
2247
+ lineType,
2248
+ isValid,
2249
+ isPossible,
2250
+ region: "KR"
2251
+ };
2252
+ }
2253
+ isNumberBlocked(phoneNumber) {
2254
+ return this.blockedNumbers.has(phoneNumber);
2255
+ }
2256
+ isRateLimited(phoneNumber) {
2257
+ const timestamps = this.rateLimitTracker.get(phoneNumber) || [];
2258
+ const rateWindow = this.options.rateLimitMinutes * 60 * 1e3;
2259
+ const now = /* @__PURE__ */ new Date();
2260
+ const recentAttempts = timestamps.filter((ts) => now.getTime() - ts.getTime() < rateWindow);
2261
+ return recentAttempts.length >= 1;
2262
+ }
2263
+ isDailyLimitExceeded(phoneNumber) {
2264
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2265
+ const dailyData = this.dailyAttemptTracker.get(phoneNumber);
2266
+ if (!dailyData || dailyData.date !== today) {
2267
+ return false;
2268
+ }
2269
+ return dailyData.count >= this.options.maxDailyAttempts;
2270
+ }
2271
+ updateRateLimit(phoneNumber) {
2272
+ const timestamps = this.rateLimitTracker.get(phoneNumber) || [];
2273
+ timestamps.push(/* @__PURE__ */ new Date());
2274
+ this.rateLimitTracker.set(phoneNumber, timestamps);
2275
+ }
2276
+ updateDailyAttempts(phoneNumber) {
2277
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2278
+ const dailyData = this.dailyAttemptTracker.get(phoneNumber);
2279
+ if (!dailyData || dailyData.date !== today) {
2280
+ this.dailyAttemptTracker.set(phoneNumber, { date: today, count: 1 });
2281
+ } else {
2282
+ dailyData.count++;
2283
+ }
2284
+ }
2285
+ validateCode(expected, provided) {
2286
+ return expected === provided.replace(/\s/g, "");
2287
+ }
2288
+ generateVerificationCode() {
2289
+ const length = this.options.codeLength;
2290
+ let code = "";
2291
+ for (let i = 0; i < length; i++) {
2292
+ code += Math.floor(Math.random() * 10).toString();
2293
+ }
2294
+ return code;
2295
+ }
2296
+ generateRequestId() {
2297
+ return `phone_verify_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2298
+ }
2299
+ };
2300
+
2301
+ // src/services/channel.service.ts
2302
+ var ChannelService = class {
2303
+ constructor() {
2304
+ this.channels = /* @__PURE__ */ new Map();
2305
+ this.senderNumbers = /* @__PURE__ */ new Map();
2306
+ }
2307
+ async createChannel(channel) {
2308
+ const newChannel = {
2309
+ ...channel,
2310
+ id: this.generateChannelId(),
2311
+ createdAt: /* @__PURE__ */ new Date(),
2312
+ updatedAt: /* @__PURE__ */ new Date()
2313
+ };
2314
+ this.channels.set(newChannel.id, newChannel);
2315
+ return newChannel;
2316
+ }
2317
+ async getChannel(channelId) {
2318
+ return this.channels.get(channelId) || null;
2319
+ }
2320
+ async listChannels(providerId) {
2321
+ const channels = Array.from(this.channels.values());
2322
+ if (providerId) {
2323
+ return channels.filter((c) => c.providerId === providerId);
2324
+ }
2325
+ return channels;
2326
+ }
2327
+ async updateChannel(channelId, updates) {
2328
+ const channel = this.channels.get(channelId);
2329
+ if (!channel) {
2330
+ throw new Error(`Channel ${channelId} not found`);
2331
+ }
2332
+ const updatedChannel = {
2333
+ ...channel,
2334
+ ...updates,
2335
+ updatedAt: /* @__PURE__ */ new Date()
2336
+ };
2337
+ this.channels.set(channelId, updatedChannel);
2338
+ return updatedChannel;
2339
+ }
2340
+ async deleteChannel(channelId) {
2341
+ this.channels.delete(channelId);
2342
+ for (const [key, senderNumber] of this.senderNumbers.entries()) {
2343
+ if (senderNumber.channelId === channelId) {
2344
+ this.senderNumbers.delete(key);
2345
+ }
2346
+ }
2347
+ }
2348
+ async addSenderNumber(channelId, phoneNumber, name) {
2349
+ const channel = this.channels.get(channelId);
2350
+ if (!channel) {
2351
+ throw new Error(`Channel ${channelId} not found`);
2352
+ }
2353
+ const senderNumber = {
2354
+ phoneNumber,
2355
+ name,
2356
+ status: "PENDING" /* PENDING */,
2357
+ channelId
2358
+ };
2359
+ this.senderNumbers.set(phoneNumber, senderNumber);
2360
+ return senderNumber;
2361
+ }
2362
+ async verifySenderNumber(phoneNumber) {
2363
+ const senderNumber = this.senderNumbers.get(phoneNumber);
2364
+ if (!senderNumber) {
2365
+ return {
2366
+ success: false,
2367
+ status: "not_found",
2368
+ error: "Sender number not found"
2369
+ };
2370
+ }
2371
+ const verificationCode = Math.floor(Math.random() * 9e5) + 1e5;
2372
+ senderNumber.verifiedAt = /* @__PURE__ */ new Date();
2373
+ senderNumber.status = "VERIFIED" /* VERIFIED */;
2374
+ this.senderNumbers.set(phoneNumber, senderNumber);
2375
+ return {
2376
+ success: true,
2377
+ status: "verified",
2378
+ verificationCode: verificationCode.toString()
2379
+ };
2380
+ }
2381
+ async getSenderNumbers(channelId) {
2382
+ const senderNumbers = Array.from(this.senderNumbers.values());
2383
+ if (channelId) {
2384
+ return senderNumbers.filter((s) => s.channelId === channelId);
2385
+ }
2386
+ return senderNumbers;
2387
+ }
2388
+ generateChannelId() {
2389
+ return `ch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2390
+ }
2391
+ };
2392
+ export {
2393
+ ActionType,
2394
+ BusinessVerifier,
2395
+ ChannelCRUD,
2396
+ ChannelCreateRequestSchema,
2397
+ ChannelFiltersSchema,
2398
+ ChannelService,
2399
+ ChannelStatus,
2400
+ ChannelType,
2401
+ DocumentStatus,
2402
+ DocumentType,
2403
+ KakaoChannelManager,
2404
+ KakaoSenderNumberManager,
2405
+ NumberVerifier,
2406
+ PermissionManager,
2407
+ PermissionScope,
2408
+ ResourceType,
2409
+ SenderNumberCategory,
2410
+ SenderNumberCreateRequestSchema,
2411
+ SenderNumberFiltersSchema,
2412
+ SenderNumberStatus,
2413
+ VerificationMethod,
2414
+ VerificationStatus,
2415
+ VerificationType
2416
+ };
2417
+ //# sourceMappingURL=index.js.map