initialized

This commit is contained in:
병준 박 2019-11-09 21:56:11 +09:00
commit dbfd0fd765
1178 changed files with 68792 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

47
.gitignore vendored Normal file
View File

@ -0,0 +1,47 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
/.awcache
# System Files
.DS_Store
Thumbs.db

11
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"recommendations": [
"Angular.ng-template",
"msjsdiag.debugger-for-chrome",
"eamodio.gitlens",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-tslint-plugin",
"VisualStudioExptTeam.vscodeintellicode",
"nkokhelox.svg-font-previewer"
]
}

7
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}

14
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.autoClosingBrackets": "languageDefined",
"editor.trimAutoWhitespace": true,
"files.trimTrailingWhitespace": true,
"files.trimFinalNewlines": true,
"go.testFlags": ["-v"],
"go.testTimeout": "100s",
"prettier.singleQuote": true,
"debug.node.autoAttach": "on"
}

15
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build:main:dev",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

1
README.md Normal file
View File

@ -0,0 +1 @@
readme

1521
angular.json Normal file

File diff suppressed because it is too large Load Diff

19
config/enviroment.ts Normal file
View File

@ -0,0 +1,19 @@
import * as fs from 'fs';
import * as Path from 'path';
const projectRoot = Path.dirname(__dirname);
const channel = process.env.NODE_ENV;
const s = JSON.stringify;
export function getEnviroments() {
return {
__DARWIN__: process.platform === 'darwin',
__WIN32__: process.platform === 'win32',
__LINUX__: process.platform === 'linux',
__DEV__: channel === 'development',
'process.platform': s(process.platform),
'process.env.NODE_ENV': s(process.env.NODE_ENV || 'development'),
'process.env.TEST_ENV': s(process.env.TEST_ENV)
};
}

View File

