From 3e59693e017742d971f490eb7c58870cb745a98d Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Fri, 18 Jul 2025 03:58:34 +0000 Subject: (김준회) 결재 모듈 개발 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../realtime-notification-guide.html | 764 +++++++++++++++++++++ .../realtime-notification/realtime-notification.ts | 333 +++++++++ 2 files changed, 1097 insertions(+) create mode 100644 lib/knox-api/realtime-notification/realtime-notification-guide.html create mode 100644 lib/knox-api/realtime-notification/realtime-notification.ts (limited to 'lib/knox-api/realtime-notification') diff --git a/lib/knox-api/realtime-notification/realtime-notification-guide.html b/lib/knox-api/realtime-notification/realtime-notification-guide.html new file mode 100644 index 00000000..728df9c1 --- /dev/null +++ b/lib/knox-api/realtime-notification/realtime-notification-guide.html @@ -0,0 +1,764 @@ +
+
+

실시간알림

+
+
+

Knox Suite을 통해 토스트 알림을 전송합니다.

+


+
[정책 및 제약사항]
+
+
+
1. 토스트알림은 Knox Suite 로그인 사용자에게만 전송가능합니다.
+
2. 토스트알림에 포함 된 링크는 호출 시 사전 확인 필요합니다.
+
3. 토스트알림 최대 수신인은 100명을 초과할 수 없습니다.
+
+


+
[API 목록]    테스트 페이지로 이동(Swagger)*Chrome Browser만 이용 가능합니다.
+
+
+ + + + + + + + + + + + + + + +
APIURIMethodDescription
+
+
+ + + + + + + + + + + + + + + +
알림 전송/sendnotificationPOST실시간 토스트 알림 전송
+
+



+


+
알림 전송
+
+

Request Parameter

URL : + /notification/api/v2.0/sendnotification +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
No.PropertiesAttributeMandatoryParameter TypeData TypeSample DataNote
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1연계 시스템 ID +    System-IDYHeaderStringC60REST0001발급받은 + 연계 ID
2수신계정   targetAddressYBodyString Array["knoxportal@samsung.com"]알림수신인 + 메일계정
3이벤트속성   ntypeYBodyStringNEW이벤트의 + 속성
4시스템이름   systemnameYBodyStringPush service외부 + 시스템 식별 Name
5알림표시명   fromYBodyString녹스포털 테스트외부 + 시스템 Toast 표시명(Local)
6알림표시명(영) +    fromGlobalYBodyStringKnox Portal Test외부 + 시스템 Toast 표시명(Global)
7Visual속성 +    exVisualVOYBodyJsonStringN/A알림 + Visual 속성
8토스트이름   templateNBodyStringdefault templatePre-Defined된 Toast + 이름
9로고이미지   skinNBodyStringdefault skinBackground 와 기본제공 + Logo 이미지
10영문사용여부 +    globalNBodyStringNText에 + 설정된 global 사용여부
11로고표시여부 +    logoNBodyStringYlogo + 표시여부
12로고이미지URL +    logourlNQueryStringhttp://www.samsung.net/logo.jpg불러올 + logo 표시 이미지 url
13Text속성 +    exTextVOYQueryJsonStringArrayN/A알림 + Text 속성
14표시문자열   contentYBodyString한글기준 10자이내, 30byte 이내표시 + 문자열
15표시문자열(영) +    contentglobalNBodyStringToast Title표시 + 문자열 Global
16표시문자크기 +    sizeNBodyInt512표시 + 문자열 문자 크기
17표시문자위치 +    posYBodyInt2표시 + 문자열 위치
18표시문자스타일 +    styleNBodyStringBOLD표시 + 문자열 문자 스타일
19Color속성 +    exColorVONBodyJsonStringN/A표시 + 문자열 문자 색상
20표시문자Red +    rYBodyInt255RGB + 값중 red 값
21표시문자Green +    gYBodyInt255RGB + 값중 green 값
22표시문자Blue +    bYBodyInt0RGB + 값중 blue 값
23Action속성 +    exActionsVOYBodyJsonStringN/A알림 + Action 속성
24팝업여부   popupNBodyStringYToast + 클릭 시 links 값 중 rel 이 popup인 href 호출
25클릭허용여부 +    clickableNBodyStringYToast + 영역 클릭 허용 여부
26Link속성 +    exLinksVONBodyJsonStringN/A알림 + Link 속성
27링크속성값   relNBodyStringpopup링크 식별 + 값
28링크URL   hrefNBodyStringhttp://www.samsung.netlink + URL
29부가 옵션   hintYHeaderStringmultibrowsermultibrowser값 지정 시 녹스포털을 로그인한 브라우저의 Tab으로 href URL을 로딩 +
* IE 는 Popup으로 표시됨
+
+
+
+

