n8n-nodes-naver-sms
Version:
Naver Cloud SENS SMS node for n8n
214 lines (213 loc) • 9.92 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NaverSms = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const crypto_1 = __importDefault(require("crypto"));
class NaverSms {
constructor() {
this.description = {
displayName: 'Naver SMS',
name: 'naverSms',
icon: 'fa:envelope',
group: ['output'],
version: 1,
description: 'Send SMS/LMS/MMS via Naver Cloud Platform',
defaults: { name: 'Naver SMS' },
inputs: ["main" /* NodeConnectionType.Main */],
outputs: ["main" /* NodeConnectionType.Main */],
credentials: [{ name: 'naverSensApi', required: true }],
properties: [
{
displayName: 'Message Type',
name: 'type',
type: 'options',
options: [
{
name: 'SMS (90바이트, 한글45자)',
value: 'SMS'
},
{
name: 'LMS (2000바이트, 한글1000자)',
value: 'LMS'
},
{
name: 'MMS (첨부파일 가능)',
value: 'MMS'
},
],
default: 'SMS',
},
{
displayName: 'To (수신번호, 하이픈없이 숫자만)',
name: 'to',
type: 'string',
required: true,
default: '',
},
{
displayName: 'Content (메시지 내용)',
name: 'content',
type: 'string',
typeOptions: { rows: 4 },
required: true,
default: '',
},
{
displayName: 'Subject (LMS/MMS 제목, 선택사항)',
name: 'subject',
type: 'string',
default: '',
displayOptions: {
show: { type: ['LMS', 'MMS'] },
},
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
let creds;
try {
creds = await this.getCredentials('naverSensApi');
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), '네이버 SENS API 인증 정보를 가져올 수 없습니다.');
}
// 필수 인증 정보 검증
if (!creds.accessKey || !creds.secretKey || !creds.serviceId || !creds.from) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), '네이버 SENS API 인증 정보가 완전하지 않습니다. Access Key, Secret Key, Service ID, From 번호를 모두 설정해주세요.');
}
const baseUrl = 'https://sens.apigw.ntruss.com';
for (let i = 0; i < items.length; i++) {
// 변수들을 try 블록 외부에서 선언
let type = '';
let to = '';
let content = '';
let subject = '';
let cleanTo = '';
let timestamp = '';
let uri = '';
let signature = '';
let body = {};
try {
type = this.getNodeParameter('type', i);
to = this.getNodeParameter('to', i);
content = this.getNodeParameter('content', i);
subject = this.getNodeParameter('subject', i, '');
// 입력 검증
if (!to || !content) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `아이템 ${i + 1}: 수신번호와 메시지 내용은 필수입니다.`);
}
// 전화번호 형식 검증 (간단한 검증)
const phoneRegex = /^01[0-9]{8,9}$/;
cleanTo = to.replace(/[^0-9]/g, '');
if (!phoneRegex.test(cleanTo)) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `아이템 ${i + 1}: 올바르지 않은 전화번호 형식입니다. (예: 01012345678)`);
}
// 메시지 길이 검증 (네이버 SMS는 EUC-KR 기준으로 바이트 계산)
const getEuckrByteLength = (str) => {
let byteLength = 0;
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
if (charCode <= 0x7F) {
// ASCII 문자 (1바이트)
byteLength += 1;
}
else if (charCode <= 0x7FF) {
// 한글 및 기타 2바이트 문자
byteLength += 2;
}
else {
// 3바이트 이상 유니코드 문자도 EUC-KR에서는 2바이트로 처리
byteLength += 2;
}
}
return byteLength;
};
const contentBytes = getEuckrByteLength(content);
if (type === 'SMS' && contentBytes > 90) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `아이템 ${i + 1}: SMS는 90바이트를 초과할 수 없습니다. 현재: ${contentBytes}바이트 (EUC-KR 기준)`);
}
else if ((type === 'LMS' || type === 'MMS') && contentBytes > 2000) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `아이템 ${i + 1}: LMS/MMS는 2000바이트를 초과할 수 없습니다. 현재: ${contentBytes}바이트 (EUC-KR 기준)`);
}
timestamp = Date.now().toString();
uri = `/sms/v2/services/${creds.serviceId}/messages`;
const sigStr = `POST ${uri}\n${timestamp}\n${creds.accessKey}`;
signature = crypto_1.default
.createHmac('sha256', creds.secretKey)
.update(sigStr)
.digest('base64');
body = {
type,
contentType: 'COMM',
countryCode: '82',
from: creds.from,
content,
messages: [{ to: cleanTo, content }],
};
// LMS/MMS인 경우 제목 추가
if (subject && (type === 'LMS' || type === 'MMS')) {
body.subject = subject;
body.messages[0].subject = subject;
}
const response = await this.helpers.httpRequest({
method: 'POST',
url: `${baseUrl}${uri}`,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': creds.accessKey,
'x-ncp-apigw-signature-v2': signature,
},
body,
json: true,
});
// 응답 데이터에 요청 정보 추가
const responseData = {
...response,
requestInfo: {
type,
to: cleanTo,
content,
subject,
timestamp: new Date(parseInt(timestamp)).toISOString(),
},
};
returnData.push({ json: responseData });
}
catch (error) {
if (error instanceof n8n_workflow_1.NodeOperationError) {
throw error;
}
// HTTP 에러의 경우 더 자세한 정보 출력
let errorMessage = error instanceof Error ? error.message : String(error);
if (error && typeof error === 'object' && 'response' in error) {
const httpError = error;
const responseData = httpError.response?.data || httpError.response || {};
const statusCode = httpError.response?.status || 'unknown';
console.log('=== NAVER SENS API 에러 디버깅 정보 ===');
console.log('Status Code:', statusCode);
console.log('Request URL:', `${baseUrl}${uri}`);
console.log('Request Headers:', {
'Content-Type': 'application/json; charset=utf-8',
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': creds.accessKey,
'x-ncp-apigw-signature-v2': signature,
});
console.log('Request Body:', JSON.stringify(body, null, 2));
console.log('Response Data:', JSON.stringify(responseData, null, 2));
console.log('=====================================');
errorMessage = `HTTP ${statusCode}: ${JSON.stringify(responseData)}`;
}
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `아이템 ${i + 1} 처리 중 오류 발생: ${errorMessage}`);
}
}
return [returnData];
}
}
exports.NaverSms = NaverSms;