@ -0,0 +1,111 @@
import * as path from 'path';
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import * as webpack from 'webpack';
import * as nodeExternals from 'webpack-node-externals';
import { getEnviroments } from './enviroment';
const enviroments = getEnviroments();
export const externals = [nodeExternals()];
// if (enviroments.__DEV__) {
// externals.push('devtron');
// }
const outputDir = 'dist/main';
const mainConfig: webpack.Configuration = {
entry: { main: path.resolve(__dirname, '..', 'main/src/index') },
target: 'electron-main',
mode: enviroments.__DEV__ ? 'development' : 'production',
devtool: 'source-map',
optimization: {
noEmitOnErrors: true
},
externals,
output: {
filename: '[name].js',
path: path.resolve(__dirname, '..', outputDir)
},
module: {
rules: [
{
test: /\.tsx?$/,
include: [
path.resolve(__dirname, '..', 'main/src'),
path.resolve(__dirname, '..', 'projects')
],
use: [
{
loader: 'awesome-typescript-loader',
options: {
useCache: true,
configFileName: path.resolve(
__dirname,
'..',
'main/tsconfig.main.json'
)
}
}
],
exclude: /node_modules/
},
{
test: /\.node$/,
loader: 'awesome-node-loader',
options: {
name: '[name].[ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin({ verbose: false }),
// This saves us a bunch of bytes by pruning locales (which we don't use)
// from moment.
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.DefinePlugin(
Object.assign({}, enviroments, {
__PROCESS_KIND__: JSON.stringify('main')
})
),
new CopyWebpackPlugin([
{
from: 'main/resources/**/*',
to: path.resolve(__dirname, '..', 'dist')
}
])
],
resolve: {
extensions: ['.js', '.ts'],
alias: {
'@ucap-webmessenger/electron-core': path.resolve(
__dirname,
'..',
'projects/ucap-webmessenger-electron-core/src/public-api'
),
'@ucap-webmessenger/electron-notification': path.resolve(
__dirname,
'..',
'projects/ucap-webmessenger-electron-notification/src/public-api'
),
'@ucap-webmessenger/native': path.resolve(
__dirname,
'..',
'projects/ucap-webmessenger-native/src/public-api'
),
'@ucap-webmessenger/native-electron': path.resolve(
__dirname,
'..',
'projects/ucap-webmessenger-native-electron/src/public-api'
)
},
modules: [path.resolve(__dirname, '..', 'node_modules/')]
},
node: {
__dirname: false,
__filename: false
}
};
export default [mainConfig];

View File

@ -0,0 +1,18 @@
const path = require('path');
module.exports = (config, options) => {
const PRODUCTION = process.env.NODE_ENV === 'production';
config.target = 'electron-renderer';
config.resolve.alias = {
...config.resolve.alias,
'@ucap-webmessenger-scss/ui': path.resolve(
__dirname,
'..',
'projects/ucap-webmessenger-ui/src/assets/scss'
)
};
return config;
};

View File

@ -0,0 +1,6 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}

40
electron-builder.json Normal file
View File

@ -0,0 +1,40 @@
{
"productName": "WooriTalk",
"appId": "lgcns.ucap.messenger",
"asar": true,
"protocols": {
"name": "WooriTalk",
"schemes": ["WooriTalk"]
},
"publish": {
"provider": "generic",
"url": "http://localhost:8099/client-updates/"
},
"mac": {
"target": ["default"],
"icon": "./resources/installer/woori.icns"
},
"dmg": {
"title": "WooriTalk",
"icon": "./resources/installer/woori.icns"
},
"win": {
"target": ["zip", "nsis"],
"icon": "./resources/installer/woori_256x256.ico"
},
"linux": {
"target": ["AppImage", "deb", "rpm", "zip", "tar.gz"],
"icon": "./resources/linuxicon"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": true,
"differentialPackage": true
},
"directories": {
"buildResources": "resources/installer/",
"output": "dist-electron/",
"app": "."
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,143 @@
'use strict';
const electron = require('electron');
const ipc = electron.ipcRenderer;
const winId = electron.remote.getCurrentWindow().id;
function setStyle(config) {
// Style it
let notiDoc = global.window.document;
let container = notiDoc.getElementById('container');
let appIcon = notiDoc.getElementById('appIcon');
let image = notiDoc.getElementById('image');
let close = notiDoc.getElementById('close');
let message = notiDoc.getElementById('message');
// Default style
setStyleOnDomElement(config.defaultStyleContainer, container);
// Size and radius
let style = {
height:
config.height -
2 * config.borderRadius -
2 * config.defaultStyleContainer.padding,
width:
config.width -
2 * config.borderRadius -
2 * config.defaultStyleContainer.padding,
borderRadius: config.borderRadius + 'px'
};
setStyleOnDomElement(style, container);
// Style appIcon or hide
if (config.appIcon) {
setStyleOnDomElement(config.defaultStyleAppIcon, appIcon);
appIcon.src = config.appIcon;
} else {
setStyleOnDomElement(
{
display: 'none'
},
appIcon
);
}
// Style image
setStyleOnDomElement(config.defaultStyleImage, image);
// Style close button
setStyleOnDomElement(config.defaultStyleClose, close);
// Remove margin from text p
setStyleOnDomElement(config.defaultStyleText, message);
}
function setContents(event, notificationObj) {
// sound
if (notificationObj.sound) {
// Check if file is accessible
try {
// If it's a local file, check it's existence
// Won't check remote files e.g. http://
if (
notificationObj.sound.match(/^file\:/) !== null ||
notificationObj.sound.match(/^\//) !== null
) {
let audio = new global.window.Audio(notificationObj.sound);
audio.play();
}
} catch (e) {
log(
'electron-notify: ERROR could not find sound file: ' +
notificationObj.sound.replace('file://', ''),
e,
e.stack
);
}
}
let notiDoc = global.window.document;
// Title
let titleDoc = notiDoc.getElementById('title');
titleDoc.innerHTML = notificationObj.title || '';
// message
let messageDoc = notiDoc.getElementById('message');
messageDoc.innerHTML = notificationObj.text || '';
// Image
let imageDoc = notiDoc.getElementById('image');
if (notificationObj.image) {
imageDoc.src = notificationObj.image;
} else {
setStyleOnDomElement({ display: 'none' }, imageDoc);
}
// Close button
let closeButton = notiDoc.getElementById('close');
closeButton.addEventListener('click', function(event) {
event.stopPropagation();
ipc.send('UCAP::ElectronNotification::close', winId, notificationObj);
});
// URL
let container = notiDoc.getElementById('container');
container.addEventListener('click', function() {
ipc.send('UCAP::ElectronNotification::click', winId, notificationObj);
});
}
function setStyleOnDomElement(styleObj, domElement) {
try {
for (let styleAttr in styleObj) {
domElement.style[styleAttr] = styleObj[styleAttr];
}
} catch (e) {
throw new Error(
'electron-notify: Could not set style on domElement',
styleObj,
domElement
);
}
}
function loadConfig(event, conf) {
setStyle(conf || {});
}
function reset() {
let notiDoc = global.window.document;
let container = notiDoc.getElementById('container');
let closeButton = notiDoc.getElementById('close');
// Remove event listener
let newContainer = container.cloneNode(true);
container.parentNode.replaceChild(newContainer, container);
let newCloseButton = closeButton.cloneNode(true);
closeButton.parentNode.replaceChild(newCloseButton, closeButton);
}
ipc.on('UCAP::ElectronNotification::BrowserWindowSetContents', setContents);
ipc.on('UCAP::ElectronNotification::loadConfig', loadConfig);
ipc.on('UCAP::ElectronNotification::reset', reset);
function log() {
console.log.apply(console, arguments);
}
delete global.require;
delete global.exports;
delete global.module;

Binary file not shown.

View File

@ -0,0 +1,130 @@
html {
height: 100%;
overflow-y: scroll;
}
body {
position: relative;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
color: #333;
font-family: '나눔고딕', Malgun Gothic, '맑은고딕', Arial, Dotum, '돋움',
Gulim, '굴림';
font-size: 12px;
line-height: 18px !important;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
ul,
ol {
list-style: none;
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
img {
border: none;
}
a:link,
a:visited,
a:hover,
a:active {
text-decoration: none;
}
.noti_messege {
width: 340px;
height: 100px;
border: 1px solid #666;
background-color: #fff;
box-shadow: 0px 0px 3px 0px #e7e7e7;
}
.info {
position: relative;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 16px 14px;
color: #fff;
}
.btn_close {
position: absolute;
z-index: 1;
right: 6px;
top: 6px;
width: 20px;
height: 20px;
background: url(../image/btn_close_gray.png) no-repeat 50% 50%;
}
.btn_close:hover {
opacity: 0.7;
}
.photo {
position: relative;
top: 0px;
right: 0;
bottom: 0;
left: 0;
margin: 4px 0;
width: 54px;
height: 54px;
border-radius: 50%;
background: #5bc1ff url(../image/img_nophoto_50.png) no-repeat 50% 50%;
border: 2px solid #ddd;
}
.info .profile {
position: absolute;
width: 60px;
text-align: center;
}
.photo img {
overflow: hidden;
width: 50px;
height: 50px;
border-radius: 50px;
}
.noti_messege .info .profile + div {
padding-left: 70px;
position: relative;
line-height: 180%;
height: 100%;
}
.sender {
font-size: 14px;
font-weight: bold;
margin-bottom: 4px;
color: #333;
width: 94%;
}
.sender .name {
color: #2e7fb5;
}
.message {
color: #666;
}
.ellipsis {
display: block;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
overflow: hidden;
}
.ellipsis_row2 {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-wrap: break-word;
line-height: 1.6em;
height: 3.2em;
}

View File

@ -0,0 +1,50 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<title>[개발]M Messenger - 메시지 알림</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="UTF-8" />
<link
type="text/css"
rel="stylesheet"
href="styles/noti_messege.css"
/>
</HEAD>
<style>
html,
body {
overflow-y: hidden;
overflow-x: hidden;
}
</style>
<BODY>
<div class="noti_messege" id="container">
<div class="info">
<a class="btn_close" id="close"></a>
<div class="profile">
<div class="photo">
<img src="" id="appIcon" />
<img
src=""
id="image"
onerror="this.src='image/img_nophoto_50.png';"
/>
</div>
</div>
<div>
<ul id="text">
<li class="sender ellipsis" id="title">
<span class="name">김 수안무 거북이와 두루미</span>님이 메시지를
보냈습니다.
</li>
<li class="message ellipsis_row2" id="message">
홍길동 대리(솔루션사업팀)홍길동 대리(솔루션사업팀)홍길동
대리(솔루션사업팀)홍길동 대리(솔루션사업팀)
</li>
</ul>
</div>
</div>
</div>
</BODY>
</HTML>

229
main/src/app/AppWindow.ts Normal file
View File

@ -0,0 +1,229 @@
import * as path from 'path';
import * as url from 'url';
import { app, BrowserWindow, screen, ipcMain, IpcMainEvent } from 'electron';
import windowStateKeeper from 'electron-window-state';
import { EventEmitter } from 'events';
import { now } from '../util/now';
import { registerWindowStateChangedEvents } from '../lib/window-state';
import {
ElectronAppChannel,
ElectronBrowserWindowChannel,
ElectronWebContentsChannel
} from '@ucap-webmessenger/electron-core';
export class AppWindow {
private window: BrowserWindow | null = null;
private eventEmitter = new EventEmitter();
// tslint:disable-next-line: variable-name
private _loadTime: number | null = null;
// tslint:disable-next-line: variable-name
private _rendererReadyTime: number | null = null;
private minWidth = 960;
private minHeight = 660;
public constructor() {
const savedWindowState = windowStateKeeper({
defaultWidth: this.minWidth,
defaultHeight: this.minHeight
});
const windowOptions: Electron.BrowserWindowConstructorOptions = {
x: savedWindowState.x,
y: savedWindowState.y,
width: savedWindowState.width,
height: savedWindowState.height,
minWidth: this.minWidth,
minHeight: this.minHeight,
center: true,
// This fixes subpixel aliasing on Windows
// See https://github.com/atom/atom/commit/683bef5b9d133cb194b476938c77cc07fd05b972
backgroundColor: '#fff',
webPreferences: {
// Disable auxclick event
// See https://developers.google.com/web/updates/2016/10/auxclick
disableBlinkFeatures: 'Auxclick',
// Enable, among other things, the ResizeObserver
experimentalFeatures: true,
nodeIntegration: true
},
acceptFirstMouse: true,
icon: path.join(__dirname, 'resources/image', 'ico_64_64.png')
};
if (__DARWIN__) {
windowOptions.titleBarStyle = 'hidden';
} else if (__WIN32__) {
windowOptions.frame = false;
} else if (__LINUX__) {
windowOptions.icon = path.join(__dirname, 'static', 'icon-logo.png');
}
this.window = new BrowserWindow(windowOptions);
savedWindowState.manage(this.window);
let quitting = false;
app.on(ElectronAppChannel.BeforeQuit, () => {
quitting = true;
});
ipcMain.on(ElectronAppChannel.WillQuit, (event: IpcMainEvent) => {
quitting = true;
event.returnValue = true;
});
// on macOS, when the user closes the window we really just hide it. This
// lets us activate quickly and keep all our interesting logic in the
// renderer.
if (__DARWIN__) {
this.window.on(ElectronBrowserWindowChannel.Close, e => {
if (!quitting) {
e.preventDefault();
}
});
}
if (__WIN32__) {
// workaround for known issue with fullscreen-ing the app and restoring
// is that some Chromium API reports the incorrect bounds, so that it
// will leave a small space at the top of the screen on every other
// maximize
//
// adapted from https://github.com/electron/electron/issues/12971#issuecomment-403956396
//
// can be tidied up once https://github.com/electron/electron/issues/12971
// has been confirmed as resolved
this.window.once(ElectronBrowserWindowChannel.ReadyToShow, () => {
this.window.on(ElectronBrowserWindowChannel.Unmaximize, () => {
setTimeout(() => {
const bounds = this.window.getBounds();
bounds.width += 1;
this.window.setBounds(bounds);
bounds.width -= 1;
this.window.setBounds(bounds);
}, 5);
});
});
}
}
public load(): void {
let startLoad = 0;
this.window.webContents.once(
ElectronWebContentsChannel.DidStartLoading,
() => {
this._rendererReadyTime = null;
this._loadTime = null;
startLoad = now();
}
);
this.window.webContents.once(
ElectronWebContentsChannel.DidFinishLoad,
() => {
this.window.webContents.setVisualZoomLevelLimits(1, 1);
if (process.env.NODE_ENV === 'development') {
this.window.webContents.openDevTools();
}
this._loadTime = now() - startLoad;
}
);
this.window.webContents.on(ElectronWebContentsChannel.DidFailLoad, () => {
this.window.webContents.openDevTools();
this.window.show();
});
registerWindowStateChangedEvents(this.window);
if (__DEV__) {
this.window.loadURL('http://localhost:4200');
} else {
this.window.loadURL(
url.format({
pathname: path.join(
__dirname,
'..',
'ucap-webmessenger-app/index.html'
),
protocol: 'file:',
slashes: true
})
);
}
}
/** Is the page loaded and has the renderer signalled it's ready? */
private get rendererLoaded(): boolean {
return !!this.loadTime && !!this.rendererReadyTime;
}
public onClose(fn: () => void) {
this.window.on(ElectronBrowserWindowChannel.Closed, fn);
}
/**
* Register a function to call when the window is done loading. At that point
* the page has loaded and the renderer has signalled that it is ready.
*/
public onDidLoad(fn: () => void): EventEmitter {
return this.eventEmitter.on('did-load', fn);
}
public isMinimized() {
return this.window.isMinimized();
}
/** Is the window currently visible? */
public isVisible() {
return this.window.isVisible();
}
public restore() {
this.window.restore();
}
public focus() {
this.window.focus();
}
/** Show the window. */
public show() {
this.window.show();
}
/**
* Get the time (in milliseconds) spent loading the page.
*
* This will be `null` until `onDidLoad` is called.
*/
public get loadTime(): number | null {
return this._loadTime;
}
/**
* Get the time (in milliseconds) elapsed from the renderer being loaded to it
* signaling it was ready.
*
* This will be `null` until `onDidLoad` is called.
*/
public get rendererReadyTime(): number | null {
return this._rendererReadyTime;
}
public destroy() {
this.window.destroy();
}
public get browserWindow(): BrowserWindow | null {
return this.window;
}
}

View File

View File

@ -0,0 +1 @@
export function showUncaughtException(isLaunchError: boolean, error: Error) {}

18
main/src/global.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/interface-name-prefix */
/** Is the app running in dev mode? */
declare const __DEV__: boolean;
/** Is the app being built to run on Darwin? */
declare const __DARWIN__: boolean;
/** Is the app being built to run on Win32? */
declare const __WIN32__: boolean;
/** Is the app being built to run on Linux? */
declare const __LINUX__: boolean;
/**
* The currently executing process kind, this is specific to desktop
* and identifies the processes that we have.
*/
declare const __PROCESS_KIND__: 'main' | 'ui';

306
main/src/index.ts Normal file
View File

@ -0,0 +1,306 @@
import { app, ipcMain, IpcMainEvent, remote } from 'electron';
import * as path from 'path';
import * as url from 'url';
import * as fse from 'fs-extra';
import * as fs from 'fs';
import { AppWindow } from './app/AppWindow';
import { now } from './util/now';
import { showUncaughtException } from './crash/show-uncaught-exception';
import {
UpdaterChannel,
FileChannel,
IdleStateChannel,
NotificationChannel
} from '@ucap-webmessenger/native-electron';
import { ElectronNotificationService } from '@ucap-webmessenger/electron-notification';
import { root } from './util/root';
import { DefaultFolder } from './lib/default-folder';
import { FileUtil } from './lib/file-util';
import { IdleChecker } from './lib/idle-checker';
import { NotificationRequest } from '@ucap-webmessenger/native';
import { ElectronAppChannel } from '@ucap-webmessenger/electron-core';
let appWindow: AppWindow | null = null;
const launchTime = now();
let readyTime: number | null = null;
type OnDidLoadFn = (window: AppWindow) => void;
let onDidLoadFns: Array<OnDidLoadFn> | null = [];
let preventQuit = false;
let notificationService: ElectronNotificationService | null;
function handleUncaughtException(error: Error) {
preventQuit = true;
// If we haven't got a window we'll assume it's because
// we've just launched and haven't created it yet.
// It could also be because we're encountering an unhandled
// exception on shutdown but that's less likely and since
// this only affects the presentation of the crash dialog
// it's a safe assumption to make.
const isLaunchError = appWindow === null;
if (appWindow) {
appWindow.destroy();
appWindow = null;
}
showUncaughtException(isLaunchError, error);
}
function getUptimeInSeconds() {
return (now() - launchTime) / 1000;
}
process.on('uncaughtException', (error: Error) => {
// error = withSourceMappedStack(error);
// reportError(error, getExtraErrorContext());
handleUncaughtException(error);
});
let isDuplicateInstance = false;
const gotSingleInstanceLock = app.requestSingleInstanceLock();
isDuplicateInstance = !gotSingleInstanceLock;
let idle: IdleChecker | null;
app.on(ElectronAppChannel.SecondInstance, (event, args, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (appWindow) {
if (appWindow.isMinimized()) {
appWindow.restore();
}
if (!appWindow.isVisible()) {
appWindow.show();
}
appWindow.focus();
}
});
if (isDuplicateInstance) {
app.quit();
}
function createWindow() {
const window = new AppWindow();
if (__DEV__) {
// const {
// default: installExtension,
// REDUX_DEVTOOLS
// } = require('electron-devtools-installer');
import('electron-debug').then(ed => {
ed.default({ showDevTools: true });
});
import('electron-devtools-installer').then(edi => {
const ChromeLens = {
id: 'idikgljglpfilbhaboonnpnnincjhjkd',
electron: '>=1.2.1'
};
const extensions = [edi.REDUX_DEVTOOLS, ChromeLens];
for (const extension of extensions) {
try {
edi.default(extension);
} catch (e) {
console.log(e);
}
}
});
}
window.onClose(() => {
appWindow = null;
if (!__DARWIN__ && !preventQuit) {
app.quit();
}
});
window.onDidLoad(() => {
window.show();
const fns = onDidLoadFns;
onDidLoadFns = null;
for (const fn of fns) {
fn(window);
}
});
window.load();
appWindow = window;
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on(ElectronAppChannel.Ready, () => {
if (isDuplicateInstance) {
return;
}
readyTime = now() - launchTime;
createWindow();
notificationService = new ElectronNotificationService({
width: 340,
height: 100,
padding: 10,
borderRadius: 0,
// appIcon: iconPath,
displayTime: 5000,
defaultStyleContainer: {},
defaultStyleAppIcon: { display: 'none' },
defaultStyleImage: {},
defaultStyleClose: {},
defaultStyleText: {}
});
notificationService.options.defaultWindow.webPreferences.preload = path.join(
__dirname,
'resources/notification/preload.js'
);
notificationService.templatePath = path.join(
__dirname,
'resources/notification/template.html'
);
ipcMain.on('uncaught-exception', (event: IpcMainEvent, error: Error) => {
handleUncaughtException(error);
});
ipcMain.on(
'send-error-report',
(
event: IpcMainEvent,
{ error, extra }: { error: Error; extra: { [key: string]: string } }
) => {}
);
});
// Quit when all windows are closed.
app.on(ElectronAppChannel.WindowAllClosed, () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on(ElectronAppChannel.Activate, () => {
onDidLoad(window => {
window.show();
});
});
function onDidLoad(fn: OnDidLoadFn) {
if (onDidLoadFns) {
onDidLoadFns.push(fn);
} else {
if (appWindow) {
fn(appWindow);
}
}
}
ipcMain.on(UpdaterChannel.Check, (event: IpcMainEvent, ...args: any[]) => {
event.returnValue = false;
});
ipcMain.on(FileChannel.ReadFile, (event: IpcMainEvent, ...args: any[]) => {
try {
fse.readFile(root(args[0]), (err, data) => {
if (!!err) {
event.returnValue = null;
} else {
event.returnValue = new Blob([data]);
}
});
} catch (error) {
event.returnValue = null;
}
});
ipcMain.on(
FileChannel.SaveFile,
async (event: IpcMainEvent, ...args: any[]) => {
try {
const buffer: Buffer = args[0];
const fileName: string = args[1];
let savePath: string = path.join(
!!args[2] ? args[2] : DefaultFolder.downloads(),
fileName
);
savePath = await FileUtil.uniqueFileName(savePath);
fse.writeFile(savePath, buffer, err => {
if (!err) {
event.returnValue = savePath;
} else {
event.returnValue = undefined;
}
});
} catch (error) {
event.returnValue = undefined;
}
}
);
ipcMain.on(
IdleStateChannel.StartCheck,
(event: IpcMainEvent, ...args: any[]) => {
if (!!idle) {
idle.destoryChecker();
idle = null;
}
idle = new IdleChecker(appWindow.browserWindow); // default 10min
idle.startChecker();
}
);
ipcMain.on(
NotificationChannel.Notify,
(event: IpcMainEvent, ...args: any[]) => {
const noti: NotificationRequest = args[0];
notificationService.notify({
title: noti.title,
text: noti.contents,
image:
noti.image ||
path.join(__dirname, 'resources/notification/image/img_nophoto_50.png'),
sound: noti.useSound
? path.join(
'file://',
__dirname,
'resources/notification/sound/messageAlarm.mp3'
)
: '',
onClick: () => {
console.log('onClick');
}
});
console.log('Channel.notify', noti);
}
);
ipcMain.on(
NotificationChannel.CloseAllNotify,
(event: IpcMainEvent, ...args: any[]) => {
console.log('Channel.closeAllNotify', args);
}
);

View File

@ -0,0 +1,37 @@
import * as os from 'os';
import { execSync } from 'child_process';
import * as fse from 'fs-extra';
export class DefaultFolder {
static downloads(): string {
switch (os.platform()) {
case 'win32':
return `${process.env.USERPROFILE}/Downloads`;
case 'darwin':
return `${process.env.HOME}/Downloads`;
case 'linux': {
let dir: Buffer;
try {
dir = execSync('xdg-user-dir DOWNLOAD', { stdio: [0, 3, 3] });
} catch (_) {}
if (dir) {
return dir.toString('utf-8');
}
let stat: fse.Stats;
const homeDownloads = `${process.env.HOME}/Downloads`;
try {
stat = fse.statSync(homeDownloads);
} catch (_) {}
if (stat) {
return homeDownloads;
}
return '/tmp/';
}
default:
break;
}
}
}

222
main/src/lib/file-util.ts Normal file
View File

@ -0,0 +1,222 @@
import * as path from 'path';
import * as fse from 'fs-extra';
/**
*
* separator
* If the specified filename exists, the separator will be added before the incremental value such as: file{separator}2.jpg
* The default value is '-'.
*
* mode
* The mode allows you to specify which characters to use to generate the incremental value (the string after the separator)
* The default value is 'numeric'.
* 'numeric' Using the following characters: 1234567890
* 'alpha' Using the following characters: abcdefghijklmnopqrstuvwxyz
* 'ALPHA' Using the following characters: ABCDEFGHIJKLMNOPQRSTUVWXYZ
* 'alphanumeric' Using the following characters: 0123456789abcdefghijklmnopqrstuvwxyz
* 'ALPHANUMERIC' Using the following characters: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
* 'charset' You must specify the characters you wish to use in the charset option
*
* paddingCharacter && paddingSize
* If you wish to left-pad the incremental values with a character, use this option. Here's an example :
* var uniquefilename = require('uniquefilename');
* options = {mode: 'alpha', paddingCharacter: '0', paddingSize: 3};
* uniquefilename.get('/path/to/dir/file.jpg', options, function(filename) {
* // filename might be "/path/to/dir/file.jpg",
* // "/path/to/dir/file-002.jpg", "/path/to/dir/file-045.jpg", etc...
* // depending on the files that exist on your filesystem
* });
*
* alwaysAppend
* If alwaysAppend is true filenames will include the separator and attachment from the first request.
* So instead of file.jpg, file-2.jpg you'd get file-1.jpg, file-2.jpg.
*/
export interface UniqueFileNameOption {
separator?: string;
mode?:
| 'numeric'
| 'alpha'
| 'ALPHA'
| 'alphanumeric'
| 'ALPHANUMERIC'
| 'charset';
paddingCharacter?: string;
paddingSize?: number;
alwaysAppend?: boolean;
charset?: string;
}
const charsets = {
alpha: 'abcdefghijklmnopqrstuvwxyz',
alphanumeric: '0123456789abcdefghijklmnopqrstuvwxyz',
ALPHA: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
ALPHANUMERIC: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
};
interface UniqueFile {
dir?: string;
ext?: string;
base?: string;
increment?: number;
}
export class FileUtil {
static blobToBuffer(blob: Blob): Promise<Buffer> {
if (typeof Blob === 'undefined' || !(blob instanceof Blob)) {
throw new Error('first argument must be a Blob');
}
return new Promise<Buffer>((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(Buffer.from(reader.result as ArrayBuffer));
};
reader.onerror = () => {
reader.abort();
reject(reader.error);
};
reader.readAsArrayBuffer(blob);
});
}
static uniqueFileName(
filePath: string,
options?: UniqueFileNameOption
): Promise<string> {
return new Promise<string>((resolve, reject) => {
const dir = path.dirname(filePath);
const ext = path.extname(filePath);
const base = path.basename(filePath, ext);
const uniqueFile: UniqueFile = {
dir,
ext,
base
};
options = options || {};
options.separator = options.separator || '-';
options.mode = options.mode || 'numeric';
if ('numeric' !== options.mode) {
if (charsets[options.mode]) {
options.charset = charsets[options.mode];
options.mode = 'charset';
} else if (
'charset' !== options.mode ||
('charset' === options.mode && !options.charset)
) {
options.mode = 'numeric';
}
}
if (options.paddingSize && !options.paddingCharacter) {
options.paddingCharacter = '0';
}
FileUtil.uniqueFileNameProcess(
uniqueFile,
options,
(fileName: string) => {
resolve(fileName);
}
);
});
}
private static uniqueFileNameProcess(
uniqueFile: UniqueFile,
options: UniqueFileNameOption,
callback: (fileName: string) => void
) {
let fileName: string;
let append = '';
if (options.alwaysAppend && !uniqueFile.increment) {
uniqueFile.increment = 1;
}
if (uniqueFile.increment) {
if ('numeric' === options.mode) {
append = '' + uniqueFile.increment;
} else {
append = FileUtil.numberToString(uniqueFile.increment, options.charset);
}
if (options.paddingSize) {
while (append.length < options.paddingSize) {
append = options.paddingCharacter + append;
}
}
append = options.separator + append;
}
fileName = path.join(
uniqueFile.dir,
uniqueFile.base + append + uniqueFile.ext
);
if (fse.existsSync(fileName)) {
if (uniqueFile.increment) {
uniqueFile.increment += 1;
} else {
uniqueFile.increment = 'numeric' === options.mode ? 2 : 1;
}
return FileUtil.uniqueFileNameProcess(uniqueFile, options, callback);
} else {
return callback(fileName);
}
}
private static numberToString(nbr: number, charset: string) {
const charsetLen = charset.length;
let strLen = 0;
let strThisLen = 0;
let tmp: number;
for (let maxpower = 20; maxpower >= 0; maxpower--) {
const maxvalue = FileUtil.sumOfPowerFromOne(charsetLen, maxpower);
if (maxvalue < nbr) {
strLen = maxpower + 1;
strThisLen = maxvalue + Math.pow(charsetLen, maxpower + 1) - maxvalue;
break;
}
}
if (0 === strLen) {
return null;
}
let str = '';
while (--strLen >= 0) {
if (strLen === 0) {
str += charset.charAt(nbr - 1);
break;
}
strThisLen = Math.pow(charsetLen, strLen);
const initial = FileUtil.sumOfPowerFromOne(charsetLen, strLen - 1);
for (tmp = charsetLen; tmp >= 1; tmp--) {
if (initial + tmp * strThisLen < nbr) {
break;
}
}
nbr -= tmp * strThisLen;
str += charset.charAt(tmp - 1);
}
return str;
}
private static sumOfPowerFromOne(base: number, maxpower: number) {
let value = 0;
for (let tmp = maxpower; tmp >= 1; tmp--) {
value += Math.pow(base, tmp);
}
return value;
}
}

View File

@ -0,0 +1,72 @@
import { powerMonitor, BrowserWindow } from 'electron';
import { IdleStateChannel } from '@ucap-webmessenger/native-electron';
import { setInterval } from 'timers';
export enum IdleType {
ACTIVE = 'ACT',
IDLE = 'IDLE'
}
export class IdleChecker {
private limitSec: number;
private intervalObject: any;
private status: IdleType;
private window: BrowserWindow | null;
public constructor(window: BrowserWindow, limitedMin?: number) {
limitedMin = limitedMin || 10;
this.limitSec = limitedMin * 60;
this.intervalObject = null;
this.status = IdleType.ACTIVE;
this.window = window;
}
private doCheckIdle(): void {
const idle: number = powerMonitor.getSystemIdleTime();
if (idle > this.limitSec) {
if (this.status === IdleType.ACTIVE) {
this.status = IdleType.IDLE;
// TODO :: USER_STATUS change away
this.window.webContents.send(IdleStateChannel.Changed, this.status);
}
} else {
if (this.status === IdleType.IDLE) {
this.status = IdleType.ACTIVE;
// TODO :: USER_STATUS chage online
this.window.webContents.send(IdleStateChannel.Changed, this.status);
}
}
}
public resetIdleTime(limitedMin: number): void {
limitedMin = limitedMin || 10;
if (!!this.intervalObject) {
clearInterval(this.intervalObject);
}
this.limitSec = limitedMin * 60;
// storage.setIdleTimeLimit(limitedMin);
// global.opt_idleTimeLimit = limitedMin;
this.startChecker();
console.log('RESET IDLE TIMER in ' + limitedMin + 'm');
}
public startChecker() {
console.log('Idle Checker Start');
if (!this.intervalObject) {
this.intervalObject = setInterval(() => {
this.doCheckIdle();
}, 1000);
}
}
public destoryChecker() {
console.log('Idle Checker Destory');
if (!!this.intervalObject) {
clearInterval(this.intervalObject);
}
}
}

99
main/src/lib/storage.ts Normal file
View File

@ -0,0 +1,99 @@
import Store from 'electron-store';
const STORE_KEY_AUTORUN = 'options.autoRun';
const STORE_KEY_AUTOLOGIN = 'options.autoLogin';
const STORE_KEY_STARTUPHIDEWINDOW = 'options.startupHideWindow';
const STORE_KEY_LOGINCOMPANY = 'login.loginCompany';
const STORE_KEY_LOGINID = 'login.loginId';
const STORE_KEY_LOGINPW = 'login.loginPw';
export class Storage extends Store<any> {
constructor() {
super({
schema: {
options: {
type: 'object',
properties: {
autoRun: {
type: 'boolean'
},
autoLogin: {
type: 'boolean'
},
startupHideWindow: {
type: 'boolean'
}
},
default: {
autoRun: false,
autoLogin: false,
startupHideWindow: false
}
},
login: {
type: 'object',
properties: {
loginCompany: {
type: 'string'
},
loginId: {
type: 'string'
},
loginPw: {
type: 'string'
}
},
default: {
loginCompany: '',
loginId: '',
loginPw: ''
}
}
},
encryptionKey: 'ucap',
fileExtension: 'dat'
});
}
get autoRun(): boolean {
return this.get(STORE_KEY_AUTORUN, false);
}
set autoRun(autoRun: boolean) {
this.set(STORE_KEY_AUTORUN, autoRun);
}
get autoLogin(): boolean {
return this.get(STORE_KEY_AUTOLOGIN, false);
}
set autoLogin(autoLogin: boolean) {
this.set(STORE_KEY_AUTOLOGIN, autoLogin);
}
get startupHideWindow(): boolean {
return this.get(STORE_KEY_STARTUPHIDEWINDOW, false);
}
set startupHideWindow(startupHideWindow: boolean) {
this.set(STORE_KEY_STARTUPHIDEWINDOW, startupHideWindow);
}
get loginCompany(): string {
return this.get(STORE_KEY_LOGINCOMPANY, false);
}
set loginCompany(loginCompany: string) {
this.set(STORE_KEY_LOGINCOMPANY, loginCompany);
}
get loginId(): string {
return this.get(STORE_KEY_LOGINID, false);
}
set loginId(loginId: string) {
this.set(STORE_KEY_LOGINID, loginId);
}
get loginPw(): string {
return this.get(STORE_KEY_LOGINPW, false);
}
set loginPw(loginPw: string) {
this.set(STORE_KEY_LOGINPW, loginPw);
}
}

View File

@ -0,0 +1,54 @@
import { BrowserWindow } from 'electron';
import { WindowState } from '@ucap-webmessenger/native';
import { WindowStateChannel } from '@ucap-webmessenger/native-electron';
import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core';
export function getWindowState(window: Electron.BrowserWindow): WindowState {
if (window.isFullScreen()) {
return WindowState.FullScreen;
} else if (window.isMaximized()) {
return WindowState.Maximized;
} else if (window.isMinimized()) {
return WindowState.Minimized;
} else if (!window.isVisible()) {
return WindowState.Hidden;
} else {
return WindowState.Normal;
}
}
export function registerWindowStateChangedEvents(window: BrowserWindow) {
window.on(ElectronBrowserWindowChannel.EnterFullScreen, () =>
sendWindowStateEvent(window, WindowState.FullScreen)
);
window.on(ElectronBrowserWindowChannel.LeaveFullScreen, () =>
sendWindowStateEvent(window, WindowState.Normal)
);
window.on(ElectronBrowserWindowChannel.Maximize, () =>
sendWindowStateEvent(window, WindowState.Maximized)
);
window.on(ElectronBrowserWindowChannel.Minimize, () =>
sendWindowStateEvent(window, WindowState.Minimized)
);
window.on(ElectronBrowserWindowChannel.Unmaximize, () =>
sendWindowStateEvent(window, WindowState.Normal)
);
window.on(ElectronBrowserWindowChannel.Restore, () =>
sendWindowStateEvent(window, WindowState.Normal)
);
window.on(ElectronBrowserWindowChannel.Hide, () =>
sendWindowStateEvent(window, WindowState.Hidden)
);
window.on(ElectronBrowserWindowChannel.Show, () => {
// because the app can be maximized before being closed - which will restore it
// maximized on the next launch - this function should inspect the current state
// rather than always assume it is a 'normal' launch
sendWindowStateEvent(window, getWindowState(window));
});
}
function sendWindowStateEvent(window: BrowserWindow, windowState: WindowState) {
window.webContents.send(WindowStateChannel.Changed, windowState);
}

4
main/src/util/now.ts Normal file
View File

@ -0,0 +1,4 @@
export function now(): number {
const time = process.hrtime();
return time[0] * 1000 + time[1] / 1000000;
}

11
main/src/util/root.ts Normal file
View File

@ -0,0 +1,11 @@
import * as path from 'path';
// tslint:disable-next-line: variable-name
const _root = __DEV__
? path.resolve(__dirname, '..', '..')
: path.resolve(__dirname);
export function root(...paths: string[]) {
const args = Array.prototype.slice.call(paths, 0);
return path.join.apply(path, [_root].concat(args));
}

32
main/tsconfig.main.json Normal file
View File

@ -0,0 +1,32 @@
{
"compilerOptions": {
"baseUrl": "./",
"outDir": "../dist/main",
"sourceMap": true,
"declaration": false,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"target": "es5",
"types": ["node"],
"lib": ["es2017", "es2016", "es2015", "dom"],
"paths": {
"@ucap-webmessenger/electron-core": [
"../projects/ucap-webmessenger-electron-core/src/public-api"
],
"@ucap-webmessenger/electron-notification": [
"../projects/ucap-webmessenger-electron-notification/src/public-api"
],
"@ucap-webmessenger/native": [
"../projects/ucap-webmessenger-native/src/public-api"
],
"@ucap-webmessenger/native-electron": [
"../projects/ucap-webmessenger-native-electron/src/public-api"
]
}
},
"exclude": ["../node_modules", "**/*.spec.ts"]
}

16640
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

116
package.json Normal file
View File

@ -0,0 +1,116 @@
{
"name": "ucap-webmessenger",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "npm-run-all -p start:renderer start:main",
"start:main": "wait-on http-get://localhost:4200/ && npm run build:main:dev && electron --nolazy --inspect-brk=9229 .",
"start:renderer": "ng serve",
"start:web": "cross-env UCAP_ENV=WEB ng serve",
"start:production": "npm run build:renderer && npm run build:main:prod && electron --nolazy --inspect-brk=9229 .",
"build:renderer": "cross-env NODE_ENV=production ng build --base-href ./",
"build:main:dev": "cross-env NODE_ENV=development TS_NODE_PROJECT='./config/tsconfig.webpack.json' parallel-webpack --config=config/main.webpack.config.ts",
"build:main:prod": "cross-env NODE_ENV=production TS_NODE_PROJECT='./config/tsconfig.webpack.json' NODE_OPTIONS='--max_old_space_size=4096' parallel-webpack --config=config/main.webpack.config.ts",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"tslib": "^1.10.0"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^8.2.0",
"@angular-devkit/build-angular": "~0.803.14",
"@angular-devkit/build-ng-packagr": "~0.803.14",
"@angular/animations": "^8.2.12",
"@angular/cdk": "^8.2.3",
"@angular/cli": "~8.3.14",
"@angular/common": "~8.2.12",
"@angular/compiler": "~8.2.12",
"@angular/compiler-cli": "~8.2.12",
"@angular/core": "~8.2.12",
"@angular/flex-layout": "^8.0.0-beta.27",
"@angular/forms": "~8.2.12",
"@angular/language-service": "~8.2.12",
"@angular/material": "^8.2.3",
"@angular/material-moment-adapter": "^8.2.3",
"@angular/platform-browser": "~8.2.12",
"@angular/platform-browser-dynamic": "~8.2.12",
"@angular/router": "~8.2.12",
"@ngrx/effects": "^8.4.0",
"@ngrx/entity": "^8.4.0",
"@ngrx/router-store": "^8.4.0",
"@ngrx/store": "^8.4.0",
"@ngrx/store-devtools": "^8.4.0",
"@ngx-translate/core": "^11.0.1",
"@types/copy-webpack-plugin": "^5.0.0",
"@types/crypto-js": "^3.1.43",
"@types/detect-browser": "^4.0.0",
"@types/electron-debug": "^2.1.0",
"@types/electron-devtools-installer": "^2.2.0",
"@types/extract-text-webpack-plugin": "^3.0.4",
"@types/fs-extra": "^8.0.0",
"@types/file-saver": "^2.0.1",
"@types/filesize": "^4.1.0",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^10.14.22",
"@types/semver": "^6.0.2",
"@types/webpack": "^4.39.5",
"@types/webpack-merge": "^4.1.5",
"@types/webpack-node-externals": "^1.6.3",
"awesome-node-loader": "^1.1.1",
"awesome-typescript-loader": "^5.2.1",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.0.4",
"codelyzer": "^5.0.0",
"concurrently": "^4.1.2",
"crypto-js": "^3.1.9-1",
"cross-env": "^5.2.1",
"detect-browser": "^4.6.0",
"devtron": "^1.4.0",
"electron": "^6.1.2",
"electron-builder": "^21.2.0",
"electron-debug": "^3.0.1",
"electron-devtools-installer": "^2.2.4",
"electron-log": "^3.0.8",
"electron-reload": "^1.5.0",
"electron-store": "^4.0.0",
"electron-updater": "^4.1.2",
"electron-window-state": "^5.0.3",
"file-saver": "^2.0.2",
"fs-extra": "^8.1.0",
"filesize": "^4.1.2",
"hammerjs": "^2.0.8",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"moment": "^2.24.0",
"ng-packagr": "^5.4.0",
"ngrx-store-freeze": "^0.2.4",
"ngx-logger": "^4.0.5",
"ngx-perfect-scrollbar": "^8.0.0",
"npm-run-all": "^4.1.5",
"parallel-webpack": "^2.4.0",
"protractor": "~5.4.0",
"queueing-subject": "^0.3.4",
"rxjs": "^6.5.2",
"semver": "^6.3.0",
"ts-node": "~7.0.0",
"tsickle": "^0.37.0",
"tslib": "^1.10.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3",
"wait-on": "^3.3.0",
"webpack": "4.39.2",
"webpack-cli": "^3.3.7",
"webpack-node-externals": "^1.7.2",
"zone.js": "~0.9.1"
},
"main": "./dist/main/main.js"
}

View File

@ -0,0 +1,24 @@
# UcapWebmessengerApiCommon
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.4.
## Code scaffolding
Run `ng generate component component-name --project ucap-webmessenger-api-common` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ucap-webmessenger-api-common`.
> Note: Don't forget to add `--project ucap-webmessenger-api-common` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build ucap-webmessenger-api-common` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build ucap-webmessenger-api-common`, go to the dist folder `cd dist/ucap-webmessenger-api-common` and run `npm publish`.
## Running unit tests
Run `ng test ucap-webmessenger-api-common` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/ucap-webmessenger-api-common'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/ucap-webmessenger-api-common",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@ -0,0 +1,8 @@
{
"name": "@ucap-webmessenger/api-common",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.4",
"@angular/core": "^8.2.4"
}
}

View File

@ -0,0 +1,36 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil
} from '@ucap-webmessenger/api';
export interface FileProfileSaveRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
file?: File;
intro?: string;
initProfileImage?: boolean;
}
export interface FileProfileSaveResponse extends APIResponse {
ProfileURL?: string;
ProfileSubDir?: string;
}
const fileProfileEncodeMap = {};
export const encodeFileProfileSave: APIEncoder<FileProfileSaveRequest> = (
req: FileProfileSaveRequest
) => {
return ParameterUtil.encode(fileProfileEncodeMap, req);
};
export const decodeFileProfileSave: APIDecoder<FileProfileSaveResponse> = (
res: any
) => {
return {} as FileProfileSaveResponse;
};

View File

@ -0,0 +1,49 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIEncoder,
ParameterUtil,
APIFormDataEncoder
} from '@ucap-webmessenger/api';
import { FileDownloadItem } from '../models/file-download-item';
export interface FileTalkDownloadRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
attachmentsSeq?: number;
fileDownloadItem?: FileDownloadItem;
}
const fileTalkDownloadEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
attachmentsSeq: 'p_att_seq'
};
export const encodeFileTalkDownload: APIEncoder<FileTalkDownloadRequest> = (
req: FileTalkDownloadRequest
) => {
const extraParams: any = {};
extraParams.userSeq = String(req.userSeq);
extraParams.attachmentsSeq = String(req.attachmentsSeq);
return ParameterUtil.encode(fileTalkDownloadEncodeMap, req, extraParams);
};
export const encodeFormDataFileTalkDownload: APIFormDataEncoder<
FileTalkDownloadRequest
> = (req: FileTalkDownloadRequest) => {
const extraParams: any = {};
extraParams.userSeq = String(req.userSeq);
extraParams.attachmentsSeq = String(req.attachmentsSeq);
return ParameterUtil.encodeFormData(
fileTalkDownloadEncodeMap,
req,
extraParams
);
};

View File

@ -0,0 +1,99 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil,
StatusCode,
JsonAnalization,
APIFormDataEncoder
} from '@ucap-webmessenger/api';
import { FileUploadItem } from '../models/file-upload-item';
export interface FileTalkSaveRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
file: File;
fileName?: string;
fileUploadItem: FileUploadItem;
thumb?: File;
voice?: boolean;
voiceTime?: string;
roomSeq?: string;
type?: string;
}
export interface FileTalkSaveResponse extends APIResponse {
roomSeq?: string;
fileName?: string;
fileExt?: string;
fileType?: string;
thumbnailUrl?: string;
attachmentSeq?: string;
attachmentSize?: string;
attachmentRegDate?: string;
imageWidth?: string;
imageHeight?: string;
companyCode?: string;
voiceTime?: string;
synapKey?: string;
returnJson?: any;
}
const fileTalkSaveEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
file: 'file',
fileName: 'p_file_name',
thumb: 'thumb',
voice: 'p_voice',
voiceTime: 'p_voice_time',
roomSeq: 'p_room_id',
type: 'p_type'
};
export const encodeFileTalkSave: APIFormDataEncoder<FileTalkSaveRequest> = (
req: FileTalkSaveRequest
) => {
const extraParams: any = {};
extraParams.userSeq = String(req.userSeq);
if (!!req.voice) {
extraParams.voice = req.voice ? 'Y' : 'N';
}
return ParameterUtil.encodeFormData(fileTalkSaveEncodeMap, req, extraParams);
};
export const decodeFileTalkSave: APIDecoder<FileTalkSaveResponse> = (
res: any
) => {
try {
const json = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
roomSeq: json.RoomID,
fileName: json.FileName,
fileExt: json.FileExt,
fileType: json.FileType,
thumbnailUrl: json.ThumbURL,
attachmentSeq: json.AttSEQ,
attachmentSize: json.AttSize,
attachmentRegDate: json.AttRegDate,
imageWidth: json.ImageWidth,
imageHeight: json.ImageHeight,
companyCode: json.CompanyCode,
voiceTime: json.VoiceTime,
synapKey: json.SynapKey,
returnJson: res
} as FileTalkSaveResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as FileTalkSaveResponse;
}
};

View File

@ -0,0 +1,75 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil,
JsonAnalization,
StatusCode
} from '@ucap-webmessenger/api';
export interface FileTalkShareRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
attachmentsSeq?: string;
roomSeq?: string;
synapKey?: string;
}
export interface FileTalkShareResponse extends APIResponse {
roomSeq?: string;
fileName?: string;
fileExt?: string;
fileType?: string;
thumbnailUrl?: string;
attachmentSeq?: string;
attachmentSize?: string;
attachmentRegDate?: string;
companyCode?: string;
synapKey?: string;
returnJson?: any;
}
const fileTalkShareEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
attachmentsSeq: 'p_att_seq',
roomSeq: 'p_room_id',
synapKey: 'p_synap_key'
};
export const encodeFileTalkShare: APIEncoder<FileTalkShareRequest> = (
req: FileTalkShareRequest
) => {
return ParameterUtil.encode(fileTalkShareEncodeMap, req);
};
export const decodeFileTalkShare: APIDecoder<FileTalkShareResponse> = (
res: any
) => {
try {
const json = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
roomSeq: json.RoomID,
fileName: json.FileName,
fileExt: json.FileExt,
fileType: json.FileType,
thumbnailUrl: json.ThumbURL,
attachmentSeq: json.AttSEQ,
attachmentSize: json.AttSize,
attachmentRegDate: json.AttRegDate,
companyCode: json.CompanyCode,
synapKey: json.SynapKey,
returnJson: res
} as FileTalkShareResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as FileTalkShareResponse;
}
};

View File

@ -0,0 +1,56 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil,
JsonAnalization,
StatusCode
} from '@ucap-webmessenger/api';
export interface MassTalkDownloadRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
eventMassSeq?: number;
}
export interface MassTalkDownloadResponse extends APIResponse {
content?: string;
userName?: string;
regDate?: string;
}
const massTalkDownloadEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
eventMassSeq: 'p_event_mass_seq'
};
export const encodeMassTalkDownload: APIEncoder<MassTalkDownloadRequest> = (
req: MassTalkDownloadRequest
) => {
return ParameterUtil.encode(massTalkDownloadEncodeMap, req);
};
export const decodeMassTalkDownload: APIDecoder<MassTalkDownloadResponse> = (
res: any
) => {
try {
const json = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
errorMessage: json.ErrorMessage,
content: json.Content,
userName: json.UserName,
regDate: json.RegDate
} as MassTalkDownloadResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as MassTalkDownloadResponse;
}
};

View File

@ -0,0 +1,62 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil,
StatusCode,
JsonAnalization
} from '@ucap-webmessenger/api';
export interface MassTalkSaveRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
content?: string;
roomSeq?: string;
}
export interface MassTalkSaveResponse extends APIResponse {
eventMassSeq?: string;
roomSeq?: string;
regDate?: string;
content?: string;
returnJson?: any;
}
const massTalkSaveEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
content: 'p_content',
roomSeq: 'p_room_id'
};
export const encodeMassTalkSave: APIEncoder<MassTalkSaveRequest> = (
req: MassTalkSaveRequest
) => {
return ParameterUtil.encode(massTalkSaveEncodeMap, req);
};
export const decodeMassTalkSave: APIDecoder<MassTalkSaveResponse> = (
res: any
) => {
try {
const json = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
errorMessage: json.ErrorMessage,
content: json.Content,
eventMassSeq: json.EventMassSeq,
regDate: json.RegDate,
roomSeq: json.RoomID,
returnJson: res
} as MassTalkSaveResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as MassTalkSaveResponse;
}
};

View File

@ -0,0 +1,37 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil
} from '@ucap-webmessenger/api';
export interface TransMassTalkDownloadRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
eventTransSeq?: string;
}
export interface TransMassTalkDownloadResponse extends APIResponse {
Original?: string;
Translation?: string;
Locale?: string;
UserName?: string;
RegDate?: string;
}
const transMassTalkDownloadEncodeMap = {};
export const encodeTransMassTalkDownload: APIEncoder<
TransMassTalkDownloadRequest
> = (req: TransMassTalkDownloadRequest) => {
return ParameterUtil.encode(transMassTalkDownloadEncodeMap, req);
};
export const decodeTransMassTalkDownload: APIDecoder<
TransMassTalkDownloadResponse
> = (res: any) => {
return {} as TransMassTalkDownloadResponse;
};

View File

@ -0,0 +1,69 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil,
JsonAnalization,
StatusCode
} from '@ucap-webmessenger/api';
export interface TransMassTalkSaveRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
original?: string;
translation?: string;
roomSeq?: string;
locale: string;
}
export interface TransMassTalkSaveResponse extends APIResponse {
roomSeq?: string;
registrationDate?: string;
translationSeq?: string;
locale?: string;
original?: string;
translation?: string;
returnJson?: any;
}
const transMassTalkSaveEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
original: 'p_original',
translation: 'p_translation',
roomSeq: 'p_room_id',
locale: 'p_locale'
};
export const encodeTransMassTalkSave: APIEncoder<TransMassTalkSaveRequest> = (
req: TransMassTalkSaveRequest
) => {
return ParameterUtil.encode(transMassTalkSaveEncodeMap, req);
};
export const decodeTransMassTalkSave: APIDecoder<TransMassTalkSaveResponse> = (
res: any
) => {
try {
const json = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
translationSeq: json.EventTransSEQ,
roomSeq: json.RoomID,
registrationDate: json.RegDate,
locale: json.Locale,
original: json.Original,
translation: json.Translation,
returnJson: res
} as TransMassTalkSaveResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as TransMassTalkSaveResponse;
}
};

View File

@ -0,0 +1,38 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil
} from '@ucap-webmessenger/api';
export interface TranslationReqRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
original: string;
srcLocale: string;
destLocale: string;
}
export interface TranslationReqResponse extends APIResponse {
SrcLocale?: string;
DestLocale?: string;
Original?: string;
Translation?: string;
}
const translationReqEncodeMap = {};
export const encodeTranslationReq: APIEncoder<TranslationReqRequest> = (
req: TranslationReqRequest
) => {
return ParameterUtil.encode(translationReqEncodeMap, req);
};
export const decodeTranslationReq: APIDecoder<TranslationReqResponse> = (
res: any
) => {
return {} as TranslationReqResponse;
};

View File

@ -0,0 +1,71 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil,
JsonAnalization,
StatusCode
} from '@ucap-webmessenger/api';
export interface TranslationSaveRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
roomSeq?: string;
original?: string;
srcLocale: string;
destLocale: string;
}
export interface TranslationSaveResponse extends APIResponse {
translationSeq?: string;
roomSeq?: string;
registrationDate?: string;
srcLocale?: string;
destLocale?: string;
original?: string;
translation?: string;
returnJson?: any;
}
const translationSaveEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
roomSeq: 'p_room_id',
original: 'p_original',
srcLocale: 'p_src_locale',
destLocale: 'p_dest_locale'
};
export const encodeTranslationSave: APIEncoder<TranslationSaveRequest> = (
req: TranslationSaveRequest
) => {
return ParameterUtil.encode(translationSaveEncodeMap, req);
};
export const decodeTranslationSave: APIDecoder<TranslationSaveResponse> = (
res: any
) => {
try {
const json = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
translationSeq: json.EventTransSEQ,
roomSeq: json.RoomID,
registrationDate: json.RegDate,
srcLocale: json.SrcLocale,
destLocale: json.DestLocale,
original: json.Original,
translation: json.Translation,
returnJson: res
} as TranslationSaveResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as TranslationSaveResponse;
}
};

View File

@ -0,0 +1,27 @@
import { Observable, Subject } from 'rxjs';
import { share } from 'rxjs/operators';
export class FileDownloadItem {
downloadTime: number;
downloadingProgress$: Observable<number>;
private downloadingProgress: Subject<number>;
private downloadStartTime: number;
constructor() {}
downloadStart(): Subject<number> {
this.downloadStartTime = new Date().getTime();
this.downloadingProgress = new Subject<number>();
this.downloadingProgress$ = this.downloadingProgress
.asObservable()
.pipe(share());
return this.downloadingProgress;
}
downloadComplete() {
const endTime = new Date().getTime();
this.downloadTime = endTime - this.downloadStartTime;
this.downloadingProgress.complete();
}
}

View File

@ -0,0 +1,41 @@
import { Observable, Subject } from 'rxjs';
import { share } from 'rxjs/operators';
export class FileUploadItem {
file: File;
uploadTime: number;
uploadingProgress$: Observable<number>;
private uploadingProgress: Subject<number>;
private uploadStartTime: number;
private constructor(file: File) {
this.file = file;
}
static fromFiles(files: FileList): FileUploadItem[] {
const fileItems: FileUploadItem[] = [];
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < files.length; i++) {
fileItems.push(new FileUploadItem(files[i]));
}
return fileItems;
}
uploadStart(): Subject<number> {
this.uploadStartTime = new Date().getTime();
this.uploadingProgress = new Subject<number>();
this.uploadingProgress$ = this.uploadingProgress
.asObservable()
.pipe(share());
return this.uploadingProgress;
}
uploadComplete() {
const endTime = new Date().getTime();
this.uploadTime = endTime - this.uploadStartTime;
this.uploadingProgress.complete();
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { CommonApiService } from './common-api.service';
describe('CommonApiService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: CommonApiService = TestBed.get(CommonApiService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,295 @@
import { Injectable, Inject } from '@angular/core';
import {
HttpClient,
HttpEventType,
HttpResponse,
HttpRequest
} from '@angular/common/http';
import { Observable, Subject } from 'rxjs';
import { map, filter } from 'rxjs/operators';
import { _MODULE_CONFIG } from '../types/token';
import { ModuleConfig } from '../types/module-config';
import {
FileProfileSaveRequest,
FileProfileSaveResponse,
encodeFileProfileSave,
decodeFileProfileSave
} from '../apis/file-profile-save';
import {
FileTalkDownloadRequest,
encodeFileTalkDownload,
encodeFormDataFileTalkDownload
} from '../apis/file-talk-download';
import {
FileTalkSaveRequest,
FileTalkSaveResponse,
encodeFileTalkSave,
decodeFileTalkSave
} from '../apis/file-talk-save';
import {
FileTalkShareRequest,
FileTalkShareResponse,
encodeFileTalkShare,
decodeFileTalkShare
} from '../apis/file-talk-share';
import {
MassTalkDownloadRequest,
MassTalkDownloadResponse,
encodeMassTalkDownload,
decodeMassTalkDownload
} from '../apis/mass-talk-download';
import {
MassTalkSaveRequest,
MassTalkSaveResponse,
encodeMassTalkSave,
decodeMassTalkSave
} from '../apis/mass-talk-save';
import {
TransMassTalkDownloadRequest,
TransMassTalkDownloadResponse,
encodeTransMassTalkDownload,
decodeTransMassTalkDownload
} from '../apis/trans-mass-talk-download';
import {
TransMassTalkSaveRequest,
TransMassTalkSaveResponse,
encodeTransMassTalkSave,
decodeTransMassTalkSave
} from '../apis/trans-mass-talk-save';
import {
TranslationReqRequest,
TranslationReqResponse,
encodeTranslationReq,
decodeTranslationReq
} from '../apis/translation-req';
import {
TranslationSaveRequest,
TranslationSaveResponse,
encodeTranslationSave,
decodeTranslationSave
} from '../apis/translation-save';
@Injectable({
providedIn: 'root'
})
export class CommonApiService {
constructor(
@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig,
private httpClient: HttpClient
) {}
public fileProfileSave(
req: FileProfileSaveRequest,
fileProfileSaveUrl?: string
): Observable<FileProfileSaveResponse> {
return this.httpClient
.post<any>(
!!fileProfileSaveUrl
? fileProfileSaveUrl
: this.moduleConfig.urls.fileProfileSave,
{},
{
params: encodeFileProfileSave(req)
}
)
.pipe(map(res => decodeFileProfileSave(res)));
}
public urlForFileTalkDownload(
req: FileTalkDownloadRequest,
fileTalkDownloadUrl?: string
): string {
const httpReq = new HttpRequest(
'GET',
!!fileTalkDownloadUrl
? fileTalkDownloadUrl
: this.moduleConfig.urls.fileTalkDownload,
{},
{
params: encodeFileTalkDownload(req)
}
);
return httpReq.urlWithParams;
}
public fileTalkDownload(
req: FileTalkDownloadRequest,
fileTalkDownloadUrl?: string
): Observable<Blob> {
const httpReq = new HttpRequest(
'POST',
!!fileTalkDownloadUrl
? fileTalkDownloadUrl
: this.moduleConfig.urls.fileTalkDownload,
encodeFormDataFileTalkDownload(req),
{ reportProgress: true, responseType: 'blob' }
);
let progress: Subject<number>;
if (!!req.fileDownloadItem) {
progress = req.fileDownloadItem.downloadStart();
}
return this.httpClient.request(httpReq).pipe(
filter(event => {
if (event instanceof HttpResponse) {
return true;
} else if (HttpEventType.DownloadProgress === event.type) {
if (!!progress) {
progress.next(Math.round((100 * event.loaded) / event.total));
}
}
return false;
}),
map((event: HttpResponse<any>) => {
if (!!progress) {
req.fileDownloadItem.downloadComplete();
}
return event.body;
})
);
}
public fileTalkSave(
req: FileTalkSaveRequest,
fileTalkSaveUrl?: string
): Observable<FileTalkSaveResponse> {
const httpReq = new HttpRequest(
'POST',
!!fileTalkSaveUrl ? fileTalkSaveUrl : this.moduleConfig.urls.fileTalkSave,
encodeFileTalkSave(req),
{ reportProgress: true, responseType: 'text' as 'json' }
);
const progress = req.fileUploadItem.uploadStart();
return this.httpClient.request(httpReq).pipe(
filter(event => {
if (event instanceof HttpResponse) {
return true;
} else if (HttpEventType.UploadProgress === event.type) {
progress.next(Math.round((100 * event.loaded) / event.total));
}
return false;
}),
map((event: HttpResponse<any>) => {
req.fileUploadItem.uploadComplete();
return decodeFileTalkSave(event.body);
})
);
}
public acceptableExtensionForFileTalk(extensions: string[]): boolean {
for (const extension of extensions) {
if (
-1 === this.moduleConfig.acceptableFileExtensions.indexOf(extension.toLowerCase())
) {
return false;
}
}
return true;
}
public fileTalkShare(
req: FileTalkShareRequest
): Observable<FileTalkShareResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.fileTalkShare,
{},
{
params: encodeFileTalkShare(req)
}
)
.pipe(map(res => decodeFileTalkShare(res)));
}
public massTalkDownload(
req: MassTalkDownloadRequest
): Observable<MassTalkDownloadResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.massTalkDownload,
{},
{
params: encodeMassTalkDownload(req),
responseType: 'text' as 'json'
}
)
.pipe(map(res => decodeMassTalkDownload(res)));
}
public massTalkSave(
req: MassTalkSaveRequest
): Observable<MassTalkSaveResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.massTalkSave,
{},
{
params: encodeMassTalkSave(req),
responseType: 'text' as 'json'
}
)
.pipe(map(res => decodeMassTalkSave(res)));
}
public transMassTalkDownload(
req: TransMassTalkDownloadRequest
): Observable<TransMassTalkDownloadResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.transMassTalkDownload,
{},
{
params: encodeTransMassTalkDownload(req)
}
)
.pipe(map(res => decodeTransMassTalkDownload(res)));
}
public transMassTalkSave(
req: TransMassTalkSaveRequest
): Observable<TransMassTalkSaveResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.transMassTalkSave,
{},
{
params: encodeTransMassTalkSave(req)
}
)
.pipe(map(res => decodeTransMassTalkSave(res)));
}
public translationReq(
req: TranslationReqRequest
): Observable<TranslationReqResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.translationReq,
{},
{
params: encodeTranslationReq(req)
}
)
.pipe(map(res => decodeTranslationReq(res)));
}
public translationSave(
req: TranslationSaveRequest
): Observable<TranslationSaveResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.translationSave,
{},
{
params: encodeTranslationSave(req)
}
)
.pipe(map(res => decodeTranslationSave(res)));
}
}

View File

@ -0,0 +1,15 @@
export interface ModuleConfig {
urls: {
fileProfileSave: string;
fileTalkDownload: string;
fileTalkSave: string;
fileTalkShare: string;
massTalkDownload: string;
massTalkSave: string;
transMassTalkDownload: string;
transMassTalkSave: string;
translationReq: string;
translationSave: string;
};
acceptableFileExtensions: string[];
}

View File

@ -0,0 +1,5 @@
import { InjectionToken } from '@angular/core';
export const _MODULE_CONFIG = new InjectionToken(
'@ucap-webmessenger/api-common config of module'
);

View File

@ -0,0 +1,23 @@
import { NgModule, ModuleWithProviders } from '@angular/core';
import { _MODULE_CONFIG } from './types/token';
import { CommonApiService } from './services/common-api.service';
import { ModuleConfig } from './types/module-config';
const SERVICES = [CommonApiService];
@NgModule({
declarations: [],
imports: [],
exports: []
})
export class UCapCommonApiModule {
public static forRoot(
config: ModuleConfig
): ModuleWithProviders<UCapCommonApiModule> {
return {
ngModule: UCapCommonApiModule,
providers: [{ provide: _MODULE_CONFIG, useValue: config }, ...SERVICES]
};
}
}

View File

@ -0,0 +1,23 @@
/*
* Public API Surface of ucap-webmessenger-api-common
*/
export * from './lib/types/module-config';
export * from './lib/apis/file-profile-save';
export * from './lib/apis/file-talk-download';
export * from './lib/apis/file-talk-save';
export * from './lib/apis/file-talk-share';
export * from './lib/apis/mass-talk-download';
export * from './lib/apis/mass-talk-save';
export * from './lib/apis/trans-mass-talk-download';
export * from './lib/apis/trans-mass-talk-save';
export * from './lib/apis/translation-req';
export * from './lib/apis/translation-save';
export * from './lib/models/file-download-item';
export * from './lib/models/file-upload-item';
export * from './lib/services/common-api.service';
export * from './lib/ucap-common-api.module';

View File

@ -0,0 +1,21 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,26 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"ucapApiCommon",
"camelCase"
],
"component-selector": [
true,
"element",
"ucap-api-common",
"kebab-case"
]
}
}

View File

@ -0,0 +1,24 @@
# UcapWebmessengerApiExternal
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.4.
## Code scaffolding
Run `ng generate component component-name --project ucap-webmessenger-api-external` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ucap-webmessenger-api-external`.
> Note: Don't forget to add `--project ucap-webmessenger-api-external` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build ucap-webmessenger-api-external` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build ucap-webmessenger-api-external`, go to the dist folder `cd dist/ucap-webmessenger-api-external` and run `npm publish`.
## Running unit tests
Run `ng test ucap-webmessenger-api-external` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/ucap-webmessenger-api-external'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/ucap-webmessenger-api-external",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@ -0,0 +1,8 @@
{
"name": "@ucap-webmessenger/api-external",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.4",
"@angular/core": "^8.2.4"
}
}

View File

@ -0,0 +1,39 @@
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil
} from '@ucap-webmessenger/api';
export interface CheckUserInfoExRequest extends APIRequest {
userId: string;
companyCode: string;
userSession: string;
}
export interface CheckUserInfoExResponse extends APIResponse {
userId?: string;
}
const checkUserInfoExEncodeMap = {
userId: 'p_user_id',
companyCode: 'p_comp_code',
userSession: 'p_user_session'
};
export const encodeCheckUserInfoEx: APIEncoder<CheckUserInfoExRequest> = (
req: CheckUserInfoExRequest
) => {
return ParameterUtil.encode(checkUserInfoExEncodeMap, req);
};
export const decodeCheckUserInfoEx: APIDecoder<CheckUserInfoExResponse> = (
res: any
) => {
return {
statusCode: res.StatusCode,
errorMessage: res.ErrorMessage,
userId: res.UserID
} as CheckUserInfoExResponse;
};

View File

@ -0,0 +1,64 @@
import { AppType, DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil
} from '@ucap-webmessenger/api';
import { Company } from '../models/company';
export interface CompanyListRequest extends APIRequest {
userSeq?: number;
appType?: AppType;
deviceType?: DeviceType;
token?: string;
companyGroupCode: string;
}
export interface CompanyListResponse extends APIResponse {
companyList?: Company[];
}
const companyListEncodeMap = {
userSeq: 'p_user_seq',
appType: 'p_app_type',
deviceType: 'p_device_type',
token: 'p_token',
companyGroupCode: 'p_comp_group_code'
};
export const encodeCompanyList: APIEncoder<CompanyListRequest> = (
req: CompanyListRequest
) => {
const extraParams: any = {};
extraParams.userSeq = String(req.userSeq);
return ParameterUtil.encode(companyListEncodeMap, req, extraParams);
};
export const decodeCompanyList: APIDecoder<CompanyListResponse> = (
res: any
) => {
let companyList: Company[] | undefined;
if (!!res.CompanyList) {
companyList = [];
for (const company of res.CompanyList) {
companyList.push({
companyCode: company.COMPANY_CODE,
companyName: company.COMPANY_NAME,
companyDomain: company.COMPANY_DOMAIN,
companyConfAuthYn: company.COMPANY_CONF_AUTH,
ucapUseYn: company.UCAP_USE_YN,
companyTimerChatAuthYn: company.COMPANY_TIMER_CHAT_AUTH
});
}
}
return {
statusCode: res.StatusCode,
errorMessage: res.ErrorMessage,
companyList
} as CompanyListResponse;
};

View File

@ -0,0 +1,43 @@
import { DeviceType, PushType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil
} from '@ucap-webmessenger/api';
export interface TokenUpdateRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
mobilePid?: string;
pushType?: PushType;
}
// tslint:disable-next-line: no-empty-interface
export interface TokenUpdateResponse extends APIResponse {}
const tokenUpdateEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
mobilePid: 'p_mobile_pid',
pushType: 'p_push_type'
};
export const encodeTokenUpdate: APIEncoder<TokenUpdateRequest> = (
req: TokenUpdateRequest
) => {
const extraParams: any = {};
extraParams.userSeq = String(req.userSeq);
return ParameterUtil.encode(tokenUpdateEncodeMap, req, extraParams);
};
export const decodeTokenUpdate: APIDecoder<TokenUpdateResponse> = (
res: any
) => {
return {} as TokenUpdateResponse;
};

View File

@ -0,0 +1,56 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil
} from '@ucap-webmessenger/api';
export interface UrlInfoRequest extends APIRequest {
deviceType: DeviceType;
}
export interface UrlInfoResponse extends APIResponse {
portCheckUrl?: string;
messageUrl?: string;
messageUrl2?: string;
passwordUrl?: string;
planUrl?: string;
planUrl2?: string;
vocUrl?: string;
confUrl?: string;
uprApiUrl?: string;
uprSvcUrl?: string;
uprDownloadUrl?: string;
synapViewUrl?: string;
}
const urlInfoEncodeMap = {
deviceType: 'p_device_type'
};
export const encodeUrlInfo: APIEncoder<UrlInfoRequest> = (
req: UrlInfoRequest
) => {
return ParameterUtil.encode(urlInfoEncodeMap, req);
};
export const decodeUrlInfo: APIDecoder<UrlInfoResponse> = (res: any) => {
return {
statusCode: res.StatusCode,
errorMessage: res.ErrorMessage,
portCheckUrl: res.PortCheckURL,
messageUrl: res.MsgURL,
messageUrl2: res.MsgURL2,
passwordUrl: res.PasswordURL,
planUrl: res.PlanURL,
planUrl2: res.PlanURL2,
vocUrl: res.VocURL,
confUrl: res.ConfURL,
uprApiUrl: res.UprApiURL,
uprSvcUrl: res.UprSvcURL,
uprDownloadUrl: res.UprDownloadURL,
synapViewUrl: res.SynapViewURL
} as UrlInfoResponse;
};

View File

@ -0,0 +1,8 @@
export interface Company {
companyCode?: string;
companyName?: string;
companyDomain?: string;
companyConfAuthYn?: string;
ucapUseYn?: string;
companyTimerChatAuthYn?: string;
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { ExternalApiService } from './external-api.service';
describe('ExternalApiService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ExternalApiService = TestBed.get(ExternalApiService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,92 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { _MODULE_CONFIG } from '../types/token';
import { ModuleConfig } from '../types/module-config';
import {
CheckUserInfoExRequest,
CheckUserInfoExResponse,
encodeCheckUserInfoEx,
decodeCheckUserInfoEx
} from '../apis/check-user-info-ex';
import {
CompanyListRequest,
CompanyListResponse,
encodeCompanyList,
decodeCompanyList
} from '../apis/company-list';
import {
TokenUpdateRequest,
TokenUpdateResponse,
encodeTokenUpdate,
decodeTokenUpdate
} from '../apis/token-update';
import {
UrlInfoResponse,
UrlInfoRequest,
encodeUrlInfo,
decodeUrlInfo
} from '../apis/url-info';
@Injectable({
providedIn: 'root'
})
export class ExternalApiService {
constructor(
@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig,
private httpClient: HttpClient
) {}
public checkUserInfoEx(
req: CheckUserInfoExRequest
): Observable<CheckUserInfoExResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.checkUserInfoEx,
{},
{
params: encodeCheckUserInfoEx(req)
}
)
.pipe(map(res => decodeCheckUserInfoEx(res)));
}
public companyList(req: CompanyListRequest): Observable<CompanyListResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.companyList,
{},
{
params: encodeCompanyList(req)
}
)
.pipe(map(res => decodeCompanyList(res)));
}
public tokenUpdate(req: TokenUpdateRequest): Observable<TokenUpdateResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.tokenUpdate,
{},
{
params: encodeTokenUpdate(req)
}
)
.pipe(map(res => decodeTokenUpdate(res)));
}
public urlInfo(req: UrlInfoRequest): Observable<UrlInfoResponse> {
return this.httpClient
.post<any>(
this.moduleConfig.urls.urlInfo,
{},
{
params: encodeUrlInfo(req)
}
)
.pipe(map(res => decodeUrlInfo(res)));
}
}

