@k-msg/provider 0.1.0 → 0.1.2
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/README.md +3 -1
- package/dist/abstract/provider.base.d.ts +108 -0
- package/dist/adapters/aligo.adapter.d.ts +50 -0
- package/dist/adapters/iwinv.adapter.d.ts +111 -0
- package/dist/aligo/provider.d.ts +18 -0
- package/dist/config/provider-config-v2.d.ts +122 -0
- package/dist/contracts/provider.contract.d.ts +355 -0
- package/dist/contracts/sms.contract.d.ts +135 -0
- package/dist/index.d.ts +29 -1424
- package/dist/index.js +21 -2003
- package/dist/index.js.map +98 -1
- package/dist/index.mjs +25 -0
- package/dist/index.mjs.map +98 -0
- package/dist/interfaces/index.d.ts +14 -0
- package/dist/interfaces/plugin.d.ts +122 -0
- package/dist/interfaces/services.d.ts +222 -0
- package/dist/iwinv/contracts/account.contract.d.ts +11 -0
- package/dist/iwinv/contracts/analytics.contract.d.ts +16 -0
- package/dist/iwinv/contracts/channel.contract.d.ts +15 -0
- package/dist/iwinv/contracts/messaging.contract.d.ts +14 -0
- package/dist/iwinv/contracts/sms.contract.d.ts +33 -0
- package/dist/iwinv/contracts/template.contract.d.ts +18 -0
- package/dist/iwinv/index.d.ts +5 -0
- package/dist/iwinv/provider-multi.d.ts +116 -0
- package/dist/iwinv/provider-sms.d.ts +55 -0
- package/dist/iwinv/provider.d.ts +42 -0
- package/dist/iwinv/types/iwinv.d.ts +153 -0
- package/dist/middleware/index.d.ts +27 -0
- package/dist/mock/index.d.ts +1 -0
- package/dist/providers/mock/index.d.ts +1 -0
- package/dist/providers/mock/mock.provider.d.ts +22 -0
- package/dist/registry/index.d.ts +1 -0
- package/dist/registry/plugin-registry.d.ts +15 -0
- package/dist/services/provider.manager.d.ts +24 -0
- package/dist/services/provider.service.d.ts +49 -0
- package/dist/test-helpers.d.ts +110 -0
- package/dist/types/aligo.d.ts +69 -0
- package/dist/types/base.d.ts +172 -0
- package/dist/types/typed-templates.d.ts +199 -0
- package/dist/types/unified-config.d.ts +197 -0
- package/dist/types/unified-errors.d.ts +225 -0
- package/dist/utils/base-plugin.d.ts +35 -0
- package/dist/utils/index.d.ts +12 -0
- package/package.json +25 -14
- package/dist/index.cjs +0 -2061
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1425
package/dist/index.cjs
DELETED
|
@@ -1,2061 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
AligoRequestAdapter: () => AligoRequestAdapter,
|
|
24
|
-
AligoResponseAdapter: () => AligoResponseAdapter,
|
|
25
|
-
BaseAlimTalkProvider: () => BaseAlimTalkProvider,
|
|
26
|
-
BasePlugin: () => BasePlugin,
|
|
27
|
-
BaseRequestAdapter: () => BaseRequestAdapter,
|
|
28
|
-
BaseResponseAdapter: () => BaseResponseAdapter,
|
|
29
|
-
IWINVProvider: () => IWINVProvider,
|
|
30
|
-
IWINVRequestAdapter: () => IWINVRequestAdapter,
|
|
31
|
-
IWINVResponseAdapter: () => IWINVResponseAdapter,
|
|
32
|
-
KakaoRequestAdapter: () => KakaoRequestAdapter,
|
|
33
|
-
KakaoResponseAdapter: () => KakaoResponseAdapter,
|
|
34
|
-
NHNResponseAdapter: () => NHNResponseAdapter,
|
|
35
|
-
PluginRegistry: () => PluginRegistry,
|
|
36
|
-
ProviderManager: () => ProviderManager,
|
|
37
|
-
RequestAdapterFactory: () => RequestAdapterFactory,
|
|
38
|
-
ResponseAdapterFactory: () => ResponseAdapterFactory,
|
|
39
|
-
createCircuitBreakerMiddleware: () => createCircuitBreakerMiddleware,
|
|
40
|
-
createLoggingMiddleware: () => createLoggingMiddleware,
|
|
41
|
-
createMetricsMiddleware: () => createMetricsMiddleware,
|
|
42
|
-
createRateLimitMiddleware: () => createRateLimitMiddleware,
|
|
43
|
-
createRetryMiddleware: () => createRetryMiddleware,
|
|
44
|
-
delay: () => delay,
|
|
45
|
-
extractVariables: () => extractVariables,
|
|
46
|
-
formatDateTime: () => formatDateTime,
|
|
47
|
-
normalizePhoneNumber: () => normalizePhoneNumber,
|
|
48
|
-
parseTemplate: () => parseTemplate,
|
|
49
|
-
retry: () => retry,
|
|
50
|
-
validatePhoneNumber: () => validatePhoneNumber
|
|
51
|
-
});
|
|
52
|
-
module.exports = __toCommonJS(index_exports);
|
|
53
|
-
|
|
54
|
-
// src/registry/plugin-registry.ts
|
|
55
|
-
var import_events = require("events");
|
|
56
|
-
var PluginRegistry = class {
|
|
57
|
-
plugins = /* @__PURE__ */ new Map();
|
|
58
|
-
instances = /* @__PURE__ */ new Map();
|
|
59
|
-
register(plugin) {
|
|
60
|
-
const id = plugin.metadata.name.toLowerCase();
|
|
61
|
-
if (this.plugins.has(id)) {
|
|
62
|
-
throw new Error(`Plugin ${id} is already registered`);
|
|
63
|
-
}
|
|
64
|
-
this.plugins.set(id, plugin);
|
|
65
|
-
}
|
|
66
|
-
async create(pluginId, config, options = {}) {
|
|
67
|
-
const plugin = this.plugins.get(pluginId.toLowerCase());
|
|
68
|
-
if (!plugin) {
|
|
69
|
-
throw new Error(`Plugin ${pluginId} not found`);
|
|
70
|
-
}
|
|
71
|
-
const PluginClass = plugin.constructor;
|
|
72
|
-
const instance = new PluginClass();
|
|
73
|
-
const context = {
|
|
74
|
-
config,
|
|
75
|
-
logger: options.logger || new ConsoleLogger(),
|
|
76
|
-
metrics: options.metrics || new NoOpMetricsCollector(),
|
|
77
|
-
storage: options.storage || new MemoryStorage(),
|
|
78
|
-
eventBus: new import_events.EventEmitter()
|
|
79
|
-
};
|
|
80
|
-
await instance.initialize(context);
|
|
81
|
-
const instanceKey = `${pluginId}-${Date.now()}`;
|
|
82
|
-
this.instances.set(instanceKey, instance);
|
|
83
|
-
return instance;
|
|
84
|
-
}
|
|
85
|
-
async loadAndCreate(pluginId, config, options) {
|
|
86
|
-
return this.create(pluginId, config, options);
|
|
87
|
-
}
|
|
88
|
-
getSupportedTypes() {
|
|
89
|
-
return Array.from(this.plugins.keys());
|
|
90
|
-
}
|
|
91
|
-
validateProviderConfig(type, config) {
|
|
92
|
-
const plugin = this.plugins.get(type.toLowerCase());
|
|
93
|
-
if (!plugin) return false;
|
|
94
|
-
return !!(config.apiUrl && config.apiKey);
|
|
95
|
-
}
|
|
96
|
-
async destroyAll() {
|
|
97
|
-
const destroyPromises = Array.from(this.instances.values()).map(
|
|
98
|
-
(instance) => instance.destroy()
|
|
99
|
-
);
|
|
100
|
-
await Promise.all(destroyPromises);
|
|
101
|
-
this.instances.clear();
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
var ConsoleLogger = class {
|
|
105
|
-
info(message, ...args) {
|
|
106
|
-
console.log(`[INFO] ${message}`, ...args);
|
|
107
|
-
}
|
|
108
|
-
error(message, error) {
|
|
109
|
-
console.error(`[ERROR] ${message}`, error);
|
|
110
|
-
}
|
|
111
|
-
debug(message, ...args) {
|
|
112
|
-
console.debug(`[DEBUG] ${message}`, ...args);
|
|
113
|
-
}
|
|
114
|
-
warn(message, ...args) {
|
|
115
|
-
console.warn(`[WARN] ${message}`, ...args);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
var NoOpMetricsCollector = class {
|
|
119
|
-
increment(_metric, _labels) {
|
|
120
|
-
}
|
|
121
|
-
histogram(_metric, _value, _labels) {
|
|
122
|
-
}
|
|
123
|
-
gauge(_metric, _value, _labels) {
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
var MemoryStorage = class {
|
|
127
|
-
store = /* @__PURE__ */ new Map();
|
|
128
|
-
async get(key) {
|
|
129
|
-
const item = this.store.get(key);
|
|
130
|
-
if (!item) return void 0;
|
|
131
|
-
if (item.expiry && Date.now() > item.expiry) {
|
|
132
|
-
this.store.delete(key);
|
|
133
|
-
return void 0;
|
|
134
|
-
}
|
|
135
|
-
return item.value;
|
|
136
|
-
}
|
|
137
|
-
async set(key, value, ttl) {
|
|
138
|
-
const expiry = ttl ? Date.now() + ttl * 1e3 : void 0;
|
|
139
|
-
this.store.set(key, { value, expiry });
|
|
140
|
-
}
|
|
141
|
-
async delete(key) {
|
|
142
|
-
this.store.delete(key);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
// src/middleware/index.ts
|
|
147
|
-
function createRetryMiddleware(options) {
|
|
148
|
-
return {
|
|
149
|
-
name: "retry",
|
|
150
|
-
error: async (error, context) => {
|
|
151
|
-
const retries = context.metadata.retries || 0;
|
|
152
|
-
if (retries >= options.maxRetries) {
|
|
153
|
-
throw error;
|
|
154
|
-
}
|
|
155
|
-
const isRetryable = options.retryableErrors?.includes(error.code) || options.retryableStatusCodes?.includes(error.status) || error.code === "ETIMEDOUT" || error.code === "ECONNRESET";
|
|
156
|
-
if (!isRetryable) {
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
159
|
-
await new Promise(
|
|
160
|
-
(resolve) => setTimeout(resolve, options.retryDelay * (retries + 1))
|
|
161
|
-
);
|
|
162
|
-
context.metadata.retries = retries + 1;
|
|
163
|
-
throw { ...error, shouldRetry: true };
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
function createRateLimitMiddleware(options) {
|
|
168
|
-
const windows = /* @__PURE__ */ new Map();
|
|
169
|
-
return {
|
|
170
|
-
name: "rate-limit",
|
|
171
|
-
pre: async (context) => {
|
|
172
|
-
const now = Date.now();
|
|
173
|
-
const key = "global";
|
|
174
|
-
if (!windows.has(key)) {
|
|
175
|
-
windows.set(key, []);
|
|
176
|
-
}
|
|
177
|
-
const timestamps = windows.get(key);
|
|
178
|
-
if (options.messagesPerSecond) {
|
|
179
|
-
const recentCount = timestamps.filter((t) => now - t < 1e3).length;
|
|
180
|
-
if (recentCount >= options.messagesPerSecond) {
|
|
181
|
-
throw new Error("Rate limit exceeded: messages per second");
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
if (options.messagesPerMinute) {
|
|
185
|
-
const recentCount = timestamps.filter((t) => now - t < 6e4).length;
|
|
186
|
-
if (recentCount >= options.messagesPerMinute) {
|
|
187
|
-
throw new Error("Rate limit exceeded: messages per minute");
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
timestamps.push(now);
|
|
191
|
-
const cutoff = now - 36e5;
|
|
192
|
-
const filtered = timestamps.filter((t) => t > cutoff);
|
|
193
|
-
windows.set(key, filtered);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function createLoggingMiddleware(options) {
|
|
198
|
-
return {
|
|
199
|
-
name: "logging",
|
|
200
|
-
pre: async (context) => {
|
|
201
|
-
if (options.logLevel === "debug") {
|
|
202
|
-
options.logger.debug("Request started", {
|
|
203
|
-
metadata: context.metadata,
|
|
204
|
-
timestamp: context.startTime
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
post: async (context) => {
|
|
209
|
-
const duration = Date.now() - context.startTime;
|
|
210
|
-
options.logger.info("Request completed", {
|
|
211
|
-
duration,
|
|
212
|
-
success: true
|
|
213
|
-
});
|
|
214
|
-
},
|
|
215
|
-
error: async (error, context) => {
|
|
216
|
-
const duration = Date.now() - context.startTime;
|
|
217
|
-
options.logger.error("Request failed", {
|
|
218
|
-
error: error.message,
|
|
219
|
-
duration,
|
|
220
|
-
stack: error.stack
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
function createMetricsMiddleware(options) {
|
|
226
|
-
return {
|
|
227
|
-
name: "metrics",
|
|
228
|
-
pre: async (context) => {
|
|
229
|
-
options.collector.increment("requests_total", options.labels);
|
|
230
|
-
},
|
|
231
|
-
post: async (context) => {
|
|
232
|
-
const duration = Date.now() - context.startTime;
|
|
233
|
-
options.collector.histogram("request_duration_ms", duration, options.labels);
|
|
234
|
-
options.collector.increment("requests_success_total", options.labels);
|
|
235
|
-
},
|
|
236
|
-
error: async (error, context) => {
|
|
237
|
-
const duration = Date.now() - context.startTime;
|
|
238
|
-
options.collector.histogram("request_duration_ms", duration, options.labels);
|
|
239
|
-
options.collector.increment("requests_error_total", {
|
|
240
|
-
...options.labels,
|
|
241
|
-
error_type: error.constructor.name
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
function createCircuitBreakerMiddleware(options) {
|
|
247
|
-
let state = "CLOSED";
|
|
248
|
-
let failures = 0;
|
|
249
|
-
let nextAttempt = 0;
|
|
250
|
-
return {
|
|
251
|
-
name: "circuit-breaker",
|
|
252
|
-
pre: async (context) => {
|
|
253
|
-
const now = Date.now();
|
|
254
|
-
if (state === "OPEN") {
|
|
255
|
-
if (now < nextAttempt) {
|
|
256
|
-
throw new Error("Circuit breaker is OPEN");
|
|
257
|
-
}
|
|
258
|
-
state = "HALF_OPEN";
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
post: async (context) => {
|
|
262
|
-
if (state === "HALF_OPEN") {
|
|
263
|
-
state = "CLOSED";
|
|
264
|
-
failures = 0;
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
error: async (error, context) => {
|
|
268
|
-
failures++;
|
|
269
|
-
if (failures >= options.threshold) {
|
|
270
|
-
state = "OPEN";
|
|
271
|
-
nextAttempt = Date.now() + options.resetTimeout;
|
|
272
|
-
}
|
|
273
|
-
throw error;
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// src/utils/base-plugin.ts
|
|
279
|
-
var BasePlugin = class {
|
|
280
|
-
context;
|
|
281
|
-
middleware = [];
|
|
282
|
-
async initialize(context) {
|
|
283
|
-
this.context = context;
|
|
284
|
-
this.context.logger.info(`Initializing plugin: ${this.metadata.name}`);
|
|
285
|
-
}
|
|
286
|
-
async destroy() {
|
|
287
|
-
this.context.logger.info(`Destroying plugin: ${this.metadata.name}`);
|
|
288
|
-
}
|
|
289
|
-
async executeMiddleware(phase, context, error) {
|
|
290
|
-
for (const middleware of this.middleware) {
|
|
291
|
-
try {
|
|
292
|
-
if (phase === "pre" && middleware.pre) {
|
|
293
|
-
await middleware.pre(context);
|
|
294
|
-
} else if (phase === "post" && middleware.post) {
|
|
295
|
-
await middleware.post(context);
|
|
296
|
-
} else if (phase === "error" && middleware.error && error) {
|
|
297
|
-
await middleware.error(error, context);
|
|
298
|
-
}
|
|
299
|
-
} catch (err) {
|
|
300
|
-
this.context.logger.error(`Middleware ${middleware.name} failed`, err);
|
|
301
|
-
throw err;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
createMiddlewareContext(request, metadata = {}) {
|
|
306
|
-
return {
|
|
307
|
-
request,
|
|
308
|
-
response: void 0,
|
|
309
|
-
metadata: {
|
|
310
|
-
...metadata,
|
|
311
|
-
pluginName: this.metadata.name,
|
|
312
|
-
pluginVersion: this.metadata.version
|
|
313
|
-
},
|
|
314
|
-
startTime: Date.now()
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
validateConfig(config, required) {
|
|
318
|
-
for (const field of required) {
|
|
319
|
-
if (!config[field]) {
|
|
320
|
-
throw new Error(`${this.metadata.name}: Missing required config field: ${field}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
async makeRequest(url, options, metadata = {}) {
|
|
325
|
-
const context = this.createMiddlewareContext({ url, options }, metadata);
|
|
326
|
-
try {
|
|
327
|
-
await this.executeMiddleware("pre", context);
|
|
328
|
-
const response = await fetch(url, {
|
|
329
|
-
...options,
|
|
330
|
-
headers: {
|
|
331
|
-
"User-Agent": `K-OTP-${this.metadata.name}/${this.metadata.version}`,
|
|
332
|
-
...this.context.config.headers,
|
|
333
|
-
...options.headers
|
|
334
|
-
},
|
|
335
|
-
signal: AbortSignal.timeout(this.context.config.timeout || 3e4)
|
|
336
|
-
});
|
|
337
|
-
context.response = response;
|
|
338
|
-
await this.executeMiddleware("post", context);
|
|
339
|
-
return response;
|
|
340
|
-
} catch (error) {
|
|
341
|
-
await this.executeMiddleware("error", context, error);
|
|
342
|
-
throw error;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Make HTTP request and parse JSON response
|
|
347
|
-
* Subclasses should use their specific response adapters to transform the result
|
|
348
|
-
*/
|
|
349
|
-
async makeJSONRequest(url, options, metadata = {}) {
|
|
350
|
-
const response = await this.makeRequest(url, options, metadata);
|
|
351
|
-
if (!response.ok) {
|
|
352
|
-
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
353
|
-
error.response = response;
|
|
354
|
-
error.status = response.status;
|
|
355
|
-
throw error;
|
|
356
|
-
}
|
|
357
|
-
try {
|
|
358
|
-
return await response.json();
|
|
359
|
-
} catch (parseError) {
|
|
360
|
-
const error = new Error("Failed to parse JSON response");
|
|
361
|
-
error.response = response;
|
|
362
|
-
error.parseError = parseError;
|
|
363
|
-
throw error;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Helper method for logging provider-specific operations
|
|
368
|
-
*/
|
|
369
|
-
logOperation(operation, data) {
|
|
370
|
-
this.context.logger.info(`${this.metadata.name}: ${operation}`, data);
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Helper method for logging provider-specific errors
|
|
374
|
-
*/
|
|
375
|
-
logError(operation, error, data) {
|
|
376
|
-
this.context.logger.error(`${this.metadata.name}: ${operation} failed`, { error, data });
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
// src/utils/index.ts
|
|
381
|
-
function normalizePhoneNumber(phone) {
|
|
382
|
-
const cleaned = phone.replace(/[^\d]/g, "");
|
|
383
|
-
if (cleaned.startsWith("82")) {
|
|
384
|
-
return "0" + cleaned.substring(2);
|
|
385
|
-
}
|
|
386
|
-
if (cleaned.startsWith("0")) {
|
|
387
|
-
return cleaned;
|
|
388
|
-
}
|
|
389
|
-
if (cleaned.length >= 10 && cleaned.length <= 11) {
|
|
390
|
-
return "0" + cleaned;
|
|
391
|
-
}
|
|
392
|
-
return cleaned;
|
|
393
|
-
}
|
|
394
|
-
function validatePhoneNumber(phone) {
|
|
395
|
-
const normalized = normalizePhoneNumber(phone);
|
|
396
|
-
return /^01[0-9]{8,9}$/.test(normalized);
|
|
397
|
-
}
|
|
398
|
-
function formatDateTime(date) {
|
|
399
|
-
const year = date.getFullYear();
|
|
400
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
401
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
402
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
403
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
404
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
405
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
406
|
-
}
|
|
407
|
-
function parseTemplate(template, variables) {
|
|
408
|
-
let result = template;
|
|
409
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
410
|
-
const regex = new RegExp(`#{${key}}`, "g");
|
|
411
|
-
result = result.replace(regex, value);
|
|
412
|
-
}
|
|
413
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
414
|
-
const regex = new RegExp(`{{${key}}}`, "g");
|
|
415
|
-
result = result.replace(regex, value);
|
|
416
|
-
}
|
|
417
|
-
return result;
|
|
418
|
-
}
|
|
419
|
-
function extractVariables(template) {
|
|
420
|
-
const variables = /* @__PURE__ */ new Set();
|
|
421
|
-
const hashMatches = template.match(/#\{([^}]+)\}/g);
|
|
422
|
-
if (hashMatches) {
|
|
423
|
-
hashMatches.forEach((match) => {
|
|
424
|
-
const variable = match.slice(2, -1);
|
|
425
|
-
variables.add(variable);
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
const braceMatches = template.match(/\{\{([^}]+)\}\}/g);
|
|
429
|
-
if (braceMatches) {
|
|
430
|
-
braceMatches.forEach((match) => {
|
|
431
|
-
const variable = match.slice(2, -2);
|
|
432
|
-
variables.add(variable);
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
return Array.from(variables);
|
|
436
|
-
}
|
|
437
|
-
function delay(ms) {
|
|
438
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
439
|
-
}
|
|
440
|
-
function retry(fn, options) {
|
|
441
|
-
return new Promise(async (resolve, reject) => {
|
|
442
|
-
let lastError;
|
|
443
|
-
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
|
|
444
|
-
try {
|
|
445
|
-
if (attempt > 0) {
|
|
446
|
-
const delayMs = options.backoff === "exponential" ? options.delay * Math.pow(2, attempt - 1) : options.delay * attempt;
|
|
447
|
-
await delay(delayMs);
|
|
448
|
-
}
|
|
449
|
-
const result = await fn();
|
|
450
|
-
resolve(result);
|
|
451
|
-
return;
|
|
452
|
-
} catch (error) {
|
|
453
|
-
lastError = error;
|
|
454
|
-
if (attempt === options.maxRetries) {
|
|
455
|
-
reject(lastError);
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// src/abstract/provider.base.ts
|
|
464
|
-
var BaseAlimTalkProvider = class {
|
|
465
|
-
config = {};
|
|
466
|
-
isConfigured = false;
|
|
467
|
-
constructor(config) {
|
|
468
|
-
if (config) {
|
|
469
|
-
this.configure(config);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Configure the provider with necessary credentials and settings
|
|
474
|
-
*/
|
|
475
|
-
configure(config) {
|
|
476
|
-
this.validateConfiguration(config);
|
|
477
|
-
this.config = { ...config };
|
|
478
|
-
this.isConfigured = true;
|
|
479
|
-
this.onConfigured();
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Validate the provided configuration
|
|
483
|
-
*/
|
|
484
|
-
validateConfiguration(config) {
|
|
485
|
-
const schema = this.getConfigurationSchema();
|
|
486
|
-
for (const field of schema.required) {
|
|
487
|
-
if (!(field.key in config)) {
|
|
488
|
-
throw new Error(`Required configuration field '${field.key}' is missing`);
|
|
489
|
-
}
|
|
490
|
-
this.validateFieldValue(field, config[field.key]);
|
|
491
|
-
}
|
|
492
|
-
for (const field of schema.optional) {
|
|
493
|
-
if (field.key in config) {
|
|
494
|
-
this.validateFieldValue(field, config[field.key]);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
validateFieldValue(field, value) {
|
|
499
|
-
switch (field.type) {
|
|
500
|
-
case "string":
|
|
501
|
-
if (typeof value !== "string") {
|
|
502
|
-
throw new Error(`Field '${field.key}' must be a string`);
|
|
503
|
-
}
|
|
504
|
-
break;
|
|
505
|
-
case "number":
|
|
506
|
-
if (typeof value !== "number") {
|
|
507
|
-
throw new Error(`Field '${field.key}' must be a number`);
|
|
508
|
-
}
|
|
509
|
-
break;
|
|
510
|
-
case "boolean":
|
|
511
|
-
if (typeof value !== "boolean") {
|
|
512
|
-
throw new Error(`Field '${field.key}' must be a boolean`);
|
|
513
|
-
}
|
|
514
|
-
break;
|
|
515
|
-
case "url":
|
|
516
|
-
try {
|
|
517
|
-
new URL(String(value));
|
|
518
|
-
} catch {
|
|
519
|
-
throw new Error(`Field '${field.key}' must be a valid URL`);
|
|
520
|
-
}
|
|
521
|
-
break;
|
|
522
|
-
}
|
|
523
|
-
if (field.validation) {
|
|
524
|
-
if (field.validation.pattern) {
|
|
525
|
-
const regex = new RegExp(field.validation.pattern);
|
|
526
|
-
if (!regex.test(String(value))) {
|
|
527
|
-
throw new Error(`Field '${field.key}' does not match required pattern`);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
if (field.validation.min !== void 0 && Number(value) < field.validation.min) {
|
|
531
|
-
throw new Error(`Field '${field.key}' must be at least ${field.validation.min}`);
|
|
532
|
-
}
|
|
533
|
-
if (field.validation.max !== void 0 && Number(value) > field.validation.max) {
|
|
534
|
-
throw new Error(`Field '${field.key}' must be at most ${field.validation.max}`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Called after configuration is set
|
|
540
|
-
*/
|
|
541
|
-
onConfigured() {
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Check if the provider is properly configured
|
|
545
|
-
*/
|
|
546
|
-
isReady() {
|
|
547
|
-
return this.isConfigured;
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Get configuration value
|
|
551
|
-
*/
|
|
552
|
-
getConfig(key) {
|
|
553
|
-
if (!this.isConfigured) {
|
|
554
|
-
throw new Error("Provider is not configured");
|
|
555
|
-
}
|
|
556
|
-
return this.config[key];
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Check if a configuration key exists
|
|
560
|
-
*/
|
|
561
|
-
hasConfig(key) {
|
|
562
|
-
return key in this.config;
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* Perform health check on the provider
|
|
566
|
-
*/
|
|
567
|
-
async healthCheck() {
|
|
568
|
-
const issues = [];
|
|
569
|
-
const startTime = Date.now();
|
|
570
|
-
try {
|
|
571
|
-
if (!this.isReady()) {
|
|
572
|
-
issues.push("Provider is not configured");
|
|
573
|
-
return { healthy: false, issues };
|
|
574
|
-
}
|
|
575
|
-
await this.testConnectivity();
|
|
576
|
-
await this.testAuthentication();
|
|
577
|
-
const latency = Date.now() - startTime;
|
|
578
|
-
return {
|
|
579
|
-
healthy: issues.length === 0,
|
|
580
|
-
issues,
|
|
581
|
-
latency
|
|
582
|
-
};
|
|
583
|
-
} catch (error) {
|
|
584
|
-
issues.push(`Health check failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
585
|
-
return { healthy: false, issues };
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Get provider information
|
|
590
|
-
*/
|
|
591
|
-
getInfo() {
|
|
592
|
-
return {
|
|
593
|
-
id: this.id,
|
|
594
|
-
name: this.name,
|
|
595
|
-
version: this.getVersion(),
|
|
596
|
-
capabilities: this.capabilities,
|
|
597
|
-
configured: this.isConfigured
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Cleanup resources when provider is destroyed
|
|
602
|
-
*/
|
|
603
|
-
destroy() {
|
|
604
|
-
this.config = {};
|
|
605
|
-
this.isConfigured = false;
|
|
606
|
-
this.onDestroy();
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Called when provider is being destroyed
|
|
610
|
-
*/
|
|
611
|
-
onDestroy() {
|
|
612
|
-
}
|
|
613
|
-
/**
|
|
614
|
-
* Create standardized error
|
|
615
|
-
*/
|
|
616
|
-
createError(code, message, details) {
|
|
617
|
-
const error = new Error(message);
|
|
618
|
-
error.code = code;
|
|
619
|
-
error.provider = this.id;
|
|
620
|
-
error.details = details;
|
|
621
|
-
return error;
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Log provider activity
|
|
625
|
-
*/
|
|
626
|
-
log(level, message, data) {
|
|
627
|
-
const logData = {
|
|
628
|
-
provider: this.id,
|
|
629
|
-
level,
|
|
630
|
-
message,
|
|
631
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
632
|
-
};
|
|
633
|
-
if (data) {
|
|
634
|
-
logData.data = data;
|
|
635
|
-
}
|
|
636
|
-
console.log(JSON.stringify(logData));
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Handle rate limiting
|
|
640
|
-
*/
|
|
641
|
-
async handleRateLimit(operation) {
|
|
642
|
-
const rateLimit = this.capabilities.messaging.maxRequestsPerSecond;
|
|
643
|
-
if (rateLimit > 0) {
|
|
644
|
-
const delay2 = 1e3 / rateLimit;
|
|
645
|
-
await new Promise((resolve) => setTimeout(resolve, delay2));
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Retry mechanism for failed operations
|
|
650
|
-
*/
|
|
651
|
-
async withRetry(operation, options = {}) {
|
|
652
|
-
const {
|
|
653
|
-
maxRetries = 3,
|
|
654
|
-
initialDelay = 1e3,
|
|
655
|
-
maxDelay = 1e4,
|
|
656
|
-
backoffFactor = 2
|
|
657
|
-
} = options;
|
|
658
|
-
let lastError;
|
|
659
|
-
let delay2 = initialDelay;
|
|
660
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
661
|
-
try {
|
|
662
|
-
return await operation();
|
|
663
|
-
} catch (error) {
|
|
664
|
-
lastError = error;
|
|
665
|
-
if (attempt === maxRetries) {
|
|
666
|
-
break;
|
|
667
|
-
}
|
|
668
|
-
this.log("warn", `Operation failed, retrying in ${delay2}ms`, {
|
|
669
|
-
attempt: attempt + 1,
|
|
670
|
-
maxRetries,
|
|
671
|
-
error: lastError.message
|
|
672
|
-
});
|
|
673
|
-
await new Promise((resolve) => setTimeout(resolve, delay2));
|
|
674
|
-
delay2 = Math.min(delay2 * backoffFactor, maxDelay);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
throw lastError;
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
|
|
681
|
-
// src/adapters/request.adapter.ts
|
|
682
|
-
var BaseRequestAdapter = class {
|
|
683
|
-
/**
|
|
684
|
-
* Common transformation utilities
|
|
685
|
-
*/
|
|
686
|
-
formatPhoneNumber(phoneNumber, countryCode = "KR") {
|
|
687
|
-
const digits = phoneNumber.replace(/\D/g, "");
|
|
688
|
-
if (countryCode === "KR") {
|
|
689
|
-
if (digits.startsWith("82")) {
|
|
690
|
-
return digits.substring(2);
|
|
691
|
-
}
|
|
692
|
-
if (digits.startsWith("0")) {
|
|
693
|
-
return digits;
|
|
694
|
-
}
|
|
695
|
-
return "0" + digits;
|
|
696
|
-
}
|
|
697
|
-
return phoneNumber;
|
|
698
|
-
}
|
|
699
|
-
formatVariables(variables) {
|
|
700
|
-
const formatted = {};
|
|
701
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
702
|
-
if (value instanceof Date) {
|
|
703
|
-
formatted[key] = value.toISOString();
|
|
704
|
-
} else if (typeof value === "object") {
|
|
705
|
-
formatted[key] = JSON.stringify(value);
|
|
706
|
-
} else {
|
|
707
|
-
formatted[key] = String(value);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
return formatted;
|
|
711
|
-
}
|
|
712
|
-
validateRequiredFields(data, requiredFields) {
|
|
713
|
-
const obj = data;
|
|
714
|
-
for (const field of requiredFields) {
|
|
715
|
-
if (!(field in obj) || obj[field] === void 0 || obj[field] === null) {
|
|
716
|
-
throw new Error(`Required field '${field}' is missing`);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
};
|
|
721
|
-
var IWINVRequestAdapter = class extends BaseRequestAdapter {
|
|
722
|
-
transformMessageRequest(request) {
|
|
723
|
-
this.validateRequiredFields(request, ["templateCode", "phoneNumber"]);
|
|
724
|
-
return {
|
|
725
|
-
profile_key: this.getProfileKey(),
|
|
726
|
-
template_code: request.templateCode,
|
|
727
|
-
phone_number: this.formatPhoneNumber(request.phoneNumber),
|
|
728
|
-
message_variables: this.formatVariables(request.variables),
|
|
729
|
-
sender_number: request.senderNumber,
|
|
730
|
-
reserve_time: request.options?.scheduledAt ? Math.floor(new Date(request.options.scheduledAt).getTime() / 1e3) : void 0
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
transformTemplateRequest(request) {
|
|
734
|
-
this.validateRequiredFields(request, ["name", "content"]);
|
|
735
|
-
return {
|
|
736
|
-
profile_key: this.getProfileKey(),
|
|
737
|
-
template_name: request.name,
|
|
738
|
-
template_content: request.content,
|
|
739
|
-
template_category: this.mapCategory(request.category),
|
|
740
|
-
template_variables: request.variables?.map((v) => ({
|
|
741
|
-
name: v.name,
|
|
742
|
-
type: v.type,
|
|
743
|
-
required: v.required ? "Y" : "N",
|
|
744
|
-
max_length: v.maxLength
|
|
745
|
-
})),
|
|
746
|
-
template_buttons: request.buttons?.map((b) => ({
|
|
747
|
-
type: b.type,
|
|
748
|
-
name: b.name,
|
|
749
|
-
url_mobile: b.linkMobile,
|
|
750
|
-
url_pc: b.linkPc,
|
|
751
|
-
scheme_ios: b.schemeIos,
|
|
752
|
-
scheme_android: b.schemeAndroid
|
|
753
|
-
}))
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
getProfileKey() {
|
|
757
|
-
return process.env.IWINV_PROFILE_KEY || "";
|
|
758
|
-
}
|
|
759
|
-
mapCategory(category) {
|
|
760
|
-
const categoryMap = {
|
|
761
|
-
"AUTHENTICATION": "A",
|
|
762
|
-
"NOTIFICATION": "N",
|
|
763
|
-
"PROMOTION": "P",
|
|
764
|
-
"INFORMATION": "I"
|
|
765
|
-
};
|
|
766
|
-
return categoryMap[category] || "I";
|
|
767
|
-
}
|
|
768
|
-
};
|
|
769
|
-
var AligoRequestAdapter = class extends BaseRequestAdapter {
|
|
770
|
-
transformMessageRequest(request) {
|
|
771
|
-
this.validateRequiredFields(request, ["templateCode", "phoneNumber"]);
|
|
772
|
-
return {
|
|
773
|
-
apikey: this.getApiKey(),
|
|
774
|
-
userid: this.getUserId(),
|
|
775
|
-
senderkey: this.getSenderKey(),
|
|
776
|
-
template_code: request.templateCode,
|
|
777
|
-
receiver: this.formatPhoneNumber(request.phoneNumber),
|
|
778
|
-
subject: "AlimTalk",
|
|
779
|
-
message: this.buildMessage(request),
|
|
780
|
-
button: request.variables.buttons ? JSON.stringify(request.variables.buttons) : void 0,
|
|
781
|
-
reservation: request.options?.scheduledAt ? this.formatDateTime(new Date(request.options.scheduledAt)) : void 0
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
transformTemplateRequest(request) {
|
|
785
|
-
this.validateRequiredFields(request, ["name", "content"]);
|
|
786
|
-
return {
|
|
787
|
-
apikey: this.getApiKey(),
|
|
788
|
-
userid: this.getUserId(),
|
|
789
|
-
senderkey: this.getSenderKey(),
|
|
790
|
-
template_name: request.name,
|
|
791
|
-
template_content: request.content,
|
|
792
|
-
template_emphasis: this.extractEmphasis(request.content),
|
|
793
|
-
template_extra: this.buildTemplateExtra(request),
|
|
794
|
-
template_ad: this.isPromotional(request.category) ? "Y" : "N"
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
getApiKey() {
|
|
798
|
-
return process.env.ALIGO_API_KEY || "";
|
|
799
|
-
}
|
|
800
|
-
getUserId() {
|
|
801
|
-
return process.env.ALIGO_USER_ID || "";
|
|
802
|
-
}
|
|
803
|
-
getSenderKey() {
|
|
804
|
-
return process.env.ALIGO_SENDER_KEY || "";
|
|
805
|
-
}
|
|
806
|
-
buildMessage(request) {
|
|
807
|
-
return request.templateCode;
|
|
808
|
-
}
|
|
809
|
-
formatDateTime(date) {
|
|
810
|
-
return date.toISOString().replace(/[-:]/g, "").replace("T", "").substring(0, 12);
|
|
811
|
-
}
|
|
812
|
-
extractEmphasis(content) {
|
|
813
|
-
const emphasisMatch = content.match(/\*\*(.*?)\*\*/);
|
|
814
|
-
return emphasisMatch ? emphasisMatch[1] : "";
|
|
815
|
-
}
|
|
816
|
-
buildTemplateExtra(request) {
|
|
817
|
-
const extra = {};
|
|
818
|
-
if (request.buttons) {
|
|
819
|
-
extra.buttons = request.buttons;
|
|
820
|
-
}
|
|
821
|
-
if (request.variables) {
|
|
822
|
-
extra.variables = request.variables;
|
|
823
|
-
}
|
|
824
|
-
return JSON.stringify(extra);
|
|
825
|
-
}
|
|
826
|
-
isPromotional(category) {
|
|
827
|
-
return category === "PROMOTION";
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
var KakaoRequestAdapter = class extends BaseRequestAdapter {
|
|
831
|
-
transformMessageRequest(request) {
|
|
832
|
-
this.validateRequiredFields(request, ["templateCode", "phoneNumber"]);
|
|
833
|
-
return {
|
|
834
|
-
template_object: {
|
|
835
|
-
object_type: "text",
|
|
836
|
-
text: this.buildTemplateText(request),
|
|
837
|
-
link: this.buildTemplateLink(request),
|
|
838
|
-
button_title: request.variables.buttonTitle || ""
|
|
839
|
-
},
|
|
840
|
-
user_ids: [this.formatPhoneNumber(request.phoneNumber)]
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
transformTemplateRequest(request) {
|
|
844
|
-
this.validateRequiredFields(request, ["name", "content"]);
|
|
845
|
-
return {
|
|
846
|
-
template: {
|
|
847
|
-
name: request.name,
|
|
848
|
-
content: request.content,
|
|
849
|
-
category_code: this.mapCategoryCode(request.category),
|
|
850
|
-
template_message_type: "BA",
|
|
851
|
-
// Basic AlimTalk
|
|
852
|
-
template_emphasis_type: this.extractEmphasisType(request.content),
|
|
853
|
-
template_title: request.name,
|
|
854
|
-
template_subtitle: "",
|
|
855
|
-
template_imageurl: "",
|
|
856
|
-
template_header: "",
|
|
857
|
-
template_item_highlight: {
|
|
858
|
-
title: "",
|
|
859
|
-
description: ""
|
|
860
|
-
},
|
|
861
|
-
template_item: {
|
|
862
|
-
list: []
|
|
863
|
-
},
|
|
864
|
-
template_button: this.buildTemplateButtons(request.buttons || [])
|
|
865
|
-
}
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
buildTemplateText(request) {
|
|
869
|
-
let text = request.templateCode;
|
|
870
|
-
for (const [key, value] of Object.entries(request.variables)) {
|
|
871
|
-
text = text.replace(new RegExp(`#{${key}}`, "g"), String(value));
|
|
872
|
-
}
|
|
873
|
-
return text;
|
|
874
|
-
}
|
|
875
|
-
buildTemplateLink(request) {
|
|
876
|
-
if (request.variables.linkUrl) {
|
|
877
|
-
return {
|
|
878
|
-
web_url: request.variables.linkUrl,
|
|
879
|
-
mobile_web_url: request.variables.linkUrl
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
return {};
|
|
883
|
-
}
|
|
884
|
-
mapCategoryCode(category) {
|
|
885
|
-
const categoryMap = {
|
|
886
|
-
"AUTHENTICATION": "999999",
|
|
887
|
-
"NOTIFICATION": "999998",
|
|
888
|
-
"PROMOTION": "999997",
|
|
889
|
-
"INFORMATION": "999996"
|
|
890
|
-
};
|
|
891
|
-
return categoryMap[category] || "999999";
|
|
892
|
-
}
|
|
893
|
-
extractEmphasisType(content) {
|
|
894
|
-
if (content.includes("**")) return "BOLD";
|
|
895
|
-
if (content.includes("__")) return "UNDERLINE";
|
|
896
|
-
return "NONE";
|
|
897
|
-
}
|
|
898
|
-
buildTemplateButtons(buttons) {
|
|
899
|
-
return buttons.map((button) => {
|
|
900
|
-
const btn = button;
|
|
901
|
-
return {
|
|
902
|
-
name: btn.name,
|
|
903
|
-
type: btn.type,
|
|
904
|
-
url_mobile: btn.linkMobile,
|
|
905
|
-
url_pc: btn.linkPc,
|
|
906
|
-
scheme_ios: btn.schemeIos,
|
|
907
|
-
scheme_android: btn.schemeAndroid
|
|
908
|
-
};
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
var RequestAdapterFactory = class _RequestAdapterFactory {
|
|
913
|
-
static adapters = /* @__PURE__ */ new Map();
|
|
914
|
-
static {
|
|
915
|
-
_RequestAdapterFactory.adapters.set("iwinv", IWINVRequestAdapter);
|
|
916
|
-
_RequestAdapterFactory.adapters.set("aligo", AligoRequestAdapter);
|
|
917
|
-
_RequestAdapterFactory.adapters.set("kakao", KakaoRequestAdapter);
|
|
918
|
-
}
|
|
919
|
-
static create(providerId) {
|
|
920
|
-
const AdapterClass = this.adapters.get(providerId.toLowerCase());
|
|
921
|
-
if (!AdapterClass) {
|
|
922
|
-
throw new Error(`No request adapter found for provider: ${providerId}`);
|
|
923
|
-
}
|
|
924
|
-
return new AdapterClass();
|
|
925
|
-
}
|
|
926
|
-
static register(providerId, adapterClass) {
|
|
927
|
-
this.adapters.set(providerId.toLowerCase(), adapterClass);
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
// src/adapters/response.adapter.ts
|
|
932
|
-
var BaseResponseAdapter = class {
|
|
933
|
-
/**
|
|
934
|
-
* Common error transformation
|
|
935
|
-
*/
|
|
936
|
-
transformError(providerError) {
|
|
937
|
-
return {
|
|
938
|
-
code: this.extractErrorCode(providerError),
|
|
939
|
-
message: this.extractErrorMessage(providerError),
|
|
940
|
-
details: this.extractErrorDetails(providerError)
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Common status mapping utilities
|
|
945
|
-
*/
|
|
946
|
-
mapMessageStatus(providerStatus) {
|
|
947
|
-
const statusMap = {
|
|
948
|
-
"queued": "QUEUED" /* QUEUED */,
|
|
949
|
-
"sending": "SENDING" /* SENDING */,
|
|
950
|
-
"sent": "SENT" /* SENT */,
|
|
951
|
-
"delivered": "DELIVERED" /* DELIVERED */,
|
|
952
|
-
"failed": "FAILED" /* FAILED */,
|
|
953
|
-
"cancelled": "CANCELLED" /* CANCELLED */
|
|
954
|
-
};
|
|
955
|
-
return statusMap[providerStatus.toLowerCase()] || "FAILED" /* FAILED */;
|
|
956
|
-
}
|
|
957
|
-
mapTemplateStatus(providerStatus) {
|
|
958
|
-
const statusMap = {
|
|
959
|
-
"draft": "DRAFT" /* DRAFT */,
|
|
960
|
-
"pending": "PENDING" /* PENDING */,
|
|
961
|
-
"approved": "APPROVED" /* APPROVED */,
|
|
962
|
-
"rejected": "REJECTED" /* REJECTED */,
|
|
963
|
-
"disabled": "DISABLED" /* DISABLED */
|
|
964
|
-
};
|
|
965
|
-
return statusMap[providerStatus.toLowerCase()] || "DRAFT" /* DRAFT */;
|
|
966
|
-
}
|
|
967
|
-
parseDate(dateString) {
|
|
968
|
-
if (!dateString) return void 0;
|
|
969
|
-
try {
|
|
970
|
-
return new Date(dateString);
|
|
971
|
-
} catch {
|
|
972
|
-
return void 0;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
};
|
|
976
|
-
var IWINVResponseAdapter = class extends BaseResponseAdapter {
|
|
977
|
-
transformMessageResponse(providerResponse) {
|
|
978
|
-
const response = providerResponse;
|
|
979
|
-
return {
|
|
980
|
-
messageId: response.msg_id || response.msgid,
|
|
981
|
-
status: this.mapIWINVMessageStatus(response.result_code),
|
|
982
|
-
sentAt: this.parseDate(response.send_time),
|
|
983
|
-
error: response.result_code !== "1" ? this.transformError(providerResponse) : void 0
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
transformTemplateResponse(providerResponse) {
|
|
987
|
-
const response = providerResponse;
|
|
988
|
-
return {
|
|
989
|
-
templateId: response.template_id,
|
|
990
|
-
providerTemplateCode: response.template_code,
|
|
991
|
-
status: this.mapIWINVTemplateStatus(response.status),
|
|
992
|
-
message: response.message || response.comment
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
mapIWINVMessageStatus(resultCode) {
|
|
996
|
-
const statusMap = {
|
|
997
|
-
"1": "SENT" /* SENT */,
|
|
998
|
-
"0": "FAILED" /* FAILED */,
|
|
999
|
-
"-1": "FAILED" /* FAILED */,
|
|
1000
|
-
"-2": "FAILED" /* FAILED */,
|
|
1001
|
-
"-3": "FAILED" /* FAILED */,
|
|
1002
|
-
"-4": "FAILED" /* FAILED */
|
|
1003
|
-
};
|
|
1004
|
-
return statusMap[resultCode] || "FAILED" /* FAILED */;
|
|
1005
|
-
}
|
|
1006
|
-
mapIWINVTemplateStatus(status) {
|
|
1007
|
-
const statusMap = {
|
|
1008
|
-
"R": "PENDING" /* PENDING */,
|
|
1009
|
-
// Request
|
|
1010
|
-
"A": "APPROVED" /* APPROVED */,
|
|
1011
|
-
// Approved
|
|
1012
|
-
"C": "REJECTED" /* REJECTED */,
|
|
1013
|
-
// Cancelled/Rejected
|
|
1014
|
-
"S": "PENDING" /* PENDING */
|
|
1015
|
-
// Standby
|
|
1016
|
-
};
|
|
1017
|
-
return statusMap[status] || "DRAFT" /* DRAFT */;
|
|
1018
|
-
}
|
|
1019
|
-
extractErrorCode(providerError) {
|
|
1020
|
-
const error = providerError;
|
|
1021
|
-
return error.result_code || error.error_code || "UNKNOWN_ERROR";
|
|
1022
|
-
}
|
|
1023
|
-
extractErrorMessage(providerError) {
|
|
1024
|
-
const error = providerError;
|
|
1025
|
-
return error.message || error.error_message || "Unknown error occurred";
|
|
1026
|
-
}
|
|
1027
|
-
extractErrorDetails(providerError) {
|
|
1028
|
-
const error = providerError;
|
|
1029
|
-
return {
|
|
1030
|
-
resultCode: error.result_code,
|
|
1031
|
-
originalResponse: providerError
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
1035
|
-
var AligoResponseAdapter = class extends BaseResponseAdapter {
|
|
1036
|
-
transformMessageResponse(providerResponse) {
|
|
1037
|
-
const response = providerResponse;
|
|
1038
|
-
return {
|
|
1039
|
-
messageId: response.msg_id || response.mid,
|
|
1040
|
-
status: this.mapAligoMessageStatus(response.result_code),
|
|
1041
|
-
sentAt: this.parseDate(response.send_time),
|
|
1042
|
-
error: response.result_code !== "1" ? this.transformError(providerResponse) : void 0
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
transformTemplateResponse(providerResponse) {
|
|
1046
|
-
const response = providerResponse;
|
|
1047
|
-
return {
|
|
1048
|
-
templateId: response.template_code,
|
|
1049
|
-
providerTemplateCode: response.template_code,
|
|
1050
|
-
status: this.mapAligoTemplateStatus(response.inspect_status),
|
|
1051
|
-
message: response.comment
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
|
-
mapAligoMessageStatus(resultCode) {
|
|
1055
|
-
const statusMap = {
|
|
1056
|
-
"1": "SENT" /* SENT */,
|
|
1057
|
-
"0": "FAILED" /* FAILED */,
|
|
1058
|
-
"-1": "FAILED" /* FAILED */,
|
|
1059
|
-
"-101": "FAILED" /* FAILED */,
|
|
1060
|
-
"-102": "FAILED" /* FAILED */
|
|
1061
|
-
};
|
|
1062
|
-
return statusMap[resultCode] || "FAILED" /* FAILED */;
|
|
1063
|
-
}
|
|
1064
|
-
mapAligoTemplateStatus(inspectStatus) {
|
|
1065
|
-
const statusMap = {
|
|
1066
|
-
"REG": "PENDING" /* PENDING */,
|
|
1067
|
-
// Registered
|
|
1068
|
-
"REQ": "PENDING" /* PENDING */,
|
|
1069
|
-
// Request
|
|
1070
|
-
"APR": "APPROVED" /* APPROVED */,
|
|
1071
|
-
// Approved
|
|
1072
|
-
"REJ": "REJECTED" /* REJECTED */,
|
|
1073
|
-
// Rejected
|
|
1074
|
-
"STOP": "DISABLED" /* DISABLED */
|
|
1075
|
-
// Stopped
|
|
1076
|
-
};
|
|
1077
|
-
return statusMap[inspectStatus] || "DRAFT" /* DRAFT */;
|
|
1078
|
-
}
|
|
1079
|
-
extractErrorCode(providerError) {
|
|
1080
|
-
const error = providerError;
|
|
1081
|
-
return error.result_code || error.code || "UNKNOWN_ERROR";
|
|
1082
|
-
}
|
|
1083
|
-
extractErrorMessage(providerError) {
|
|
1084
|
-
const error = providerError;
|
|
1085
|
-
return error.message || error.error || "Unknown error occurred";
|
|
1086
|
-
}
|
|
1087
|
-
extractErrorDetails(providerError) {
|
|
1088
|
-
const error = providerError;
|
|
1089
|
-
return {
|
|
1090
|
-
resultCode: error.result_code,
|
|
1091
|
-
inspectStatus: error.inspect_status,
|
|
1092
|
-
originalResponse: providerError
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
var KakaoResponseAdapter = class extends BaseResponseAdapter {
|
|
1097
|
-
transformMessageResponse(providerResponse) {
|
|
1098
|
-
const response = providerResponse;
|
|
1099
|
-
return {
|
|
1100
|
-
messageId: response.message_id,
|
|
1101
|
-
status: this.mapKakaoMessageStatus(response.result_code),
|
|
1102
|
-
sentAt: this.parseDate(response.sent_time),
|
|
1103
|
-
error: response.result_code !== 0 ? this.transformError(providerResponse) : void 0
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
transformTemplateResponse(providerResponse) {
|
|
1107
|
-
const response = providerResponse;
|
|
1108
|
-
return {
|
|
1109
|
-
templateId: response.template_id,
|
|
1110
|
-
providerTemplateCode: response.template_code,
|
|
1111
|
-
status: this.mapKakaoTemplateStatus(response.status),
|
|
1112
|
-
message: response.comments
|
|
1113
|
-
};
|
|
1114
|
-
}
|
|
1115
|
-
mapKakaoMessageStatus(resultCode) {
|
|
1116
|
-
const statusMap = {
|
|
1117
|
-
0: "SENT" /* SENT */,
|
|
1118
|
-
[-1]: "FAILED" /* FAILED */,
|
|
1119
|
-
[-2]: "FAILED" /* FAILED */,
|
|
1120
|
-
[-3]: "FAILED" /* FAILED */,
|
|
1121
|
-
[-999]: "FAILED" /* FAILED */
|
|
1122
|
-
};
|
|
1123
|
-
return statusMap[resultCode] || "FAILED" /* FAILED */;
|
|
1124
|
-
}
|
|
1125
|
-
mapKakaoTemplateStatus(status) {
|
|
1126
|
-
const statusMap = {
|
|
1127
|
-
"TSC01": "PENDING" /* PENDING */,
|
|
1128
|
-
// Under review
|
|
1129
|
-
"TSC02": "APPROVED" /* APPROVED */,
|
|
1130
|
-
// Approved
|
|
1131
|
-
"TSC03": "REJECTED" /* REJECTED */,
|
|
1132
|
-
// Rejected
|
|
1133
|
-
"TSC04": "DISABLED" /* DISABLED */
|
|
1134
|
-
// Disabled
|
|
1135
|
-
};
|
|
1136
|
-
return statusMap[status] || "DRAFT" /* DRAFT */;
|
|
1137
|
-
}
|
|
1138
|
-
extractErrorCode(providerError) {
|
|
1139
|
-
const error = providerError;
|
|
1140
|
-
return String(error.result_code || error.error_code || "UNKNOWN_ERROR");
|
|
1141
|
-
}
|
|
1142
|
-
extractErrorMessage(providerError) {
|
|
1143
|
-
const error = providerError;
|
|
1144
|
-
return error.message || error.error_message || "Unknown error occurred";
|
|
1145
|
-
}
|
|
1146
|
-
extractErrorDetails(providerError) {
|
|
1147
|
-
const error = providerError;
|
|
1148
|
-
return {
|
|
1149
|
-
resultCode: error.result_code,
|
|
1150
|
-
originalResponse: providerError
|
|
1151
|
-
};
|
|
1152
|
-
}
|
|
1153
|
-
};
|
|
1154
|
-
var NHNResponseAdapter = class extends BaseResponseAdapter {
|
|
1155
|
-
transformMessageResponse(providerResponse) {
|
|
1156
|
-
const response = providerResponse;
|
|
1157
|
-
return {
|
|
1158
|
-
messageId: response.requestId,
|
|
1159
|
-
status: this.mapNHNMessageStatus(response.statusCode),
|
|
1160
|
-
sentAt: this.parseDate(response.statusDateTime),
|
|
1161
|
-
error: response.statusCode !== "SSS" ? this.transformError(providerResponse) : void 0
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
transformTemplateResponse(providerResponse) {
|
|
1165
|
-
const response = providerResponse;
|
|
1166
|
-
return {
|
|
1167
|
-
templateId: response.templateId,
|
|
1168
|
-
providerTemplateCode: response.templateId,
|
|
1169
|
-
status: this.mapNHNTemplateStatus(response.templateStatus),
|
|
1170
|
-
message: response.templateStatusName
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
mapNHNMessageStatus(statusCode) {
|
|
1174
|
-
const statusMap = {
|
|
1175
|
-
"SSS": "SENT" /* SENT */,
|
|
1176
|
-
// Success
|
|
1177
|
-
"RDY": "QUEUED" /* QUEUED */,
|
|
1178
|
-
// Ready
|
|
1179
|
-
"PRG": "SENDING" /* SENDING */,
|
|
1180
|
-
// Progress
|
|
1181
|
-
"CPL": "DELIVERED" /* DELIVERED */,
|
|
1182
|
-
// Complete
|
|
1183
|
-
"FAL": "FAILED" /* FAILED */,
|
|
1184
|
-
// Failed
|
|
1185
|
-
"CAL": "CANCELLED" /* CANCELLED */
|
|
1186
|
-
// Cancelled
|
|
1187
|
-
};
|
|
1188
|
-
return statusMap[statusCode] || "FAILED" /* FAILED */;
|
|
1189
|
-
}
|
|
1190
|
-
mapNHNTemplateStatus(templateStatus) {
|
|
1191
|
-
const statusMap = {
|
|
1192
|
-
"WAITING": "PENDING" /* PENDING */,
|
|
1193
|
-
"APPROVED": "APPROVED" /* APPROVED */,
|
|
1194
|
-
"REJECTED": "REJECTED" /* REJECTED */,
|
|
1195
|
-
"DISABLED": "DISABLED" /* DISABLED */
|
|
1196
|
-
};
|
|
1197
|
-
return statusMap[templateStatus] || "DRAFT" /* DRAFT */;
|
|
1198
|
-
}
|
|
1199
|
-
extractErrorCode(providerError) {
|
|
1200
|
-
const error = providerError;
|
|
1201
|
-
return error.statusCode || error.errorCode || "UNKNOWN_ERROR";
|
|
1202
|
-
}
|
|
1203
|
-
extractErrorMessage(providerError) {
|
|
1204
|
-
const error = providerError;
|
|
1205
|
-
return error.statusMessage || error.errorMessage || "Unknown error occurred";
|
|
1206
|
-
}
|
|
1207
|
-
extractErrorDetails(providerError) {
|
|
1208
|
-
const error = providerError;
|
|
1209
|
-
return {
|
|
1210
|
-
statusCode: error.statusCode,
|
|
1211
|
-
statusMessage: error.statusMessage,
|
|
1212
|
-
originalResponse: providerError
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
|
-
};
|
|
1216
|
-
var ResponseAdapterFactory = class {
|
|
1217
|
-
static adapters = /* @__PURE__ */ new Map([
|
|
1218
|
-
["iwinv", IWINVResponseAdapter],
|
|
1219
|
-
["aligo", AligoResponseAdapter],
|
|
1220
|
-
["kakao", KakaoResponseAdapter],
|
|
1221
|
-
["nhn", NHNResponseAdapter]
|
|
1222
|
-
]);
|
|
1223
|
-
static create(providerId) {
|
|
1224
|
-
const AdapterClass = this.adapters.get(providerId.toLowerCase());
|
|
1225
|
-
if (!AdapterClass) {
|
|
1226
|
-
throw new Error(`No response adapter found for provider: ${providerId}`);
|
|
1227
|
-
}
|
|
1228
|
-
return new AdapterClass();
|
|
1229
|
-
}
|
|
1230
|
-
static register(providerId, adapterClass) {
|
|
1231
|
-
this.adapters.set(providerId.toLowerCase(), adapterClass);
|
|
1232
|
-
}
|
|
1233
|
-
};
|
|
1234
|
-
|
|
1235
|
-
// src/services/provider.manager.ts
|
|
1236
|
-
var ProviderManager = class {
|
|
1237
|
-
providers = /* @__PURE__ */ new Map();
|
|
1238
|
-
defaultProvider;
|
|
1239
|
-
registerProvider(provider) {
|
|
1240
|
-
this.providers.set(provider.id, provider);
|
|
1241
|
-
if (!this.defaultProvider) {
|
|
1242
|
-
this.defaultProvider = provider.id;
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
unregisterProvider(providerId) {
|
|
1246
|
-
this.providers.delete(providerId);
|
|
1247
|
-
if (this.defaultProvider === providerId) {
|
|
1248
|
-
const remaining = Array.from(this.providers.keys());
|
|
1249
|
-
this.defaultProvider = remaining.length > 0 ? remaining[0] : void 0;
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
getProvider(providerId) {
|
|
1253
|
-
const id = providerId || this.defaultProvider;
|
|
1254
|
-
return id ? this.providers.get(id) || null : null;
|
|
1255
|
-
}
|
|
1256
|
-
listProviders() {
|
|
1257
|
-
return Array.from(this.providers.values());
|
|
1258
|
-
}
|
|
1259
|
-
setDefaultProvider(providerId) {
|
|
1260
|
-
if (!this.providers.has(providerId)) {
|
|
1261
|
-
throw new Error(`Provider ${providerId} not found`);
|
|
1262
|
-
}
|
|
1263
|
-
this.defaultProvider = providerId;
|
|
1264
|
-
}
|
|
1265
|
-
async healthCheckAll() {
|
|
1266
|
-
const results = {};
|
|
1267
|
-
for (const [id, provider] of this.providers.entries()) {
|
|
1268
|
-
try {
|
|
1269
|
-
const health = await provider.healthCheck();
|
|
1270
|
-
results[id] = health.healthy;
|
|
1271
|
-
} catch (error) {
|
|
1272
|
-
results[id] = false;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
return results;
|
|
1276
|
-
}
|
|
1277
|
-
getProvidersForChannel(channel) {
|
|
1278
|
-
return Array.from(this.providers.values()).filter((provider) => {
|
|
1279
|
-
const providerWithChannels = provider;
|
|
1280
|
-
return providerWithChannels.supportedChannels?.includes(channel);
|
|
1281
|
-
});
|
|
1282
|
-
}
|
|
1283
|
-
};
|
|
1284
|
-
|
|
1285
|
-
// src/iwinv/contracts/messaging.contract.ts
|
|
1286
|
-
var IWINVMessagingContract = class {
|
|
1287
|
-
constructor(config) {
|
|
1288
|
-
this.config = config;
|
|
1289
|
-
}
|
|
1290
|
-
async send(message) {
|
|
1291
|
-
try {
|
|
1292
|
-
const response = await fetch(`${this.config.baseUrl}/send`, {
|
|
1293
|
-
method: "POST",
|
|
1294
|
-
headers: {
|
|
1295
|
-
"Content-Type": "application/json",
|
|
1296
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1297
|
-
},
|
|
1298
|
-
body: JSON.stringify({
|
|
1299
|
-
templateCode: message.templateCode,
|
|
1300
|
-
phone: message.phoneNumber,
|
|
1301
|
-
variables: message.variables,
|
|
1302
|
-
senderNumber: message.senderNumber,
|
|
1303
|
-
...message.options
|
|
1304
|
-
})
|
|
1305
|
-
});
|
|
1306
|
-
const result = await response.json();
|
|
1307
|
-
if (!response.ok) {
|
|
1308
|
-
return {
|
|
1309
|
-
messageId: `failed_${Date.now()}`,
|
|
1310
|
-
status: "FAILED" /* FAILED */,
|
|
1311
|
-
error: {
|
|
1312
|
-
code: result.code || "SEND_FAILED",
|
|
1313
|
-
message: result.message || "Failed to send message"
|
|
1314
|
-
}
|
|
1315
|
-
};
|
|
1316
|
-
}
|
|
1317
|
-
return {
|
|
1318
|
-
messageId: result.messageId || `msg_${Date.now()}`,
|
|
1319
|
-
status: "SENT" /* SENT */,
|
|
1320
|
-
sentAt: /* @__PURE__ */ new Date()
|
|
1321
|
-
};
|
|
1322
|
-
} catch (error) {
|
|
1323
|
-
return {
|
|
1324
|
-
messageId: `error_${Date.now()}`,
|
|
1325
|
-
status: "FAILED" /* FAILED */,
|
|
1326
|
-
error: {
|
|
1327
|
-
code: "NETWORK_ERROR",
|
|
1328
|
-
message: error instanceof Error ? error.message : "Network error occurred"
|
|
1329
|
-
}
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
async sendBulk(messages) {
|
|
1334
|
-
const results = [];
|
|
1335
|
-
for (const message of messages) {
|
|
1336
|
-
const result = await this.send(message);
|
|
1337
|
-
results.push(result);
|
|
1338
|
-
}
|
|
1339
|
-
const sent = results.filter((r) => r.status === "SENT" /* SENT */).length;
|
|
1340
|
-
const failed = results.filter((r) => r.status === "FAILED" /* FAILED */).length;
|
|
1341
|
-
return {
|
|
1342
|
-
requestId: `bulk_${Date.now()}`,
|
|
1343
|
-
results,
|
|
1344
|
-
summary: {
|
|
1345
|
-
total: messages.length,
|
|
1346
|
-
sent,
|
|
1347
|
-
failed
|
|
1348
|
-
}
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
async schedule(message, scheduledAt) {
|
|
1352
|
-
return {
|
|
1353
|
-
scheduleId: `schedule_${Date.now()}`,
|
|
1354
|
-
messageId: `msg_${Date.now()}`,
|
|
1355
|
-
scheduledAt,
|
|
1356
|
-
status: "scheduled"
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
|
-
async cancel(messageId) {
|
|
1360
|
-
const response = await fetch(`${this.config.baseUrl}/cancel`, {
|
|
1361
|
-
method: "POST",
|
|
1362
|
-
headers: {
|
|
1363
|
-
"Content-Type": "application/json",
|
|
1364
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1365
|
-
},
|
|
1366
|
-
body: JSON.stringify({ messageId })
|
|
1367
|
-
});
|
|
1368
|
-
if (!response.ok) {
|
|
1369
|
-
const result = await response.json();
|
|
1370
|
-
throw new Error(`Failed to cancel message: ${result.message}`);
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
async getStatus(messageId) {
|
|
1374
|
-
try {
|
|
1375
|
-
const response = await fetch(`${this.config.baseUrl}/status`, {
|
|
1376
|
-
method: "POST",
|
|
1377
|
-
headers: {
|
|
1378
|
-
"Content-Type": "application/json",
|
|
1379
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1380
|
-
},
|
|
1381
|
-
body: JSON.stringify({ messageId })
|
|
1382
|
-
});
|
|
1383
|
-
const result = await response.json();
|
|
1384
|
-
if (!response.ok) {
|
|
1385
|
-
return "FAILED" /* FAILED */;
|
|
1386
|
-
}
|
|
1387
|
-
switch (result.statusCode) {
|
|
1388
|
-
case "OK":
|
|
1389
|
-
return "DELIVERED" /* DELIVERED */;
|
|
1390
|
-
case "PENDING":
|
|
1391
|
-
return "SENDING" /* SENDING */;
|
|
1392
|
-
case "FAILED":
|
|
1393
|
-
return "FAILED" /* FAILED */;
|
|
1394
|
-
default:
|
|
1395
|
-
return "SENT" /* SENT */;
|
|
1396
|
-
}
|
|
1397
|
-
} catch (error) {
|
|
1398
|
-
return "FAILED" /* FAILED */;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
};
|
|
1402
|
-
|
|
1403
|
-
// src/iwinv/contracts/template.contract.ts
|
|
1404
|
-
var IWINVTemplateContract = class {
|
|
1405
|
-
constructor(config) {
|
|
1406
|
-
this.config = config;
|
|
1407
|
-
}
|
|
1408
|
-
async create(template) {
|
|
1409
|
-
try {
|
|
1410
|
-
const response = await fetch(`${this.config.baseUrl}/template/create`, {
|
|
1411
|
-
method: "POST",
|
|
1412
|
-
headers: {
|
|
1413
|
-
"Content-Type": "application/json",
|
|
1414
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1415
|
-
},
|
|
1416
|
-
body: JSON.stringify({
|
|
1417
|
-
templateName: template.name,
|
|
1418
|
-
templateContent: template.content,
|
|
1419
|
-
templateCategory: template.category,
|
|
1420
|
-
templateVariables: template.variables,
|
|
1421
|
-
templateButtons: template.buttons
|
|
1422
|
-
})
|
|
1423
|
-
});
|
|
1424
|
-
const result = await response.json();
|
|
1425
|
-
if (!response.ok) {
|
|
1426
|
-
throw new Error(`Template creation failed: ${result.message}`);
|
|
1427
|
-
}
|
|
1428
|
-
return {
|
|
1429
|
-
templateId: result.templateId || `tpl_${Date.now()}`,
|
|
1430
|
-
providerTemplateCode: result.templateCode || template.name,
|
|
1431
|
-
status: "PENDING" /* PENDING */,
|
|
1432
|
-
message: result.message || "Template created successfully"
|
|
1433
|
-
};
|
|
1434
|
-
} catch (error) {
|
|
1435
|
-
throw new Error(`Failed to create template: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
async update(templateId, template) {
|
|
1439
|
-
try {
|
|
1440
|
-
const response = await fetch(`${this.config.baseUrl}/template/modify`, {
|
|
1441
|
-
method: "POST",
|
|
1442
|
-
headers: {
|
|
1443
|
-
"Content-Type": "application/json",
|
|
1444
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1445
|
-
},
|
|
1446
|
-
body: JSON.stringify({
|
|
1447
|
-
templateCode: templateId,
|
|
1448
|
-
templateName: template.name,
|
|
1449
|
-
templateContent: template.content,
|
|
1450
|
-
templateButtons: template.buttons
|
|
1451
|
-
})
|
|
1452
|
-
});
|
|
1453
|
-
const result = await response.json();
|
|
1454
|
-
if (!response.ok) {
|
|
1455
|
-
throw new Error(`Template update failed: ${result.message}`);
|
|
1456
|
-
}
|
|
1457
|
-
return {
|
|
1458
|
-
templateId,
|
|
1459
|
-
status: "PENDING" /* PENDING */,
|
|
1460
|
-
message: result.message || "Template updated successfully"
|
|
1461
|
-
};
|
|
1462
|
-
} catch (error) {
|
|
1463
|
-
throw new Error(`Failed to update template: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
async delete(templateId) {
|
|
1467
|
-
try {
|
|
1468
|
-
const response = await fetch(`${this.config.baseUrl}/template/delete`, {
|
|
1469
|
-
method: "POST",
|
|
1470
|
-
headers: {
|
|
1471
|
-
"Content-Type": "application/json",
|
|
1472
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1473
|
-
},
|
|
1474
|
-
body: JSON.stringify({
|
|
1475
|
-
templateCode: templateId
|
|
1476
|
-
})
|
|
1477
|
-
});
|
|
1478
|
-
const result = await response.json();
|
|
1479
|
-
if (!response.ok) {
|
|
1480
|
-
throw new Error(`Template deletion failed: ${result.message}`);
|
|
1481
|
-
}
|
|
1482
|
-
} catch (error) {
|
|
1483
|
-
throw new Error(`Failed to delete template: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
async get(templateId) {
|
|
1487
|
-
const templates = await this.list({ templateCode: templateId });
|
|
1488
|
-
const template = templates.find((t) => t.code === templateId);
|
|
1489
|
-
if (!template) {
|
|
1490
|
-
throw new Error(`Template ${templateId} not found`);
|
|
1491
|
-
}
|
|
1492
|
-
return template;
|
|
1493
|
-
}
|
|
1494
|
-
async list(filters) {
|
|
1495
|
-
try {
|
|
1496
|
-
const response = await fetch(`${this.config.baseUrl}/template/list`, {
|
|
1497
|
-
method: "POST",
|
|
1498
|
-
headers: {
|
|
1499
|
-
"Content-Type": "application/json",
|
|
1500
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1501
|
-
},
|
|
1502
|
-
body: JSON.stringify({
|
|
1503
|
-
page: 1,
|
|
1504
|
-
size: 100,
|
|
1505
|
-
...filters
|
|
1506
|
-
})
|
|
1507
|
-
});
|
|
1508
|
-
const result = await response.json();
|
|
1509
|
-
if (!response.ok) {
|
|
1510
|
-
throw new Error(`Failed to list templates: ${result.message}`);
|
|
1511
|
-
}
|
|
1512
|
-
return (result.list || []).map((template) => ({
|
|
1513
|
-
id: template.templateId || template.templateCode,
|
|
1514
|
-
code: template.templateCode,
|
|
1515
|
-
name: template.templateName,
|
|
1516
|
-
content: template.templateContent,
|
|
1517
|
-
status: this.mapIWINVStatus(template.status || template.templateStatus),
|
|
1518
|
-
createdAt: template.createDate ? new Date(template.createDate) : /* @__PURE__ */ new Date(),
|
|
1519
|
-
updatedAt: template.updateDate ? new Date(template.updateDate) : /* @__PURE__ */ new Date(),
|
|
1520
|
-
approvedAt: template.approvedAt ? new Date(template.approvedAt) : void 0,
|
|
1521
|
-
rejectedAt: template.rejectedAt ? new Date(template.rejectedAt) : void 0,
|
|
1522
|
-
rejectionReason: template.rejectionReason
|
|
1523
|
-
}));
|
|
1524
|
-
} catch (error) {
|
|
1525
|
-
throw new Error(`Failed to list templates: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
async sync() {
|
|
1529
|
-
try {
|
|
1530
|
-
const templates = await this.list();
|
|
1531
|
-
return {
|
|
1532
|
-
synced: templates.length,
|
|
1533
|
-
created: 0,
|
|
1534
|
-
updated: 0,
|
|
1535
|
-
deleted: 0,
|
|
1536
|
-
errors: []
|
|
1537
|
-
};
|
|
1538
|
-
} catch (error) {
|
|
1539
|
-
return {
|
|
1540
|
-
synced: 0,
|
|
1541
|
-
created: 0,
|
|
1542
|
-
updated: 0,
|
|
1543
|
-
deleted: 0,
|
|
1544
|
-
errors: [{
|
|
1545
|
-
templateId: "unknown",
|
|
1546
|
-
error: error instanceof Error ? error.message : "Sync failed"
|
|
1547
|
-
}]
|
|
1548
|
-
};
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
mapIWINVStatus(status) {
|
|
1552
|
-
switch (status) {
|
|
1553
|
-
case "Y":
|
|
1554
|
-
return "APPROVED" /* APPROVED */;
|
|
1555
|
-
case "I":
|
|
1556
|
-
return "PENDING" /* PENDING */;
|
|
1557
|
-
case "R":
|
|
1558
|
-
return "REJECTED" /* REJECTED */;
|
|
1559
|
-
case "D":
|
|
1560
|
-
return "DISABLED" /* DISABLED */;
|
|
1561
|
-
default:
|
|
1562
|
-
return "DRAFT" /* DRAFT */;
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
};
|
|
1566
|
-
|
|
1567
|
-
// src/iwinv/contracts/channel.contract.ts
|
|
1568
|
-
var IWINVChannelContract = class {
|
|
1569
|
-
constructor(config) {
|
|
1570
|
-
this.config = config;
|
|
1571
|
-
}
|
|
1572
|
-
async register(channel) {
|
|
1573
|
-
return {
|
|
1574
|
-
id: `channel_${Date.now()}`,
|
|
1575
|
-
name: channel.name,
|
|
1576
|
-
profileKey: channel.profileKey,
|
|
1577
|
-
status: "pending",
|
|
1578
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1579
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
async list() {
|
|
1583
|
-
return [
|
|
1584
|
-
{
|
|
1585
|
-
id: "iwinv-default",
|
|
1586
|
-
name: "IWINV Default Channel",
|
|
1587
|
-
profileKey: "default",
|
|
1588
|
-
status: "active",
|
|
1589
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1590
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1591
|
-
}
|
|
1592
|
-
];
|
|
1593
|
-
}
|
|
1594
|
-
async addSenderNumber(channelId, number) {
|
|
1595
|
-
return {
|
|
1596
|
-
id: `sender_${Date.now()}`,
|
|
1597
|
-
channelId,
|
|
1598
|
-
phoneNumber: number,
|
|
1599
|
-
isVerified: false,
|
|
1600
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
1601
|
-
};
|
|
1602
|
-
}
|
|
1603
|
-
async verifySenderNumber(number, verificationCode) {
|
|
1604
|
-
return true;
|
|
1605
|
-
}
|
|
1606
|
-
};
|
|
1607
|
-
|
|
1608
|
-
// src/iwinv/contracts/analytics.contract.ts
|
|
1609
|
-
var IWINVAnalyticsContract = class {
|
|
1610
|
-
constructor(config) {
|
|
1611
|
-
this.config = config;
|
|
1612
|
-
}
|
|
1613
|
-
async getUsage(period) {
|
|
1614
|
-
try {
|
|
1615
|
-
const response = await fetch(`${this.config.baseUrl}/history/list`, {
|
|
1616
|
-
method: "POST",
|
|
1617
|
-
headers: {
|
|
1618
|
-
"Content-Type": "application/json",
|
|
1619
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1620
|
-
},
|
|
1621
|
-
body: JSON.stringify({
|
|
1622
|
-
startDate: period.from.toISOString(),
|
|
1623
|
-
endDate: period.to.toISOString(),
|
|
1624
|
-
page: 1,
|
|
1625
|
-
size: 1e3
|
|
1626
|
-
})
|
|
1627
|
-
});
|
|
1628
|
-
const result = await response.json();
|
|
1629
|
-
if (!response.ok) {
|
|
1630
|
-
throw new Error(`Failed to get usage stats: ${result.message}`);
|
|
1631
|
-
}
|
|
1632
|
-
const messages = result.list || [];
|
|
1633
|
-
const totalMessages = messages.length;
|
|
1634
|
-
const deliveredMessages = messages.filter((msg) => msg.statusCode === "OK").length;
|
|
1635
|
-
const failedMessages = messages.filter((msg) => msg.statusCode === "FAILED").length;
|
|
1636
|
-
const sentMessages = totalMessages - failedMessages;
|
|
1637
|
-
return {
|
|
1638
|
-
period,
|
|
1639
|
-
totalMessages,
|
|
1640
|
-
sentMessages,
|
|
1641
|
-
deliveredMessages,
|
|
1642
|
-
failedMessages,
|
|
1643
|
-
deliveryRate: totalMessages > 0 ? deliveredMessages / totalMessages * 100 : 0,
|
|
1644
|
-
failureRate: totalMessages > 0 ? failedMessages / totalMessages * 100 : 0,
|
|
1645
|
-
breakdown: {
|
|
1646
|
-
byTemplate: this.groupByTemplate(messages),
|
|
1647
|
-
byDay: this.groupByDay(messages, period),
|
|
1648
|
-
byHour: this.groupByHour(messages)
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
return {
|
|
1653
|
-
period,
|
|
1654
|
-
totalMessages: 0,
|
|
1655
|
-
sentMessages: 0,
|
|
1656
|
-
deliveredMessages: 0,
|
|
1657
|
-
failedMessages: 0,
|
|
1658
|
-
deliveryRate: 0,
|
|
1659
|
-
failureRate: 0,
|
|
1660
|
-
breakdown: {
|
|
1661
|
-
byTemplate: {},
|
|
1662
|
-
byDay: {},
|
|
1663
|
-
byHour: {}
|
|
1664
|
-
}
|
|
1665
|
-
};
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
async getTemplateStats(templateId, period) {
|
|
1669
|
-
try {
|
|
1670
|
-
const usage = await this.getUsage(period);
|
|
1671
|
-
const templateMessages = usage.breakdown.byTemplate[templateId] || 0;
|
|
1672
|
-
return {
|
|
1673
|
-
templateId,
|
|
1674
|
-
period,
|
|
1675
|
-
totalSent: templateMessages,
|
|
1676
|
-
delivered: Math.round(templateMessages * (usage.deliveryRate / 100)),
|
|
1677
|
-
failed: Math.round(templateMessages * (usage.failureRate / 100)),
|
|
1678
|
-
deliveryRate: usage.deliveryRate,
|
|
1679
|
-
averageDeliveryTime: 30
|
|
1680
|
-
// Mock average delivery time in seconds
|
|
1681
|
-
};
|
|
1682
|
-
} catch (error) {
|
|
1683
|
-
return {
|
|
1684
|
-
templateId,
|
|
1685
|
-
period,
|
|
1686
|
-
totalSent: 0,
|
|
1687
|
-
delivered: 0,
|
|
1688
|
-
failed: 0,
|
|
1689
|
-
deliveryRate: 0,
|
|
1690
|
-
averageDeliveryTime: 0
|
|
1691
|
-
};
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
async getDeliveryReport(messageId) {
|
|
1695
|
-
try {
|
|
1696
|
-
const response = await fetch(`${this.config.baseUrl}/history/detail`, {
|
|
1697
|
-
method: "POST",
|
|
1698
|
-
headers: {
|
|
1699
|
-
"Content-Type": "application/json",
|
|
1700
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1701
|
-
},
|
|
1702
|
-
body: JSON.stringify({
|
|
1703
|
-
messageId: parseInt(messageId) || 0
|
|
1704
|
-
})
|
|
1705
|
-
});
|
|
1706
|
-
const result = await response.json();
|
|
1707
|
-
if (!response.ok) {
|
|
1708
|
-
throw new Error(`Failed to get delivery report: ${result.message}`);
|
|
1709
|
-
}
|
|
1710
|
-
return {
|
|
1711
|
-
messageId,
|
|
1712
|
-
phoneNumber: result.phone || "unknown",
|
|
1713
|
-
templateCode: result.templateCode || "unknown",
|
|
1714
|
-
status: this.mapStatus(result.statusCode),
|
|
1715
|
-
sentAt: result.sendDate ? new Date(result.sendDate) : void 0,
|
|
1716
|
-
deliveredAt: result.receiveDate ? new Date(result.receiveDate) : void 0,
|
|
1717
|
-
failedAt: result.statusCode === "FAILED" ? new Date(result.sendDate) : void 0,
|
|
1718
|
-
clickedAt: result.clickedAt ? new Date(result.clickedAt) : void 0,
|
|
1719
|
-
error: result.statusCode !== "OK" ? {
|
|
1720
|
-
code: result.statusCode,
|
|
1721
|
-
message: result.statusCodeName
|
|
1722
|
-
} : void 0,
|
|
1723
|
-
attempts: [
|
|
1724
|
-
{
|
|
1725
|
-
attemptNumber: 1,
|
|
1726
|
-
attemptedAt: new Date(result.requestDate),
|
|
1727
|
-
status: this.mapStatus(result.statusCode),
|
|
1728
|
-
error: result.statusCode !== "OK" ? {
|
|
1729
|
-
code: result.statusCode,
|
|
1730
|
-
message: result.statusCodeName
|
|
1731
|
-
} : void 0
|
|
1732
|
-
}
|
|
1733
|
-
]
|
|
1734
|
-
};
|
|
1735
|
-
} catch (error) {
|
|
1736
|
-
return {
|
|
1737
|
-
messageId,
|
|
1738
|
-
phoneNumber: "unknown",
|
|
1739
|
-
templateCode: "unknown",
|
|
1740
|
-
status: "FAILED",
|
|
1741
|
-
error: {
|
|
1742
|
-
code: "API_ERROR",
|
|
1743
|
-
message: error instanceof Error ? error.message : "Unknown error"
|
|
1744
|
-
},
|
|
1745
|
-
attempts: []
|
|
1746
|
-
};
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
groupByTemplate(messages) {
|
|
1750
|
-
const groups = {};
|
|
1751
|
-
messages.forEach((msg) => {
|
|
1752
|
-
const template = msg.templateCode || "unknown";
|
|
1753
|
-
groups[template] = (groups[template] || 0) + 1;
|
|
1754
|
-
});
|
|
1755
|
-
return groups;
|
|
1756
|
-
}
|
|
1757
|
-
groupByDay(messages, period) {
|
|
1758
|
-
const groups = {};
|
|
1759
|
-
const current = new Date(period.from);
|
|
1760
|
-
while (current <= period.to) {
|
|
1761
|
-
const dateKey = current.toISOString().split("T")[0];
|
|
1762
|
-
groups[dateKey] = 0;
|
|
1763
|
-
current.setDate(current.getDate() + 1);
|
|
1764
|
-
}
|
|
1765
|
-
messages.forEach((msg) => {
|
|
1766
|
-
if (msg.requestDate) {
|
|
1767
|
-
const dateKey = new Date(msg.requestDate).toISOString().split("T")[0];
|
|
1768
|
-
if (groups.hasOwnProperty(dateKey)) {
|
|
1769
|
-
groups[dateKey]++;
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
});
|
|
1773
|
-
return groups;
|
|
1774
|
-
}
|
|
1775
|
-
groupByHour(messages) {
|
|
1776
|
-
const groups = {};
|
|
1777
|
-
for (let i = 0; i < 24; i++) {
|
|
1778
|
-
groups[i.toString()] = 0;
|
|
1779
|
-
}
|
|
1780
|
-
messages.forEach((msg) => {
|
|
1781
|
-
if (msg.requestDate) {
|
|
1782
|
-
const hour = new Date(msg.requestDate).getHours();
|
|
1783
|
-
groups[hour.toString()]++;
|
|
1784
|
-
}
|
|
1785
|
-
});
|
|
1786
|
-
return groups;
|
|
1787
|
-
}
|
|
1788
|
-
mapStatus(statusCode) {
|
|
1789
|
-
switch (statusCode) {
|
|
1790
|
-
case "OK":
|
|
1791
|
-
return "DELIVERED";
|
|
1792
|
-
case "PENDING":
|
|
1793
|
-
return "SENDING";
|
|
1794
|
-
case "FAILED":
|
|
1795
|
-
return "FAILED";
|
|
1796
|
-
default:
|
|
1797
|
-
return "SENT";
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
};
|
|
1801
|
-
|
|
1802
|
-
// src/iwinv/contracts/account.contract.ts
|
|
1803
|
-
var IWINVAccountContract = class {
|
|
1804
|
-
constructor(config) {
|
|
1805
|
-
this.config = config;
|
|
1806
|
-
}
|
|
1807
|
-
async getBalance() {
|
|
1808
|
-
try {
|
|
1809
|
-
const response = await fetch(`${this.config.baseUrl}/balance`, {
|
|
1810
|
-
method: "GET",
|
|
1811
|
-
headers: {
|
|
1812
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
1813
|
-
}
|
|
1814
|
-
});
|
|
1815
|
-
const result = await response.json();
|
|
1816
|
-
if (!response.ok) {
|
|
1817
|
-
throw new Error(`Failed to get balance: ${result.message}`);
|
|
1818
|
-
}
|
|
1819
|
-
return {
|
|
1820
|
-
current: Number(result.balance) || 0,
|
|
1821
|
-
currency: "KRW",
|
|
1822
|
-
lastUpdated: /* @__PURE__ */ new Date(),
|
|
1823
|
-
threshold: Number(result.threshold) || void 0
|
|
1824
|
-
};
|
|
1825
|
-
} catch (error) {
|
|
1826
|
-
return {
|
|
1827
|
-
current: 0,
|
|
1828
|
-
currency: "KRW",
|
|
1829
|
-
lastUpdated: /* @__PURE__ */ new Date()
|
|
1830
|
-
};
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
async getProfile() {
|
|
1834
|
-
try {
|
|
1835
|
-
const balance = await this.getBalance();
|
|
1836
|
-
return {
|
|
1837
|
-
accountId: "iwinv-account",
|
|
1838
|
-
name: "IWINV Account",
|
|
1839
|
-
email: "account@iwinv.kr",
|
|
1840
|
-
phone: "1588-1234",
|
|
1841
|
-
status: balance.current > 0 ? "active" : "suspended",
|
|
1842
|
-
tier: "standard",
|
|
1843
|
-
features: ["alimtalk", "sms", "lms"],
|
|
1844
|
-
limits: {
|
|
1845
|
-
dailyMessageLimit: 1e4,
|
|
1846
|
-
monthlyMessageLimit: 3e5,
|
|
1847
|
-
rateLimit: 100
|
|
1848
|
-
// per second
|
|
1849
|
-
}
|
|
1850
|
-
};
|
|
1851
|
-
} catch (error) {
|
|
1852
|
-
return {
|
|
1853
|
-
accountId: "iwinv-account",
|
|
1854
|
-
name: "IWINV Account",
|
|
1855
|
-
email: "account@iwinv.kr",
|
|
1856
|
-
phone: "1588-1234",
|
|
1857
|
-
status: "active",
|
|
1858
|
-
tier: "basic",
|
|
1859
|
-
features: ["alimtalk"],
|
|
1860
|
-
limits: {
|
|
1861
|
-
dailyMessageLimit: 1e3,
|
|
1862
|
-
monthlyMessageLimit: 3e4,
|
|
1863
|
-
rateLimit: 10
|
|
1864
|
-
}
|
|
1865
|
-
};
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
};
|
|
1869
|
-
|
|
1870
|
-
// src/iwinv/provider.ts
|
|
1871
|
-
var IWINVProvider = class extends BaseAlimTalkProvider {
|
|
1872
|
-
id = "iwinv";
|
|
1873
|
-
name = "IWINV AlimTalk Provider";
|
|
1874
|
-
capabilities = {
|
|
1875
|
-
templates: {
|
|
1876
|
-
maxLength: 1e3,
|
|
1877
|
-
maxVariables: 20,
|
|
1878
|
-
maxButtons: 5,
|
|
1879
|
-
supportedButtonTypes: ["WL", "AL", "DB", "BK", "MD"],
|
|
1880
|
-
requiresApproval: true,
|
|
1881
|
-
approvalTime: "1-2 days"
|
|
1882
|
-
},
|
|
1883
|
-
messaging: {
|
|
1884
|
-
maxRecipientsPerRequest: 1,
|
|
1885
|
-
maxRequestsPerSecond: 100,
|
|
1886
|
-
supportsBulk: false,
|
|
1887
|
-
// IWINV doesn't have native bulk API
|
|
1888
|
-
supportsScheduling: true,
|
|
1889
|
-
maxScheduleDays: 30,
|
|
1890
|
-
supportsFallback: true
|
|
1891
|
-
},
|
|
1892
|
-
channels: {
|
|
1893
|
-
requiresBusinessVerification: true,
|
|
1894
|
-
maxSenderNumbers: 10,
|
|
1895
|
-
supportsMultipleChannels: false
|
|
1896
|
-
}
|
|
1897
|
-
};
|
|
1898
|
-
// Contract implementations
|
|
1899
|
-
templates;
|
|
1900
|
-
channels;
|
|
1901
|
-
messaging;
|
|
1902
|
-
analytics;
|
|
1903
|
-
account;
|
|
1904
|
-
constructor(config) {
|
|
1905
|
-
super(config);
|
|
1906
|
-
const iwinvConfig = this.getIWINVConfig();
|
|
1907
|
-
this.templates = new IWINVTemplateContract(iwinvConfig);
|
|
1908
|
-
this.channels = new IWINVChannelContract(iwinvConfig);
|
|
1909
|
-
this.messaging = new IWINVMessagingContract(iwinvConfig);
|
|
1910
|
-
this.analytics = new IWINVAnalyticsContract(iwinvConfig);
|
|
1911
|
-
this.account = new IWINVAccountContract(iwinvConfig);
|
|
1912
|
-
}
|
|
1913
|
-
getConfigurationSchema() {
|
|
1914
|
-
return {
|
|
1915
|
-
required: [
|
|
1916
|
-
{
|
|
1917
|
-
key: "apiKey",
|
|
1918
|
-
name: "IWINV API Key",
|
|
1919
|
-
type: "password",
|
|
1920
|
-
description: "Your IWINV API key",
|
|
1921
|
-
required: true,
|
|
1922
|
-
validation: {
|
|
1923
|
-
pattern: "^[a-zA-Z0-9-_]+$",
|
|
1924
|
-
min: 10
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
],
|
|
1928
|
-
optional: [
|
|
1929
|
-
{
|
|
1930
|
-
key: "baseUrl",
|
|
1931
|
-
name: "Base URL",
|
|
1932
|
-
type: "url",
|
|
1933
|
-
description: "IWINV API base URL",
|
|
1934
|
-
required: false,
|
|
1935
|
-
default: "https://alimtalk.bizservice.iwinv.kr",
|
|
1936
|
-
validation: {
|
|
1937
|
-
pattern: "^https?://.+"
|
|
1938
|
-
}
|
|
1939
|
-
},
|
|
1940
|
-
{
|
|
1941
|
-
key: "debug",
|
|
1942
|
-
name: "Debug Mode",
|
|
1943
|
-
type: "boolean",
|
|
1944
|
-
description: "Enable debug logging",
|
|
1945
|
-
required: false,
|
|
1946
|
-
default: false
|
|
1947
|
-
}
|
|
1948
|
-
]
|
|
1949
|
-
};
|
|
1950
|
-
}
|
|
1951
|
-
async testConnectivity() {
|
|
1952
|
-
const config = this.getIWINVConfig();
|
|
1953
|
-
try {
|
|
1954
|
-
const response = await fetch(`${config.baseUrl}/balance`, {
|
|
1955
|
-
method: "GET",
|
|
1956
|
-
headers: {
|
|
1957
|
-
"Authorization": `Bearer ${config.apiKey}`
|
|
1958
|
-
}
|
|
1959
|
-
});
|
|
1960
|
-
if (!response.ok) {
|
|
1961
|
-
throw new Error(`Connectivity test failed: ${response.status} ${response.statusText}`);
|
|
1962
|
-
}
|
|
1963
|
-
} catch (error) {
|
|
1964
|
-
throw new Error(`Cannot connect to IWINV API: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
async testAuthentication() {
|
|
1968
|
-
const config = this.getIWINVConfig();
|
|
1969
|
-
try {
|
|
1970
|
-
const response = await fetch(`${config.baseUrl}/balance`, {
|
|
1971
|
-
method: "GET",
|
|
1972
|
-
headers: {
|
|
1973
|
-
"Authorization": `Bearer ${config.apiKey}`
|
|
1974
|
-
}
|
|
1975
|
-
});
|
|
1976
|
-
if (response.status === 401 || response.status === 403) {
|
|
1977
|
-
throw new Error("Invalid API key or insufficient permissions");
|
|
1978
|
-
}
|
|
1979
|
-
if (!response.ok) {
|
|
1980
|
-
const result = await response.json().catch(() => ({}));
|
|
1981
|
-
throw new Error(`Authentication failed: ${result.message || response.statusText}`);
|
|
1982
|
-
}
|
|
1983
|
-
} catch (error) {
|
|
1984
|
-
if (error instanceof Error && error.message.includes("Authentication failed")) {
|
|
1985
|
-
throw error;
|
|
1986
|
-
}
|
|
1987
|
-
throw new Error(`Authentication test failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
getVersion() {
|
|
1991
|
-
return "2.0.0";
|
|
1992
|
-
}
|
|
1993
|
-
/**
|
|
1994
|
-
* Get IWINV-specific configuration
|
|
1995
|
-
*/
|
|
1996
|
-
getIWINVConfig() {
|
|
1997
|
-
return {
|
|
1998
|
-
apiKey: this.getConfig("apiKey"),
|
|
1999
|
-
baseUrl: this.getConfig("baseUrl") || "https://alimtalk.bizservice.iwinv.kr",
|
|
2000
|
-
debug: this.getConfig("debug") || false
|
|
2001
|
-
};
|
|
2002
|
-
}
|
|
2003
|
-
/**
|
|
2004
|
-
* IWINV-specific methods for backward compatibility
|
|
2005
|
-
*/
|
|
2006
|
-
/**
|
|
2007
|
-
* Send AlimTalk message (legacy method)
|
|
2008
|
-
*/
|
|
2009
|
-
async sendMessage(options) {
|
|
2010
|
-
return this.messaging.send({
|
|
2011
|
-
templateCode: options.templateCode,
|
|
2012
|
-
phoneNumber: options.phoneNumber,
|
|
2013
|
-
variables: options.variables,
|
|
2014
|
-
senderNumber: options.senderNumber
|
|
2015
|
-
});
|
|
2016
|
-
}
|
|
2017
|
-
/**
|
|
2018
|
-
* Get account balance (legacy method)
|
|
2019
|
-
*/
|
|
2020
|
-
async getBalance() {
|
|
2021
|
-
return this.account.getBalance();
|
|
2022
|
-
}
|
|
2023
|
-
/**
|
|
2024
|
-
* List templates (legacy method)
|
|
2025
|
-
*/
|
|
2026
|
-
async listTemplates(filters) {
|
|
2027
|
-
return this.templates.list(filters);
|
|
2028
|
-
}
|
|
2029
|
-
};
|
|
2030
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
2031
|
-
0 && (module.exports = {
|
|
2032
|
-
AligoRequestAdapter,
|
|
2033
|
-
AligoResponseAdapter,
|
|
2034
|
-
BaseAlimTalkProvider,
|
|
2035
|
-
BasePlugin,
|
|
2036
|
-
BaseRequestAdapter,
|
|
2037
|
-
BaseResponseAdapter,
|
|
2038
|
-
IWINVProvider,
|
|
2039
|
-
IWINVRequestAdapter,
|
|
2040
|
-
IWINVResponseAdapter,
|
|
2041
|
-
KakaoRequestAdapter,
|
|
2042
|
-
KakaoResponseAdapter,
|
|
2043
|
-
NHNResponseAdapter,
|
|
2044
|
-
PluginRegistry,
|
|
2045
|
-
ProviderManager,
|
|
2046
|
-
RequestAdapterFactory,
|
|
2047
|
-
ResponseAdapterFactory,
|
|
2048
|
-
createCircuitBreakerMiddleware,
|
|
2049
|
-
createLoggingMiddleware,
|
|
2050
|
-
createMetricsMiddleware,
|
|
2051
|
-
createRateLimitMiddleware,
|
|
2052
|
-
createRetryMiddleware,
|
|
2053
|
-
delay,
|
|
2054
|
-
extractVariables,
|
|
2055
|
-
formatDateTime,
|
|
2056
|
-
normalizePhoneNumber,
|
|
2057
|
-
parseTemplate,
|
|
2058
|
-
retry,
|
|
2059
|
-
validatePhoneNumber
|
|
2060
|
-
});
|
|
2061
|
-
//# sourceMappingURL=index.cjs.map
|