Response Parameter

+
호출 성공여부 및 알림의 키 값(UID)을 리턴합니다. +
+
+ + + + + + + + + + + + + + + + + + + +
No.PropertiesAttributeData TypeSample DataNote
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1API 호출 + 성공여부   ResultStringSuccess실시간알림 연계 + 성공여부
2에러코드   ErrorCodeStringnull에러발생 + 시 코드 값
3응답메시지 및 알림 + ID   Message and IDStringAlarm Created, uid : 3cd8085ff2c4420bb4cf1828d6369ea4응답 + 메시지 및 알림 ID
+
+
+
+

Sample

+
+
+
+ + + + + + + + + + + +
RequestResponse
+
+
+ + + + + + + + + + + +
+
{
"targetAddress" : ["knoxportal@samsung.com"],
"ntype": "NEW",
"messageid": "EXT201804030305581008775022",
"systemname" : "push service",
"from" :"외부 알림 테스트",
"fromGlobal" : "External Notification Test ",
"exVisualVO": {
"template": "content",
"skin": "White",
"global": "N",
"logo": "Y",
"logourl": "",
"exTextVOList": [
{
"content": "제목",
"contentglobal": "Title",
"size": 14,
"pos": 1,
"exColorVO": {
"r": 0,
"g": 0,
"b": 0
},
"style": "BOLD"
}
]
},
"exActionsVO": {
"popup": "Y",
"snooze": "N",
"clickable": "Y",
"hint": "multibrowser",
"exLinksVOList":
[
{
"rel": "popup",
"href": "http://naver.com",
"args" : ""
}
]
}
}
+
+
+
{
"result": "OK",
"errorCode": null,
"message": "Alarm Created, uid : 3cd8085ff2c4420bb4cf1828d6369ea4"
}
+
+
+
+
+
+

Error Code