View File

@ -0,0 +1,8 @@
export interface ModuleConfig {
urls: {
checkUserInfoEx: string;
companyList: string;
tokenUpdate: string;
urlInfo: string;
};
}

View File

@ -0,0 +1,5 @@
import { InjectionToken } from '@angular/core';
export const _MODULE_CONFIG = new InjectionToken(
'@ucap-webmessenger/api-external config of module'
);

View File

@ -0,0 +1,23 @@
import { NgModule, ModuleWithProviders } from '@angular/core';
import { _MODULE_CONFIG } from './types/token';
import { ExternalApiService } from './services/external-api.service';
import { ModuleConfig } from './types/module-config';
const SERVICES = [ExternalApiService];
@NgModule({
declarations: [],
imports: [],
exports: []
})
export class UCapExternalApiModule {
public static forRoot(
config: ModuleConfig
): ModuleWithProviders<UCapExternalApiModule> {
return {
ngModule: UCapExternalApiModule,
providers: [{ provide: _MODULE_CONFIG, useValue: config }, ...SERVICES]
};
}
}

View File

@ -0,0 +1,16 @@
/*
* Public API Surface of ucap-webmessenger-api-public
*/
export * from './lib/types/module-config';
export * from './lib/models/company';
export * from './lib/apis/check-user-info-ex';
export * from './lib/apis/company-list';
export * from './lib/apis/token-update';
export * from './lib/apis/url-info';
export * from './lib/services/external-api.service';
export * from './lib/ucap-external-api.module';

View File

@ -0,0 +1,21 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,26 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

Some files were not shown because too many files have changed in this diff Show More