+
+
+
+ + + + + + + + + + + + + + + +
HTTP응답코드에러코드에러메시지조치방안
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
400EX001Notification information is null.알림 인풋 + 전체가 NULL일 경우 발생
400EX002[ntype] value is NEW or RECALL.ntype + 값이 NEW이거나 RECALL이지 않은 경우 발생
400EX003[from] is mendentory.from값이 + 입력되지 않은 경우 발생
400EX004[fromGlobal] is mendentory.fromGlobal값이 입력되지 않은 경우 + 발생
400EX006[systemname] is mendentory.systemname값이 입력되지 않은 경우 + 발생
400EX007[exVisualVO] is mendentory.exVisualVO값이 입력되지 않은 경우 + 발생
400EX008[exActionsVO] is mendentory.exActionsVO값이 입력되지 않은 경우 + 발생
400EX009The length of [template] is max 10 characters.template 값이 10자 이상 입력될 경우 + 발생
400EX010The length of [skin] is max 10 characters.skin 값이 + 10자 이상 입력될 경우 발생
400EX011The value of [global] value is not Y or N.global + 값이 Y혹은 N이 입력되지 않는 경우 발생
400EX012The value of [logo] value is not Y or N.logo 값이 + Y 혹은 N이 입력되지 않는 경우 발생
400EX013The value of [logourl] must start with url pattern(http://).Logourl값이 정상적인 URL이 아닌 경우 + 발생(http://로 시작필요)
400EX014[exTextVO] is mendentory.exTextVO 값이 입력되지 않은 경우 + 발생
400EX015[content] is mendentory.content + 값이 입력되지 않은 경우 발생
400EX016[R,G,B] value range is 0 to 255.R,G,B + 값이 0~255범위에서 벗어나는 경우 발생
400EX017For the requested search conditions, paging is not possible.href 값이 + 정상적인 URL이 아닌 경우 발생(http://로 시작필요)
400EX018The value of [template] encoding is supported UTF-8.Template 값이 UTF-8로 인코딩 되지 + 않은 경우 발생
400EX019The value of [skin] encoding is supported UTF-8.Skin 값이 + UTF-8로 인코딩 되지 않은 경우 발생
400EX020The value of [rel] is max 10 characters.Rel 값이 + 10자 이상 입력될 경우 발생
400EX021The value of [rel] encoding is supported UTF-8.rel 값이 + UTF-8로 인코딩 되지 않은 경우 발생
400EX022[targetAddress] is invalid. Check if an on-leave or retired person.targetAddress 값의 임직원 정보가 없는 + 경우 발생
400EX023[size] value range is 0 to 1024.size 값이 + 0~1024범위에서 벗어나는 경우 발생
400EX024[pos] value range is 1 to 3.pos 값이 + 1~3위에서 벗어나는 경우 발생
+
+


+
\ No newline at end of file diff --git a/lib/knox-api/realtime-notification/realtime-notification.ts b/lib/knox-api/realtime-notification/realtime-notification.ts new file mode 100644 index 00000000..a26f5b55 --- /dev/null +++ b/lib/knox-api/realtime-notification/realtime-notification.ts @@ -0,0 +1,333 @@ +"use server" + +import { z } from "zod" + +// 타입 정의 +const ColorSchema = z.object({ + r: z.number().min(0).max(255), + g: z.number().min(0).max(255), + b: z.number().min(0).max(255), +}) + +const TextSchema = z.object({ + content: z.string().min(1), + contentglobal: z.string().optional(), + size: z.number().min(0).max(1024).optional(), + pos: z.number().min(1).max(3), + exColorVO: ColorSchema.optional(), + style: z.string().optional(), +}) + +const LinkSchema = z.object({ + rel: z.string().max(10).optional(), + href: z.string().url().optional(), + args: z.string().optional(), +}) + +const VisualSchema = z.object({ + template: z.string().max(10).optional(), + skin: z.string().max(10).optional(), + global: z.enum(["Y", "N"]).optional(), + logo: z.enum(["Y", "N"]).optional(), + logourl: z.string().url().optional(), + exTextVOList: z.array(TextSchema), +}) + +const ActionsSchema = z.object({ + popup: z.enum(["Y", "N"]).optional(), + snooze: z.enum(["Y", "N"]).optional(), + clickable: z.enum(["Y", "N"]).optional(), + hint: z.string().optional(), + exLinksVOList: z.array(LinkSchema).optional(), +}) + +const NotificationRequestSchema = z.object({ + targetAddress: z.array(z.string().email()).max(100), + ntype: z.enum(["NEW", "RECALL"]), + messageid: z.string().optional(), + systemname: z.string().min(1), + from: z.string().min(1), + fromGlobal: z.string().min(1), + exVisualVO: VisualSchema, + exActionsVO: ActionsSchema, +}) + +export type NotificationRequest = z.infer +export type NotificationColor = z.infer +export type NotificationText = z.infer +export type NotificationLink = z.infer +export type NotificationVisual = z.infer +export type NotificationActions = z.infer + +// 응답 타입 +export interface NotificationResponse { + result: string + errorCode: string | null + message: string +} + +// 에러 타입 +export interface NotificationError { + httpStatus: number + errorCode: string + message: string +} + +// 환경 변수 검증 +const getApiConfig = () => { + const baseUrl = process.env.KNOX_API_BASE_URL + const systemId = process.env.KNOX_SYSTEM_ID + + if (!baseUrl || !systemId) { + throw new Error("Knox API configuration missing: KNOX_API_BASE_URL and KNOX_SYSTEM_ID are required") + } + + return { baseUrl, systemId } +} + +/** + * Knox Suite 실시간 토스트 알림 전송 + */ +export async function sendNotification( + request: NotificationRequest +): Promise { + try { + // 요청 데이터 검증 + const validatedRequest = NotificationRequestSchema.parse(request) + + const { baseUrl, systemId } = getApiConfig() + + const response = await fetch(`${baseUrl}/notification/api/v2.0/sendnotification`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "System-ID": systemId, + "hint": validatedRequest.exActionsVO.hint || "multibrowser", + }, + body: JSON.stringify(validatedRequest), + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(`API Error: ${errorData.message || response.statusText}`) + } + + const result: NotificationResponse = await response.json() + return result + + } catch (error) { + console.error("Knox notification error:", error) + throw error + } +} + +/** + * 간단한 토스트 알림 전송 (기본 설정 사용) + */ +export async function sendSimpleNotification( + targetEmails: string[], + title: string, + titleGlobal: string, + systemName: string, + link?: string +): Promise { + const notification: NotificationRequest = { + targetAddress: targetEmails, + ntype: "NEW", + systemname: systemName, + from: title, + fromGlobal: titleGlobal, + exVisualVO: { + template: "content", + skin: "White", + global: "N", + logo: "Y", + logourl: "", + exTextVOList: [ + { + content: title, + contentglobal: titleGlobal, + size: 14, + pos: 1, + exColorVO: { + r: 0, + g: 0, + b: 0, + }, + style: "BOLD", + }, + ], + }, + exActionsVO: { + popup: "Y", + clickable: "Y", + hint: "multibrowser", + exLinksVOList: link ? [ + { + rel: "popup", + href: link, + args: "", + }, + ] : [], + }, + } + + return await sendNotification(notification) +} + +/** + * 알림 취소 (RECALL) + */ +export async function recallNotification( + targetEmails: string[], + systemName: string, + messageId: string +): Promise { + const notification: NotificationRequest = { + targetAddress: targetEmails, + ntype: "RECALL", + messageid: messageId, + systemname: systemName, + from: "알림 취소", + fromGlobal: "Notification Recall", + exVisualVO: { + template: "content", + skin: "White", + global: "N", + logo: "Y", + logourl: "", + exTextVOList: [ + { + content: "알림이 취소되었습니다", + contentglobal: "Notification has been recalled", + size: 14, + pos: 1, + exColorVO: { + r: 255, + g: 0, + b: 0, + }, + style: "BOLD", + }, + ], + }, + exActionsVO: { + popup: "N", + clickable: "N", + hint: "multibrowser", + }, + } + + return await sendNotification(notification) +} + +/** + * 사용자 정의 스타일 토스트 알림 + */ +export async function sendCustomNotification( + targetEmails: string[], + systemName: string, + title: string, + titleGlobal: string, + options: { + template?: string + skin?: string + logoUrl?: string + textColor?: NotificationColor + textSize?: number + textStyle?: string + link?: string + linkRel?: string + } = {} +): Promise { + const notification: NotificationRequest = { + targetAddress: targetEmails, + ntype: "NEW", + systemname: systemName, + from: title, + fromGlobal: titleGlobal, + exVisualVO: { + template: options.template || "content", + skin: options.skin || "White", + global: "N", + logo: options.logoUrl ? "Y" : "Y", + logourl: options.logoUrl || "", + exTextVOList: [ + { + content: title, + contentglobal: titleGlobal, + size: options.textSize || 14, + pos: 1, + exColorVO: options.textColor || { + r: 0, + g: 0, + b: 0, + }, + style: options.textStyle || "BOLD", + }, + ], + }, + exActionsVO: { + popup: options.link ? "Y" : "N", + clickable: options.link ? "Y" : "N", + hint: "multibrowser", + exLinksVOList: options.link ? [ + { + rel: options.linkRel || "popup", + href: options.link, + args: "", + }, + ] : [], + }, + } + + return await sendNotification(notification) +} + +/** + * 헬퍼 함수: 알림 ID 추출 + */ +export async function extractNotificationId(response: NotificationResponse): Promise { + if (response.result === "OK" && response.message) { + const match = response.message.match(/uid\s*:\s*([a-f0-9]+)/i) + return match ? match[1] : null + } + return null +} + +/** + * 헬퍼 함수: 여러 사용자에게 배치 알림 전송 + */ +export async function sendBatchNotifications( + notifications: Array<{ + targetEmails: string[] + title: string + titleGlobal: string + systemName: string + link?: string + }> +): Promise { + const results: NotificationResponse[] = [] + + for (const notification of notifications) { + try { + const result = await sendSimpleNotification( + notification.targetEmails, + notification.title, + notification.titleGlobal, + notification.systemName, + notification.link + ) + results.push(result) + } catch (error) { + console.error("Batch notification error:", error) + results.push({ + result: "ERROR", + errorCode: "BATCH_ERROR", + message: error instanceof Error ? error.message : "Unknown error", + }) + } + } + + return results +} -- cgit v1.2.3