├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── apex ├── demo_app.sql └── plugins │ ├── init_websocket_notify_connection │ ├── dynamic_action_plugin_de_danielh_initwsnotifyconnection.sql │ └── plugin_files │ │ ├── initNotifyWebsocket.js │ │ ├── initNotifyWebsocket.min.js │ │ ├── socket.io.js │ │ ├── socket.io.js.map │ │ └── socket.io.min.js │ ├── send_websocket_notify │ ├── dynamic_action_plugin_de_danielh_sendwsnotify.sql │ └── plugin_files │ │ ├── sendNotifyWebsocket.js │ │ └── sendNotifyWebsocket.min.js │ └── show_websocket_notify │ ├── dynamic_action_plugin_de_danielh_showwsnotify.sql │ └── plugin_files │ ├── css │ ├── alertify.css │ ├── alertify.min.css │ ├── default.css │ └── default.min.css │ └── js │ ├── alertify.js │ ├── alertify.min.js │ ├── showNotifyWebsocket.js │ └── showNotifyWebsocket.min.js ├── apexplugin.json ├── docs ├── README.pdf ├── infrastructure_diagram.png ├── plsqldoc │ ├── frame.html │ ├── frame_Index.html │ ├── frame_home.html │ ├── index.html │ ├── plsqldoc.css │ ├── ws_notify_api.body.html │ └── ws_notify_api.html └── preview.gif ├── node └── node-notify-server │ ├── certs │ ├── cert.pem │ ├── create_cert.sh │ └── key.pem │ ├── client.html │ ├── index.html │ ├── localstore.js │ ├── package.json │ ├── prefs.js │ ├── prefs.json │ ├── server.js │ └── srvhelper.js └── plsql ├── ws_notify_api.pkb └── ws_notify_api.pks /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | 3 | 4 | ### Actual behavior 5 | 6 | 7 | ### Steps to reproduce the issue 8 | 9 | 10 | ### APEX version 11 | 12 | 13 | ### Database version and platform 14 | 15 | 16 | ### Node version and platform 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Daniel Hochleitner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # APEX Websocket Notification Bundle 2 | 3 | [![APEX Community](https://cdn.rawgit.com/Dani3lSun/apex-github-badges/78c5adbe/badges/apex-community-badge.svg)](https://github.com/Dani3lSun/apex-github-badges) [![APEX Plugin](https://cdn.rawgit.com/Dani3lSun/apex-github-badges/b7e95341/badges/apex-plugin-badge.svg)](https://github.com/Dani3lSun/apex-github-badges) 4 | [![APEX Built with Love](https://cdn.rawgit.com/Dani3lSun/apex-github-badges/7919f913/badges/apex-love-badge.svg)](https://github.com/Dani3lSun/apex-github-badges) 5 | 6 | 7 | **Table of Contents** 8 | 9 | - [APEX Websocket Notification Bundle](#apex-websocket-notification-bundle) 10 | - [Demo](#demo) 11 | - [Preview](#preview) 12 | - [Changelog](#changelog) 13 | - [Installation and Configuration](#installation-and-configuration) 14 | - [Installation Node.js Server](#installation-nodejs-server) 15 | - [Install Node.js](#install-nodejs) 16 | - [Install Notification Package](#install-notification-package) 17 | - [Configure Notification Package](#configure-notification-package) 18 | - [Installation Database](#installation-database) 19 | - [Database ACL](#database-acl) 20 | - [Oracle SSL Wallet](#oracle-ssl-wallet) 21 | - [Compile the PL/SQL package](#compile-plsql-package) 22 | - [Installation APEX](#installation-apex) 23 | - [Install Plugins](#install-plugins) 24 | - [Usage](#usage) 25 | - [Node.js Server](#nodejs-server) 26 | - [REST-Service](#rest-service) 27 | - [PL/SQL API](#plsql-api) 28 | - [List of global package variables](#list-of-global-package-variables) 29 | - [List of all procedures with all parameters](#list-of-all-procedures-with-all-parameters) 30 | - [APEX](#apex) 31 | - [Init Websocket Notify Connection](#init-websocket-notify-connection) 32 | - [Send Websocket Notify](#send-websocket-notify) 33 | - [Show Websocket Notify](#show-websocket-notify) 34 | - [License](#license) 35 | 36 | 37 | Purpose of this software bundle was to enable all APEX developers to use modern and state of the art web features like Node, Websockets and nice looking notifications in their applications. 38 | 39 | This bundle includes all these features and simultaneously is designed to use all of them out of the box. This means: 40 | 41 | - Ready to go Node.js websocket server especially for notifications using socket.io 42 | - A native PL/SQL package to send all kinds of different messages/notifications using APEX_WEB_SERVICE 43 | - APEX plugins for all kind of events that are needed by the notification system: 44 | 45 | - Initialize websocket connection to server 46 | - Send messages and notifications to users 47 | - Show different styled notifications on client side 48 | 49 | Developers don´t need to be experts in Javascript or JQuery and stuff like that (But as always, it´s not a bad skill!;) ). APEX & PL/SQL Know-How and a good knowledge of using Dynamic Actions should be enough to implement this notification bundle in your applications... 50 | 51 | #### Infrastructure Diagram 52 | ![](https://github.com/Dani3lSun/apex-websocket-notify-bundle/blob/master/docs/infrastructure_diagram.png?raw=true) 53 | 54 | 55 | ## Demo 56 | 57 | A demo application is available under 58 | https://apex.danielh.de/ords/f?p=WSNOTIFY 59 | 60 | And of course you find a APEX export (**demo_app.sql**) of it in [../apex/](https://github.com/Dani3lSun/apex-websocket-notify-bundle/tree/master/apex) folder. To use it just import the app and then go through the installation steps below. 61 | Under Shared Components --> Edit Application Definition --> Substitutions Strings, set 62 | 63 | - **G_WS_SERVER_HOST** to the hostname or ip address of your node notification server 64 | - **G_WS_SERVER_PORT** to the port of your node notification server 65 | - **G_WS_SERVER_AUTHTOKEN** to your secure and random authToken of your node notification server (read further for more informations) 66 | 67 | The demo includes all plugins and shows the most common preferences and possibilities. 68 | 69 | 70 | ## Preview 71 | ![](https://github.com/Dani3lSun/apex-websocket-notify-bundle/blob/master/docs/preview.gif?raw=true) 72 | 73 | 74 | ## Changelog 75 | 76 | #### [1.2.0 - Updated Javascript Libs (socket.io 1.7.4 & Alertify 1.10.0) / Improved all bundled APEX plugins enhancements](https://github.com/Dani3lSun/apex-websocket-notify-bundle/releases/tag/v1.2.0) 77 | 78 | #### [1.1.0 - Updated Javascript Libs (socket.io 1.5.1 & Alertify 1.8.0) / Send Websocket Notify Plugin enhancements](https://github.com/Dani3lSun/apex-websocket-notify-bundle/releases/tag/v1.1.0) 79 | 80 | #### [1.0.0 - Initial Release](https://github.com/Dani3lSun/apex-websocket-notify-bundle/releases/tag/v1.0.0) 81 | 82 | 83 | ## Installation and Configuration 84 | 85 | ### Installation Node.js Server 86 | 87 | #### Install Node.js 88 | 89 | It is required to have a up and running Node.js installation on your server. 90 | Either install it using a package manager, or download the latest version from [Nodejs homepage](https://nodejs.org)...for example: 91 | - Ubuntu: 92 | ``` 93 | apt-get install nodejs 94 | apt-get install npm 95 | ``` 96 | 97 | - Mac OS X (Homebrew): 98 | ``` 99 | brew install nodejs 100 | ``` 101 | 102 | - Windows: 103 | Download and install it from [Nodejs homepage](https://nodejs.org) 104 | 105 | npm is the package manager for Node applications. npm is used to install all required packages by the Node Websocket Notification Server... 106 | 107 | #### Install Notification Package 108 | 109 | - Copy the complete folder [../node/node-notify-server](https://github.com/Dani3lSun/apex-websocket-notify-bundle/tree/master/node/node-notify-server) to your server 110 | - change to this directory via command line: 111 | ``` 112 | cd /path/to/node-notify-server 113 | ``` 114 | - Install all dependencies 115 | ``` 116 | npm install 117 | ``` 118 | - Start server 119 | ``` 120 | npm start 121 | ``` 122 | 123 | This should be everything to have the Notification Server up und running. To check that, you can point your web browser to http://[host-ip-of-server]:8080 124 | 125 | There you should get a overview of all supported services by the Notification Server. 126 | 127 | This helper pages are supported by the server: 128 | 129 | - **Overview Services:** http://[host-ip-of-server]:8080 130 | - **Server Status Page:** http://[host-ip-of-server]:8080/status 131 | - **Websocket Test Client:** http://[host-ip-of-server]:8080/testclient 132 | 133 | #### Configure Notification Package 134 | 135 | You can change the default behavior of the server by editing the JSON config file [../node/node-notify-server/prefs.json](https://github.com/Dani3lSun/apex-websocket-notify-bundle/blob/master/node/node-notify-server/prefs.json) 136 | 137 | ``` 138 | { 139 | "server": { 140 | "ip": "0.0.0.0", // listener ip address 0.0.0.0 for all interfaces 141 | "port": "8080", // listener port 142 | "authUser": "", // User for HTTP basic auth, empty means no user auth (only REST-Interface) 143 | "authPwd": "", // Password for HTTP basic auth, empty means no user auth (only REST-Interface) 144 | "sslKeyPath": "", // FOR SSL: path to ssl key file (./certs/key.pem), empty means no SSL/HTTPS 145 | "sslCertPath": "", // FOR SSL: path to ssl certificate file (./certs/cert.pem), empty means no SSL/HTTPS 146 | "logging": true // logging to console on or off, for prod disable logging 147 | }, 148 | "socket": { 149 | "private": true, // activate private websocket room/namespace of server 150 | "public": true, // activate public websocket room/namespace of server 151 | "authToken":"please-change-me" // authentication token, client should have the same to connect with websocket, please change it to some random string 152 | } 153 | } 154 | ``` 155 | 156 | After changing one of these settings, please restart the Node Notification Server. 157 | 158 | SSL Support: 159 | - For test environments you can use the script [../node/node-notify-server/certs/create_cert.sh](https://github.com/Dani3lSun/apex-websocket-notify-bundle/blob/master/node/node-notify-server/certs/create_cert.sh) to create a self signed certificate 160 | - For production environments please get a officially signed certificate and place key.pem and cert.pem into the certs folder 161 | 162 | 163 | ### Installation Database 164 | 165 | #### Database ACL 166 | 167 | All notifications are sent through web service requests. Therefore a ACL is needed, so you are allowed to connect to this host. Here is a example script, configure it to reflect your environment... 168 | 169 | ```language-sql 170 | DECLARE 171 | -- 172 | l_filename VARCHAR2(30) := 'ws-notify-host.xml'; 173 | l_host_or_ip VARCHAR2(50) := '' 174 | l_port NUMBER := 8080; 175 | l_schema VARCHAR2(20) := ''; 176 | -- 177 | BEGIN 178 | -- 179 | BEGIN 180 | dbms_network_acl_admin.drop_acl(acl => l_filename); 181 | EXCEPTION 182 | WHEN OTHERS THEN 183 | NULL; 184 | END; 185 | -- 186 | dbms_network_acl_admin.create_acl(acl => l_filename, 187 | description => 'All requests to Node Websocket Server', 188 | principal => l_schema, 189 | is_grant => TRUE, 190 | privilege => 'connect'); 191 | -- 192 | dbms_network_acl_admin.add_privilege(acl => l_filename, 193 | principal => l_schema, 194 | is_grant => TRUE, 195 | privilege => 'resolve'); 196 | -- 197 | dbms_network_acl_admin.assign_acl(acl => l_filename, 198 | host => l_host_or_ip, 199 | lower_port => l_port); 200 | END; 201 | / 202 | ``` 203 | 204 | #### Oracle SSL Wallet 205 | 206 | If you configured the Node Notification Server with SSL/HTTPS support, a Oracle SSL Wallet is needed by the database to communicate with the REST-Interface for sending notifications. 207 | 208 | To manually create a wallet, either use Oracle Wallet Manager or create the wallet with openssl utils like: 209 | - Grab the certificate from your server [node-notify-server/certs](https://github.com/Dani3lSun/apex-websocket-notify-bundle/tree/master/node/node-notify-server/certs) 210 | - Create the wallet on command line 211 | 212 | ```shell 213 | openssl pkcs12 -export -in cert.pem -out ewallet.p12 -nokeys 214 | ``` 215 | 216 | - Place the wallet file on your database server 217 | - Change the wallet path and password in [package specification](https://github.com/Dani3lSun/apex-websocket-notify-bundle/blob/master/plsql/ws_notify_api.pks) under "Websocket REST Call defaults / security defaults" 218 | - **g_ssl_wallet_path:** Path of Oracle SSL wallet 219 | - **g_ssl_wallet_pwd:** Password of Oracle SSL wallet 220 | 221 | 222 | #### Compile PL/SQL package 223 | 224 | - Change the global variables in the [package specification](https://github.com/Dani3lSun/apex-websocket-notify-bundle/blob/master/plsql/ws_notify_api.pks) under "Websocket REST Call defaults" to reflect your environment 225 | - **g_ws_rest_host:** Hostname or IP of Node Server 226 | - **g_ws_rest_port:** Port of Node Server 227 | - **g_ws_rest_proto:** Protocol of Node Server (http or https) - if https, then "g_ssl_wallet_path" and "g_ssl_wallet_pwd" are required 228 | - **g_ws_basic_auth_user:** HTTP Basic Auth username of Node Server (REST-Interface) 229 | - **g_ws_basic_auth_pwd:** HTTP Basic Auth password of Node Server (REST-Interface) 230 | - Connect to your database and compile the package spec and body (ws_notify_api.pks & ws_notify_api.pkb) from [../plsql](https://github.com/Dani3lSun/apex-websocket-notify-bundle/tree/master/plsql) folder 231 | 232 | 233 | ### Installation APEX 234 | 235 | #### Install Plugins 236 | 237 | The APEX part contains 3 plugins, you can find it in [../apex/plugins](https://github.com/Dani3lSun/apex-websocket-notify-bundle/tree/master/apex/plugins) folder. 238 | Just import these 3 files to your application and you are ready to go. 239 | 240 | - **Init Websocket Notify Connection** - dynamic_action_plugin_de_danielh_initwsnotifyconnection.sql 241 | - **Send Websocket Notify** - dynamic_action_plugin_de_danielh_sendwsnotify.sql 242 | - **Show Websocket Notify** - dynamic_action_plugin_de_danielh_showwsnotify.sql 243 | 244 | For a detailed description of the plugins, read further under **"Usage Section"** or import the demo app sql file to your workspace. 245 | 246 | 247 | ## Usage 248 | 249 | ### Node.js Server 250 | 251 | As mentioned in the installation steps, the node notification server component consists of 3 areas: 252 | 253 | - **REST-Interface** 254 | 255 | Sending messages and notifications to users which are connected to the websocket interface. 256 | 257 | - **Websocket-Interface** 258 | 259 | Connecting and authenticating users against the node server and still more to receive live messages on client browser from server part. 260 | There exists 2 rooms/namespaces which users can subscribe to: 261 | 262 | - **private** - For single user messages to all instances of one user (e.g. one user is logged in with 3 browsers) 263 | - **public** - For single user messages to all instances of one user *AND* broadcasting messages to all connected clients 264 | 265 | - **Helper pages** 266 | 267 | Helper pages to get informations about services, status of server and a test client page to test some websocket interactions. 268 | 269 | - **Overview Services:** http://[host-ip-of-server]:8080 270 | - **Server Status Page:** http://[host-ip-of-server]:8080/status 271 | - **Websocket Test Client:** http://[host-ip-of-server]:8080/testclient 272 | 273 | General settings of the node server like IP, port, authentication, SSL support and active websocket rooms can be configured with [../node/node-notify-server/prefs.json](https://github.com/Dani3lSun/apex-websocket-notify-bundle/blob/master/node/node-notify-server/prefs.json) file as mentioned above. 274 | 275 | ### REST-Service 276 | 277 | The REST-Service is designed to send messages to connected websocket users. Base-URL scheme looks like this: 278 | 279 | ``` 280 | Type: GET 281 | http://[host-ip-of-server]:[port]/notifyuser 282 | ``` 283 | 284 | - **URL-Parameter** 285 | - **userid** (required) - User-ID of connected user, *in APEX APP_USER is used* 286 | - **room** (required) - Websocket room - *valid values: private, public* 287 | - **type** (required) - Notification type - *valid values: info, success, warn, error* 288 | - **optparam** (optional) - Optional Parameter string to send any kind of information to the websocket client 289 | 290 | - **HTTP Header-Variables** 291 | - **notify-title** (required) - Title of notification 292 | - **notify-message** (required) - Message content of notification 293 | 294 | A demo call using curl looks like this: 295 | 296 | ``` 297 | curl -H "notify-title: Test Title Text" -H "notify-message: Test Message Text" "http://[host-ip-of-server]:[port]/notifyuser?userid=daniel&room=private&type=info&optparam=myoptionalinfo123" 298 | ``` 299 | 300 | ### PL/SQL API 301 | 302 | The PL/SQL API consists of one package **ws_notify_api** and includes many procedures to send any kind of possible notifications over the REST-Interface. It can be used to send notifications to users via PL/SQL or inside of APEX. All web service requests are based on APEX package APEX_WEB_SERVICE. 303 | 304 | #### List of global package variables 305 | 306 | - **g_ws_rest_host** - Node Notification Server Hostname or IP 307 | - **g_ws_rest_port** - Node Notification Server Port 308 | - **g_ws_rest_path** - Node Notification Server REST-Service Base Path 309 | - **g_ws_rest_proto** - Node Notification Server Protocol (http or https) 310 | - **g_ws_rest_base_url** - Combines Protocol, Host, Port and Path 311 | - **g_ws_basic_auth_user** - HTTP Basic Auth username of Node Server (REST-Interface) 312 | - **g_ws_basic_auth_pwd** - HTTP Basic Auth password of Node Server (REST-Interface) 313 | - **g_ssl_wallet_path** - If https, path to oracle wallet 314 | - **g_ssl_wallet_pwd** - If https, password of oracle wallet 315 | 316 | 317 | #### List of all procedures with all parameters 318 | 319 | **Procedure:** do_rest_notify_user 320 | 321 | **Purpose:** Send Websocket Notifications over REST to connected users (General sending procedure with all parameters) 322 | 323 | **Parameter:** 324 | - **i_userid** (required) 325 | - **i_room** (required) - ("private" or "public") 326 | - **i_type** (required) - (info, success, warn, error) 327 | - **i_title** (required) 328 | - **i_message** (required) 329 | - **i_optparam** (optional) - (Optional Parameter String) 330 | 331 | ---- 332 | 333 | **Procedure:** do_notify_user_private_info 334 | 335 | **Purpose:** Send Websocket Notification to User / Room: Private / Type: Info 336 | 337 | **Parameter:** 338 | - **i_userid** (required) 339 | - **i_title** (required) 340 | - **i_message** (required) 341 | - **i_optparam** (optional) - (Optional Parameter String) 342 | 343 | ---- 344 | 345 | **Procedure:** do_notify_user_private_success 346 | 347 | **Purpose:** Send Websocket Notification to User / Room: Private / Type: Success 348 | 349 | **Parameter:** 350 | - **i_userid** (required) 351 | - **i_title** (required) 352 | - **i_message** (required) 353 | - **i_optparam** (optional) - (Optional Parameter String) 354 | 355 | ---- 356 | 357 | **Procedure:** do_notify_user_private_warn 358 | 359 | **Purpose:** Send Websocket Notification to User / Room: Private / Type: Warn 360 | 361 | **Parameter:** 362 | - **i_userid** (required) 363 | - **i_title** (required) 364 | - **i_message** (required) 365 | - **i_optparam** (optional) - (Optional Parameter String) 366 | 367 | ---- 368 | 369 | **Procedure:** do_notify_user_private_error 370 | 371 | **Purpose:** Send Websocket Notification to User / Room: Private / Type: Error 372 | 373 | **Parameter:** 374 | - **i_userid** (required) 375 | - **i_title** (required) 376 | - **i_message** (required) 377 | - **i_optparam** (optional) - (Optional Parameter String) 378 | 379 | ---- 380 | 381 | **Procedure:** do_notify_user_public_info 382 | 383 | **Purpose:** Send Websocket Notification to User / Room: Public / Type: Info 384 | 385 | **Parameter:** 386 | - **i_userid** (required) 387 | - **i_title** (required) 388 | - **i_message** (required) 389 | - **i_optparam** (optional) - (Optional Parameter String) 390 | 391 | ---- 392 | 393 | **Procedure:** do_notify_user_public_success 394 | 395 | **Purpose:** Send Websocket Notification to User / Room: Public / Type: Success 396 | 397 | **Parameter:** 398 | - **i_userid** (required) 399 | - **i_title** (required) 400 | - **i_message** (required) 401 | - **i_optparam** (optional) - (Optional Parameter String) 402 | 403 | ---- 404 | 405 | **Procedure:** do_notify_user_public_warn 406 | 407 | **Purpose:** Send Websocket Notification to User / Room: Public / Type: Warn 408 | 409 | **Parameter:** 410 | - **i_userid** (required) 411 | - **i_title** (required) 412 | - **i_message** (required) 413 | - **i_optparam** (optional) - (Optional Parameter String) 414 | 415 | ---- 416 | 417 | **Procedure:** do_notify_user_public_error 418 | 419 | **Purpose:** Send Websocket Notification to User / Room: Public / Type: Error 420 | 421 | **Parameter:** 422 | - **i_userid** (required) 423 | - **i_title** (required) 424 | - **i_message** (required) 425 | - **i_optparam** (optional) - (Optional Parameter String) 426 | 427 | ---- 428 | 429 | **Procedure:** do_notify_all_public_info 430 | 431 | **Purpose:** Send Websocket Notification to all Users / Room: Public / Type: Info 432 | 433 | **Parameter:** 434 | - **i_title** (required) 435 | - **i_message** (required) 436 | - **i_optparam** (optional) - (Optional Parameter String) 437 | 438 | ---- 439 | 440 | **Procedure:** do_notify_all_public_success 441 | 442 | **Purpose:** Send Websocket Notification to all Users / Room: Public / Type: Success 443 | 444 | **Parameter:** 445 | - **i_title** (required) 446 | - **i_message** (required) 447 | - **i_optparam** (optional) - (Optional Parameter String) 448 | 449 | ---- 450 | 451 | **Procedure:** do_notify_all_public_warn 452 | 453 | **Purpose:** Send Websocket Notification to all Users / Room: Public / Type: Warn 454 | 455 | **Parameter:** 456 | - **i_title** (required) 457 | - **i_message** (required) 458 | - **i_optparam** (optional) - (Optional Parameter String) 459 | 460 | ---- 461 | 462 | **Procedure:** do_notify_all_public_error 463 | 464 | **Purpose:** Send Websocket Notification to all Users / Room: Public / Type: Error 465 | 466 | **Parameter:** 467 | - **i_title** (required) 468 | - **i_message** (required) 469 | - **i_optparam** (optional) - (Optional Parameter String) 470 | 471 | ---- 472 | 473 | **A demo call could look like this:** 474 | 475 | ```language-sql 476 | BEGIN 477 | ws_notify_api.do_rest_notify_user(i_userid => 'daniel', 478 | i_room => 'private', 479 | i_type => 'info', 480 | i_title => 'My test title', 481 | i_message => 'My test message content...', 482 | i_optparam => 'myoptionalinfo123'); 483 | END; 484 | ``` 485 | 486 | 487 | ### APEX 488 | 489 | As already mentioned above, the APEX part contains 3 plugins to cover all functionalities from initialization of a websocket connection, sending notifications to other connected users to show incoming notifications. All 3 plugin files are located under [../apex/plugins](https://github.com/Dani3lSun/apex-websocket-notify-bundle/tree/master/apex/plugins) folder. 490 | 491 | #### Init Websocket Notify Connection 492 | 493 | - **Plugin File:** dynamic_action_plugin_de_danielh_initwsnotifyconnection.sql 494 | 495 | - **Purpose:** Initialize a connection to the websocket server, for general usage over all pages of your APEX application this plugin should be located on Global Page 0 (Zero) 496 | 497 | - **Plugin Attributes:** 498 | - **Use SSL** - Choose if the connection to the websocket server is secure (HTTPS) or plain (HTTP) 499 | - **Server Hostname or IP** - The hostname or ip address of the websocket server 500 | - **Server Port** - The port of the websocket server 501 | - **Websocket Type or Room** - The type/room of the websocket server. There are 2 possible connections: private, public 502 | - **Websocket Auth User-ID** - User-ID which connects / authenticates against the websocket server. Is used to identify a user. Default: APEX APP_USER 503 | - **Websocket Auth-Token** - Auth-Token of the Node Notify Websocket Server. This is to increase security. 504 | - **Logging** - Whether to log events in the console 505 | 506 | - **Plugin Events:** 507 | - **Private Websocket Connection success** - Successfully connected to private room 508 | - **Private Websocket Connection error** - Error connecting to private room 509 | - **Private Websocket Disconnected** - Websocket connection is disconnected for private room 510 | - **Public Websocket Connection success** - Successfully connected to public room 511 | - **Public Websocket Connection error** - Error connecting to public room 512 | - **Public Websocket Disconnected** - Websocket connection is disconnected for public room 513 | - **Receive Private Message** - Receiving an incoming private message 514 | - **Receive Public Message** - Receiving an incoming public message 515 | 516 | ---- 517 | 518 | #### Send Websocket Notify 519 | 520 | - **Plugin File:** dynamic_action_plugin_de_danielh_sendwsnotify.sql 521 | 522 | - **Purpose:** Send websocket notifications to other connected users or to all connected users 523 | 524 | - **Plugin Attributes:** 525 | - **Source** - Source of all Notification relevant informations (Values: Items / SQL Query) 526 | - **To User (User-ID)** - Item which holds informations about the User-ID or Username of the user who get´s the notification 527 | - **Websocket Room** - Item which holds informations about the websocket room - Valid values: "private" or "public" 528 | - **Notification Type** - Item which holds informations about the type of the notification - Valid values: info, success, warn, error 529 | - **Notification Title** - Item which holds informations about the title of the notification 530 | - **Notification Message** - Item which holds informations about the message content of the notification 531 | - **Optional Parameter** - Item which holds informations about a optional parameter - This could be any kind of string or number combination. This information can be processed on the client side 532 | - **SQL Query** - SQL Query which returns all relevant informations for sending a notification. The Query should only return one row! 533 | - **Show Wait Spinner** - Show / Hide wait spinner for AJAX call 534 | - **Escape HTML** - Whether to escape special chars (HTML) or not 535 | - **Logging** - Whether to log events in the console 536 | 537 | - **Plugin Events:** 538 | - **Send Notification success** - Sending a notification was successfull 539 | - **Send Notification error** - Error sending a notification 540 | - **Send Notification missing values** - Missing required parameters for sending a notification 541 | 542 | **Example SQL Source Query:** 543 | 544 | ```language-sql 545 | SELECT 'MYUSER' AS user_id, 546 | 'private' AS room, -- private, public 547 | 'info' AS notify_type, -- info, success, warn, error 548 | 'Test Title' AS title, 549 | 'My Test Message Content...' AS message, 550 | '123:abc' AS optional_parameter 551 | FROM dual 552 | ``` 553 | 554 | ---- 555 | 556 | #### Show Websocket Notify 557 | 558 | - **Plugin File:** dynamic_action_plugin_de_danielh_showwsnotify.sql 559 | 560 | - **Purpose:** Show notifications for incoming websocket message events. Notifications UI based on [AlertifyJS](https://github.com/MohammadYounes/AlertifyJS) 561 | 562 | - **Plugin Attributes:** 563 | - **Notification Icon CSS Class** - Icon CSS class for the incoming notification - Default: fa-bell 564 | - **Notification Wait Time** - Time (in seconds) to wait before the notification is dismissed, a value of 0 means keep open till clicked. 565 | - **Notification Position** - Position of the notification message on screen 566 | - **Logging** - Whether to log events in the console 567 | 568 | All other parameters of an notification object (title, message, type (info, success, warn, error), etc.) are automatically fetched from the websocket message event. 569 | 570 | - **Plugin Events:** 571 | - **Private Notification clicked** - Clicked on a private notification object 572 | - **Public Notification clicked** - Clicked on a public notification object 573 | 574 | 575 | ## License 576 | 577 | This software is under [**MIT License**](https://github.com/Dani3lSun/apex-websocket-notify-bundle/blob/master/LICENSE). 578 | -------------------------------------------------------------------------------- /apex/plugins/init_websocket_notify_connection/plugin_files/initNotifyWebsocket.js: -------------------------------------------------------------------------------- 1 | // Init Websocket Notify Connection 2 | // Author: Daniel Hochleitner 3 | // Version: 1.2 4 | 5 | // global namespace 6 | var initNotifyWebsocket = { 7 | // parse string to boolean 8 | parseBoolean: function(pString) { 9 | var pBoolean; 10 | if (pString.toLowerCase() == 'true') { 11 | pBoolean = true; 12 | } 13 | if (pString.toLowerCase() == 'false') { 14 | pBoolean = false; 15 | } 16 | if (!(pString.toLowerCase() == 'true') && !(pString.toLowerCase() == 'false')) { 17 | pBoolean = undefined; 18 | } 19 | return pBoolean; 20 | }, 21 | // function that gets called from plugin 22 | initConnection: function() { 23 | // plugin attributes 24 | var daThis = this; 25 | var vUseSSL = parseInt(daThis.action.attribute01); 26 | var vServerHostname = daThis.action.attribute02; 27 | var vServerPort = parseInt(daThis.action.attribute03); 28 | var vWsRoom = daThis.action.attribute04; 29 | var vWsUserId = daThis.action.attribute05; 30 | var vWsAuthToken = daThis.action.attribute06; 31 | var vLogging = initNotifyWebsocket.parseBoolean(daThis.action.attribute07); 32 | // Logging 33 | if (vLogging) { 34 | console.log('initConnection: Attribute Use SSL:', vUseSSL); 35 | console.log('initConnection: Attribute Server Hostname/IP:', vServerHostname); 36 | console.log('initConnection: Attribute Server Port:', vServerPort); 37 | console.log('initConnection: Attribute Websocket Type/Room:', vWsRoom); 38 | console.log('initConnection: Attribute Websocket User-ID:', vWsUserId); 39 | console.log('initConnection: Attribute Websocket Auth-Token:', vWsAuthToken); 40 | console.log('initConnection: Attribute Logging:', vLogging); 41 | } 42 | // Websocket connection 43 | var serverBaseUrl; 44 | if (vUseSSL == 1) { 45 | serverBaseUrl = 'https://' + vServerHostname + ':' + vServerPort; 46 | } else if (vUseSSL === 0) { 47 | serverBaseUrl = 'http://' + vServerHostname + ':' + vServerPort; 48 | } 49 | // private Websocket 50 | if (vWsRoom === 'private') { 51 | // Login to socket.io services 52 | var privateSocket = io.connect(serverBaseUrl + '/private', { 53 | query: "userid=" + vWsUserId + "&authtoken=" + vWsAuthToken 54 | }); 55 | // Events 56 | // incoming message 57 | privateSocket.on('message', function(data) { 58 | // Trigger Event 59 | apex.event.trigger('body', 'ws-private-message', data); 60 | // Logging 61 | if (vLogging) { 62 | console.log('private-socket-message'); 63 | console.log('type', data.type); 64 | console.log('title', data.title); 65 | console.log('message', data.message); 66 | console.log('time', data.time); 67 | console.log('optparam', data.optparam); 68 | } 69 | }); 70 | // Connect 71 | privateSocket.on('connect', function() { 72 | // Trigger Event 73 | apex.event.trigger('body', 'ws-private-connect-success'); 74 | }); 75 | // Error 76 | privateSocket.on('error', function() { 77 | // Trigger Event 78 | apex.event.trigger('body', 'ws-private-connect-error'); 79 | }); 80 | // Disconnect 81 | privateSocket.on('disconnect', function() { 82 | // Trigger Event 83 | apex.event.trigger('body', 'ws-private-disconnect'); 84 | // Reconnect 85 | var privateSocket = io.connect(serverBaseUrl + '/private', { 86 | query: "userid=" + vWsUserId + "&authtoken=" + vWsAuthToken 87 | }); 88 | }); 89 | // public Websocket 90 | } else if (vWsRoom === 'public') { 91 | // Login to socket.io services 92 | var publicSocket = io.connect(serverBaseUrl + '/public', { 93 | query: "userid=" + vWsUserId + "&authtoken=" + vWsAuthToken 94 | }); 95 | // Events 96 | // incoming message 97 | publicSocket.on('message', function(data) { 98 | // Trigger Event 99 | apex.event.trigger('body', 'ws-public-message', data); 100 | // Logging 101 | if (vLogging) { 102 | console.log('public-socket-message'); 103 | console.log('type', data.type); 104 | console.log('title', data.title); 105 | console.log('message', data.message); 106 | console.log('time', data.time); 107 | console.log('optparam', data.optparam); 108 | } 109 | }); 110 | // Connect 111 | publicSocket.on('connect', function() { 112 | // Trigger Event 113 | apex.event.trigger('body', 'ws-public-connect-success'); 114 | }); 115 | // Error 116 | publicSocket.on('error', function() { 117 | // Trigger Event 118 | apex.event.trigger('body', 'ws-public-connect-error'); 119 | }); 120 | // Disconnect 121 | publicSocket.on('disconnect', function() { 122 | // Trigger Event 123 | apex.event.trigger('body', 'ws-public-disconnect'); 124 | // Reconnect 125 | var publicSocket = io.connect(serverBaseUrl + '/public', { 126 | query: "userid=" + vWsUserId + "&authtoken=" + vWsAuthToken 127 | }); 128 | }); 129 | } 130 | } 131 | }; 132 | -------------------------------------------------------------------------------- /apex/plugins/init_websocket_notify_connection/plugin_files/initNotifyWebsocket.min.js: -------------------------------------------------------------------------------- 1 | var initNotifyWebsocket={parseBoolean:function(a){var b;"true"==a.toLowerCase()&&(b=!0);"false"==a.toLowerCase()&&(b=!1);"true"!=a.toLowerCase()&&"false"!=a.toLowerCase()&&(b=void 0);return b},initConnection:function(){var a=parseInt(this.action.attribute01),b=this.action.attribute02,g=parseInt(this.action.attribute03),h=this.action.attribute04,d=this.action.attribute05,e=this.action.attribute06,f=initNotifyWebsocket.parseBoolean(this.action.attribute07);f&&(console.log("initConnection: Attribute Use SSL:", 2 | a),console.log("initConnection: Attribute Server Hostname/IP:",b),console.log("initConnection: Attribute Server Port:",g),console.log("initConnection: Attribute Websocket Type/Room:",h),console.log("initConnection: Attribute Websocket User-ID:",d),console.log("initConnection: Attribute Websocket Auth-Token:",e),console.log("initConnection: Attribute Logging:",f));var c;1==a?c="https://"+b+":"+g:0===a&&(c="http://"+b+":"+g);"private"===h?(a=io.connect(c+"/private",{query:"userid="+d+"&authtoken="+ 3 | e}),a.on("message",function(a){apex.event.trigger("body","ws-private-message",a);f&&(console.log("private-socket-message"),console.log("type",a.type),console.log("title",a.title),console.log("message",a.message),console.log("time",a.time),console.log("optparam",a.optparam))}),a.on("connect",function(){apex.event.trigger("body","ws-private-connect-success")}),a.on("error",function(){apex.event.trigger("body","ws-private-connect-error")}),a.on("disconnect",function(){apex.event.trigger("body","ws-private-disconnect"); 4 | io.connect(c+"/private",{query:"userid="+d+"&authtoken="+e})})):"public"===h&&(a=io.connect(c+"/public",{query:"userid="+d+"&authtoken="+e}),a.on("message",function(a){apex.event.trigger("body","ws-public-message",a);f&&(console.log("public-socket-message"),console.log("type",a.type),console.log("title",a.title),console.log("message",a.message),console.log("time",a.time),console.log("optparam",a.optparam))}),a.on("connect",function(){apex.event.trigger("body","ws-public-connect-success")}),a.on("error", 5 | function(){apex.event.trigger("body","ws-public-connect-error")}),a.on("disconnect",function(){apex.event.trigger("body","ws-public-disconnect");io.connect(c+"/public",{query:"userid="+d+"&authtoken="+e})}))}}; 6 | -------------------------------------------------------------------------------- /apex/plugins/send_websocket_notify/plugin_files/sendNotifyWebsocket.js: -------------------------------------------------------------------------------- 1 | // Send Websocket Notify 2 | // Author: Daniel Hochleitner 3 | // Version: 1.2 4 | 5 | // global namespace 6 | var sendNotifyWebsocket = { 7 | // parse string to boolean 8 | parseBoolean: function(pString) { 9 | var pBoolean; 10 | if (pString.toLowerCase() == 'true') { 11 | pBoolean = true; 12 | } 13 | if (pString.toLowerCase() == 'false') { 14 | pBoolean = false; 15 | } 16 | if (!(pString.toLowerCase() == 'true') && !(pString.toLowerCase() == 'false')) { 17 | pBoolean = undefined; 18 | } 19 | return pBoolean; 20 | }, 21 | // function that gets called from plugin 22 | sendNotify: function() { 23 | // plugin attributes 24 | var daThis = this; 25 | var vAjaxIdentifier = daThis.action.ajaxIdentifier; 26 | var vUserIdItem = daThis.action.attribute01; 27 | var vRoomItem = daThis.action.attribute02; 28 | var vTypeItem = daThis.action.attribute03; 29 | var vTitleItem = daThis.action.attribute04; 30 | var vMessageItem = daThis.action.attribute05; 31 | var vOptParamItem = daThis.action.attribute06; 32 | var vShowWaitSpinner = sendNotifyWebsocket.parseBoolean(daThis.action.attribute07); 33 | var vLogging = sendNotifyWebsocket.parseBoolean(daThis.action.attribute08); 34 | var vSource = daThis.action.attribute09; 35 | var vUserId; 36 | var vRoom; 37 | var vType; 38 | var vTitle; 39 | var vMessage; 40 | var vOptParam; 41 | // item values 42 | if (vSource == 'ITEM') { 43 | vUserId = $v(vUserIdItem); 44 | vRoom = $v(vRoomItem); 45 | vType = $v(vTypeItem); 46 | vTitle = $v(vTitleItem); 47 | vMessage = $v(vMessageItem); 48 | vOptParam = $v(vOptParamItem); 49 | } 50 | // Logging 51 | if (vLogging) { 52 | console.log('sendNotify: Plugin AjaxIdentifier:', vAjaxIdentifier); 53 | console.log('sendNotify: Source:', vSource); 54 | console.log('sendNotify: Attribute User-ID Item:', vUserIdItem); 55 | console.log('sendNotify: Attribute Websocket Room Item:', vRoomItem); 56 | console.log('sendNotify: Attribute Notification Type Item:', vTypeItem); 57 | console.log('sendNotify: Attribute Notification Title Item:', vTitleItem); 58 | console.log('sendNotify: Attribute Notification Message Item:', vMessageItem); 59 | console.log('sendNotify: Attribute Optional Parameter Item:', vOptParamItem); 60 | console.log('sendNotify: Attribute User-ID Item:', vUserId); 61 | console.log('sendNotify: Attribute Websocket Room Value:', vRoom); 62 | console.log('sendNotify: Attribute Notification Type Value:', vType); 63 | console.log('sendNotify: Attribute Notification Title Value:', vTitle); 64 | console.log('sendNotify: Attribute Notification Message Value:', vMessage); 65 | console.log('sendNotify: Attribute Optional Parameter Value:', vOptParam); 66 | console.log('sendNotify: Attribute Show Wait Spinner:', vShowWaitSpinner); 67 | console.log('sendNotify: Attribute Logging:', vLogging); 68 | } 69 | // AJAX call send Notification 70 | // check parameter values 71 | if ((vUserId && vRoom && vType && vTitle && vMessage) || vSource == 'SQL') { 72 | // show spinner 73 | if (vShowWaitSpinner) { 74 | var lSpinner$ = apex.util.showSpinner('body'); 75 | } 76 | // change defaults if all users 77 | var lRoom; 78 | if (vUserId === 'all') { 79 | lRoom = 'public'; 80 | } else { 81 | lRoom = vRoom; 82 | } 83 | $s(vRoomItem, lRoom); 84 | // async server call 85 | apex.server.plugin(vAjaxIdentifier, { 86 | x01: vUserId, 87 | x02: lRoom, 88 | x03: vType, 89 | x04: vTitle, 90 | x05: vMessage, 91 | x06: vOptParam 92 | }, { 93 | dataType: 'html', 94 | // SUCESS function 95 | success: function() { 96 | // add apex event 97 | apex.event.trigger('body', 'ws-send-notify-success'); 98 | // hide spinner 99 | if (vShowWaitSpinner) { 100 | lSpinner$.remove(); 101 | } 102 | }, 103 | // ERROR function 104 | error: function(xhr, pMessage) { 105 | // add apex event 106 | apex.event.trigger('body', 'ws-send-notify-error'); 107 | // hide spinner 108 | if (vShowWaitSpinner) { 109 | lSpinner$.remove(); 110 | } 111 | } 112 | }); 113 | } else { 114 | // add apex event 115 | apex.event.trigger('body', 'ws-send-notify-missing-values'); 116 | } 117 | } 118 | }; 119 | -------------------------------------------------------------------------------- /apex/plugins/send_websocket_notify/plugin_files/sendNotifyWebsocket.min.js: -------------------------------------------------------------------------------- 1 | var sendNotifyWebsocket={parseBoolean:function(b){var a;"true"==b.toLowerCase()&&(a=!0);"false"==b.toLowerCase()&&(a=!1);"true"!=b.toLowerCase()&&"false"!=b.toLowerCase()&&(a=void 0);return a},sendNotify:function(){var b=this.action.ajaxIdentifier,a=this.action.attribute01,k=this.action.attribute02,n=this.action.attribute03,p=this.action.attribute04,q=this.action.attribute05,r=this.action.attribute06,d=sendNotifyWebsocket.parseBoolean(this.action.attribute07),t=sendNotifyWebsocket.parseBoolean(this.action.attribute08), 2 | l=this.action.attribute09,c,e,f,g,h,m;"ITEM"==l&&(c=$v(a),e=$v(k),f=$v(n),g=$v(p),h=$v(q),m=$v(r));t&&(console.log("sendNotify: Plugin AjaxIdentifier:",b),console.log("sendNotify: Source:",l),console.log("sendNotify: Attribute User-ID Item:",a),console.log("sendNotify: Attribute Websocket Room Item:",k),console.log("sendNotify: Attribute Notification Type Item:",n),console.log("sendNotify: Attribute Notification Title Item:",p),console.log("sendNotify: Attribute Notification Message Item:",q),console.log("sendNotify: Attribute Optional Parameter Item:", 3 | r),console.log("sendNotify: Attribute User-ID Item:",c),console.log("sendNotify: Attribute Websocket Room Value:",e),console.log("sendNotify: Attribute Notification Type Value:",f),console.log("sendNotify: Attribute Notification Title Value:",g),console.log("sendNotify: Attribute Notification Message Value:",h),console.log("sendNotify: Attribute Optional Parameter Value:",m),console.log("sendNotify: Attribute Show Wait Spinner:",d),console.log("sendNotify: Attribute Logging:",t));if(c&&e&&f&&g&&h|| 4 | "SQL"==l){if(d)var u=apex.util.showSpinner("body");a="all"===c?"public":e;$s(k,a);apex.server.plugin(b,{x01:c,x02:a,x03:f,x04:g,x05:h,x06:m},{dataType:"html",success:function(){apex.event.trigger("body","ws-send-notify-success");d&&u.remove()},error:function(a,b){apex.event.trigger("body","ws-send-notify-error");d&&u.remove()}})}else apex.event.trigger("body","ws-send-notify-missing-values")}}; 5 | -------------------------------------------------------------------------------- /apex/plugins/show_websocket_notify/plugin_files/css/alertify.css: -------------------------------------------------------------------------------- 1 | /** 2 | * alertifyjs 1.10.0 http://alertifyjs.com 3 | * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications. 4 | * Copyright 2017 Mohammad Younes (http://alertifyjs.com) 5 | * Licensed under GPL 3 */ 6 | .alertify .ajs-dimmer { 7 | position: fixed; 8 | z-index: 1981; 9 | top: 0; 10 | right: 0; 11 | bottom: 0; 12 | left: 0; 13 | padding: 0; 14 | margin: 0; 15 | background-color: #252525; 16 | opacity: .5; 17 | } 18 | .alertify .ajs-modal { 19 | position: fixed; 20 | top: 0; 21 | right: 0; 22 | left: 0; 23 | bottom: 0; 24 | padding: 0; 25 | overflow-y: auto; 26 | z-index: 1981; 27 | } 28 | .alertify .ajs-dialog { 29 | position: relative; 30 | margin: 5% auto; 31 | min-height: 110px; 32 | max-width: 500px; 33 | padding: 24px 24px 0 24px; 34 | outline: 0; 35 | background-color: #fff; 36 | } 37 | .alertify .ajs-dialog.ajs-capture:before { 38 | content: ''; 39 | position: absolute; 40 | top: 0; 41 | right: 0; 42 | bottom: 0; 43 | left: 0; 44 | display: block; 45 | z-index: 1; 46 | } 47 | .alertify .ajs-reset { 48 | position: absolute !important; 49 | display: inline !important; 50 | width: 0 !important; 51 | height: 0 !important; 52 | opacity: 0 !important; 53 | } 54 | .alertify .ajs-commands { 55 | position: absolute; 56 | right: 4px; 57 | margin: -14px 24px 0 0; 58 | z-index: 2; 59 | } 60 | .alertify .ajs-commands button { 61 | display: none; 62 | width: 10px; 63 | height: 10px; 64 | margin-left: 10px; 65 | padding: 10px; 66 | border: 0; 67 | background-color: transparent; 68 | background-repeat: no-repeat; 69 | background-position: center; 70 | cursor: pointer; 71 | } 72 | .alertify .ajs-commands button.ajs-close { 73 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAh0lEQVQYlY2QsQ0EIQwEB9cBAR1CJUaI/gigDnwR6NBL/7/xWLNrZ2b8EwGotVpr7eOitWa1VjugiNB7R1UPrKrWe0dEAHBbXUqxMQbeewDmnHjvyTm7C3zDwAUd9c63YQdUVdu6EAJzzquz7HXvTiklt+H9DQFYaxFjvDqllFyMkbXWvfpXHjJrWFgdBq/hAAAAAElFTkSuQmCC); 74 | } 75 | .alertify .ajs-commands button.ajs-maximize { 76 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAOUlEQVQYlWP8//8/AzGAhYGBgaG4uBiv6t7eXkYmooxjYGAgWiELsvHYFMCcRX2rSXcjoSBiJDbAAeD+EGu+8BZcAAAAAElFTkSuQmCC); 77 | } 78 | .alertify .ajs-header { 79 | margin: -24px; 80 | margin-bottom: 0; 81 | padding: 16px 24px; 82 | background-color: #fff; 83 | } 84 | .alertify .ajs-body { 85 | min-height: 56px; 86 | } 87 | .alertify .ajs-body .ajs-content { 88 | padding: 16px 24px 16px 16px; 89 | } 90 | .alertify .ajs-footer { 91 | padding: 4px; 92 | margin-left: -24px; 93 | margin-right: -24px; 94 | min-height: 43px; 95 | background-color: #fff; 96 | } 97 | .alertify .ajs-footer .ajs-buttons.ajs-primary { 98 | text-align: right; 99 | } 100 | .alertify .ajs-footer .ajs-buttons.ajs-primary .ajs-button { 101 | margin: 4px; 102 | } 103 | .alertify .ajs-footer .ajs-buttons.ajs-auxiliary { 104 | float: left; 105 | clear: none; 106 | text-align: left; 107 | } 108 | .alertify .ajs-footer .ajs-buttons.ajs-auxiliary .ajs-button { 109 | margin: 4px; 110 | } 111 | .alertify .ajs-footer .ajs-buttons .ajs-button { 112 | min-width: 88px; 113 | min-height: 35px; 114 | } 115 | .alertify .ajs-handle { 116 | position: absolute; 117 | display: none; 118 | width: 10px; 119 | height: 10px; 120 | right: 0; 121 | bottom: 0; 122 | z-index: 1; 123 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMS8xNEDQYmMAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAQ0lEQVQYlaXNMQoAIAxD0dT7H657l0KX3iJuUlBUNOsPPCGJm7VDp6ryeMxMuDsAQH7owW3pyn3RS26iKxERMLN3ugOaAkaL3sWVigAAAABJRU5ErkJggg==); 124 | -webkit-transform: scaleX(1) /*rtl:scaleX(-1)*/; 125 | transform: scaleX(1) /*rtl:scaleX(-1)*/; 126 | cursor: se-resize; 127 | } 128 | .alertify.ajs-no-overflow .ajs-body .ajs-content { 129 | overflow: hidden !important; 130 | } 131 | .alertify.ajs-no-padding.ajs-maximized .ajs-body .ajs-content { 132 | left: 0; 133 | right: 0; 134 | padding: 0; 135 | } 136 | .alertify.ajs-no-padding:not(.ajs-maximized) .ajs-body { 137 | margin-left: -24px; 138 | margin-right: -24px; 139 | } 140 | .alertify.ajs-no-padding:not(.ajs-maximized) .ajs-body .ajs-content { 141 | padding: 0; 142 | } 143 | .alertify.ajs-no-padding.ajs-resizable .ajs-body .ajs-content { 144 | left: 0; 145 | right: 0; 146 | } 147 | .alertify.ajs-maximizable .ajs-commands button.ajs-maximize, 148 | .alertify.ajs-maximizable .ajs-commands button.ajs-restore { 149 | display: inline-block; 150 | } 151 | .alertify.ajs-closable .ajs-commands button.ajs-close { 152 | display: inline-block; 153 | } 154 | .alertify.ajs-maximized .ajs-dialog { 155 | width: 100% !important; 156 | height: 100% !important; 157 | max-width: none !important; 158 | margin: 0 auto !important; 159 | top: 0 !important; 160 | left: 0 !important; 161 | } 162 | .alertify.ajs-maximized.ajs-modeless .ajs-modal { 163 | position: fixed !important; 164 | min-height: 100% !important; 165 | max-height: none !important; 166 | margin: 0 !important; 167 | } 168 | .alertify.ajs-maximized .ajs-commands button.ajs-maximize { 169 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAASklEQVQYlZWQ0QkAMQhDtXRincOZX78KVtrDCwgqJNEoIB3MPLj7lRUROlpyVXGzby6zWuY+kz6tj5sBMTMAyVV3/595RbOh3cAXsww1raeiOcoAAAAASUVORK5CYII=); 170 | } 171 | .alertify.ajs-resizable .ajs-dialog, 172 | .alertify.ajs-maximized .ajs-dialog { 173 | padding: 0; 174 | } 175 | .alertify.ajs-resizable .ajs-commands, 176 | .alertify.ajs-maximized .ajs-commands { 177 | margin: 14px 24px 0 0; 178 | } 179 | .alertify.ajs-resizable .ajs-header, 180 | .alertify.ajs-maximized .ajs-header { 181 | position: absolute; 182 | top: 0; 183 | left: 0; 184 | right: 0; 185 | margin: 0; 186 | padding: 16px 24px; 187 | } 188 | .alertify.ajs-resizable .ajs-body, 189 | .alertify.ajs-maximized .ajs-body { 190 | min-height: 224px; 191 | display: inline-block; 192 | } 193 | .alertify.ajs-resizable .ajs-body .ajs-content, 194 | .alertify.ajs-maximized .ajs-body .ajs-content { 195 | position: absolute; 196 | top: 50px; 197 | right: 24px; 198 | bottom: 50px; 199 | left: 24px; 200 | overflow: auto; 201 | } 202 | .alertify.ajs-resizable .ajs-footer, 203 | .alertify.ajs-maximized .ajs-footer { 204 | position: absolute; 205 | left: 0; 206 | right: 0; 207 | bottom: 0; 208 | margin: 0; 209 | } 210 | .alertify.ajs-resizable:not(.ajs-maximized) .ajs-dialog { 211 | min-width: 548px; 212 | } 213 | .alertify.ajs-resizable:not(.ajs-maximized) .ajs-handle { 214 | display: block; 215 | } 216 | .alertify.ajs-movable:not(.ajs-maximized) .ajs-header { 217 | cursor: move; 218 | } 219 | .alertify.ajs-modeless .ajs-dimmer, 220 | .alertify.ajs-modeless .ajs-reset { 221 | display: none; 222 | } 223 | .alertify.ajs-modeless .ajs-modal { 224 | overflow: visible; 225 | max-width: none; 226 | max-height: 0; 227 | } 228 | .alertify.ajs-modeless.ajs-pinnable .ajs-commands button.ajs-pin { 229 | display: inline-block; 230 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAQklEQVQYlcWPMQ4AIAwCqU9u38GbcbHRWN1MvKQDhQFMEpKImGJA0gCgnYw0V0rwxseg5erT4oSkQVI5d9f+e9+xA0NbLpWfitPXAAAAAElFTkSuQmCC); 231 | } 232 | .alertify.ajs-modeless.ajs-unpinned .ajs-modal { 233 | position: absolute; 234 | } 235 | .alertify.ajs-modeless.ajs-unpinned .ajs-commands button.ajs-pin { 236 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAO0lEQVQYlWP8//8/AzGAiShV6AqLi4txGs+CLoBLMYbC3t5eRmyaWfBZhwwYkX2NTxPRvibKjRhW4wMAhxkYGbLu3pEAAAAASUVORK5CYII=); 237 | } 238 | .alertify.ajs-modeless:not(.ajs-unpinned) .ajs-body { 239 | max-height: 500px; 240 | overflow: auto; 241 | } 242 | .alertify.ajs-basic .ajs-header { 243 | opacity: 0; 244 | } 245 | .alertify.ajs-basic .ajs-footer { 246 | visibility: hidden; 247 | } 248 | .alertify.ajs-frameless .ajs-header { 249 | position: absolute; 250 | top: 0; 251 | left: 0; 252 | right: 0; 253 | min-height: 60px; 254 | margin: 0; 255 | padding: 0; 256 | opacity: 0; 257 | z-index: 1; 258 | } 259 | .alertify.ajs-frameless .ajs-footer { 260 | display: none; 261 | } 262 | .alertify.ajs-frameless .ajs-body .ajs-content { 263 | position: absolute; 264 | top: 0; 265 | right: 0; 266 | bottom: 0; 267 | left: 0; 268 | } 269 | .alertify.ajs-frameless:not(.ajs-resizable) .ajs-dialog { 270 | padding-top: 0; 271 | } 272 | .alertify.ajs-frameless:not(.ajs-resizable) .ajs-dialog .ajs-commands { 273 | margin-top: 0; 274 | } 275 | .ajs-no-overflow { 276 | overflow: hidden !important; 277 | outline: none; 278 | } 279 | .ajs-no-overflow.ajs-fixed { 280 | position: fixed; 281 | top: 0; 282 | right: 0; 283 | bottom: 0; 284 | left: 0; 285 | overflow-y: scroll!important; 286 | } 287 | .ajs-no-selection, 288 | .ajs-no-selection * { 289 | -webkit-user-select: none; 290 | -moz-user-select: none; 291 | -ms-user-select: none; 292 | user-select: none; 293 | } 294 | @media screen and (max-width: 568px) { 295 | .alertify .ajs-dialog { 296 | min-width: 150px; 297 | } 298 | .alertify:not(.ajs-maximized) .ajs-modal { 299 | padding: 0 5%; 300 | } 301 | .alertify:not(.ajs-maximized).ajs-resizable .ajs-dialog { 302 | min-width: initial; 303 | min-width: auto /*IE fallback*/; 304 | } 305 | } 306 | @-moz-document url-prefix() { 307 | .alertify button:focus { 308 | outline: 1px dotted #3593D2; 309 | } 310 | } 311 | .alertify .ajs-dimmer, 312 | .alertify .ajs-modal { 313 | -webkit-transform: translate3d(0, 0, 0); 314 | transform: translate3d(0, 0, 0); 315 | transition-property: opacity, visibility; 316 | transition-timing-function: linear; 317 | transition-duration: 250ms; 318 | } 319 | .alertify.ajs-hidden .ajs-dimmer, 320 | .alertify.ajs-hidden .ajs-modal { 321 | visibility: hidden; 322 | opacity: 0; 323 | } 324 | .alertify.ajs-in:not(.ajs-hidden) .ajs-dialog { 325 | -webkit-animation-duration: 500ms; 326 | animation-duration: 500ms; 327 | } 328 | .alertify.ajs-out.ajs-hidden .ajs-dialog { 329 | -webkit-animation-duration: 250ms; 330 | animation-duration: 250ms; 331 | } 332 | .alertify .ajs-dialog.ajs-shake { 333 | -webkit-animation-name: ajs-shake; 334 | animation-name: ajs-shake; 335 | -webkit-animation-duration: .1s; 336 | animation-duration: .1s; 337 | -webkit-animation-fill-mode: both; 338 | animation-fill-mode: both; 339 | } 340 | @-webkit-keyframes ajs-shake { 341 | 0%, 342 | 100% { 343 | -webkit-transform: translate3d(0, 0, 0); 344 | transform: translate3d(0, 0, 0); 345 | } 346 | 10%, 347 | 30%, 348 | 50%, 349 | 70%, 350 | 90% { 351 | -webkit-transform: translate3d(-10px, 0, 0); 352 | transform: translate3d(-10px, 0, 0); 353 | } 354 | 20%, 355 | 40%, 356 | 60%, 357 | 80% { 358 | -webkit-transform: translate3d(10px, 0, 0); 359 | transform: translate3d(10px, 0, 0); 360 | } 361 | } 362 | @keyframes ajs-shake { 363 | 0%, 364 | 100% { 365 | -webkit-transform: translate3d(0, 0, 0); 366 | transform: translate3d(0, 0, 0); 367 | } 368 | 10%, 369 | 30%, 370 | 50%, 371 | 70%, 372 | 90% { 373 | -webkit-transform: translate3d(-10px, 0, 0); 374 | transform: translate3d(-10px, 0, 0); 375 | } 376 | 20%, 377 | 40%, 378 | 60%, 379 | 80% { 380 | -webkit-transform: translate3d(10px, 0, 0); 381 | transform: translate3d(10px, 0, 0); 382 | } 383 | } 384 | .alertify.ajs-slide.ajs-in:not(.ajs-hidden) .ajs-dialog { 385 | -webkit-animation-name: ajs-slideIn; 386 | animation-name: ajs-slideIn; 387 | -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); 388 | animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); 389 | } 390 | .alertify.ajs-slide.ajs-out.ajs-hidden .ajs-dialog { 391 | -webkit-animation-name: ajs-slideOut; 392 | animation-name: ajs-slideOut; 393 | -webkit-animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045); 394 | animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045); 395 | } 396 | .alertify.ajs-zoom.ajs-in:not(.ajs-hidden) .ajs-dialog { 397 | -webkit-animation-name: ajs-zoomIn; 398 | animation-name: ajs-zoomIn; 399 | } 400 | .alertify.ajs-zoom.ajs-out.ajs-hidden .ajs-dialog { 401 | -webkit-animation-name: ajs-zoomOut; 402 | animation-name: ajs-zoomOut; 403 | } 404 | .alertify.ajs-fade.ajs-in:not(.ajs-hidden) .ajs-dialog { 405 | -webkit-animation-name: ajs-fadeIn; 406 | animation-name: ajs-fadeIn; 407 | } 408 | .alertify.ajs-fade.ajs-out.ajs-hidden .ajs-dialog { 409 | -webkit-animation-name: ajs-fadeOut; 410 | animation-name: ajs-fadeOut; 411 | } 412 | .alertify.ajs-pulse.ajs-in:not(.ajs-hidden) .ajs-dialog { 413 | -webkit-animation-name: ajs-pulseIn; 414 | animation-name: ajs-pulseIn; 415 | } 416 | .alertify.ajs-pulse.ajs-out.ajs-hidden .ajs-dialog { 417 | -webkit-animation-name: ajs-pulseOut; 418 | animation-name: ajs-pulseOut; 419 | } 420 | .alertify.ajs-flipx.ajs-in:not(.ajs-hidden) .ajs-dialog { 421 | -webkit-animation-name: ajs-flipInX; 422 | animation-name: ajs-flipInX; 423 | } 424 | .alertify.ajs-flipx.ajs-out.ajs-hidden .ajs-dialog { 425 | -webkit-animation-name: ajs-flipOutX; 426 | animation-name: ajs-flipOutX; 427 | } 428 | .alertify.ajs-flipy.ajs-in:not(.ajs-hidden) .ajs-dialog { 429 | -webkit-animation-name: ajs-flipInY; 430 | animation-name: ajs-flipInY; 431 | } 432 | .alertify.ajs-flipy.ajs-out.ajs-hidden .ajs-dialog { 433 | -webkit-animation-name: ajs-flipOutY; 434 | animation-name: ajs-flipOutY; 435 | } 436 | @-webkit-keyframes ajs-pulseIn { 437 | 0%, 438 | 20%, 439 | 40%, 440 | 60%, 441 | 80%, 442 | 100% { 443 | transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 444 | } 445 | 0% { 446 | opacity: 0; 447 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 448 | transform: scale3d(0.3, 0.3, 0.3); 449 | } 450 | 20% { 451 | -webkit-transform: scale3d(1.1, 1.1, 1.1); 452 | transform: scale3d(1.1, 1.1, 1.1); 453 | } 454 | 40% { 455 | -webkit-transform: scale3d(0.9, 0.9, 0.9); 456 | transform: scale3d(0.9, 0.9, 0.9); 457 | } 458 | 60% { 459 | opacity: 1; 460 | -webkit-transform: scale3d(1.03, 1.03, 1.03); 461 | transform: scale3d(1.03, 1.03, 1.03); 462 | } 463 | 80% { 464 | -webkit-transform: scale3d(0.97, 0.97, 0.97); 465 | transform: scale3d(0.97, 0.97, 0.97); 466 | } 467 | 100% { 468 | opacity: 1; 469 | -webkit-transform: scale3d(1, 1, 1); 470 | transform: scale3d(1, 1, 1); 471 | } 472 | } 473 | @keyframes ajs-pulseIn { 474 | 0%, 475 | 20%, 476 | 40%, 477 | 60%, 478 | 80%, 479 | 100% { 480 | transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 481 | } 482 | 0% { 483 | opacity: 0; 484 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 485 | transform: scale3d(0.3, 0.3, 0.3); 486 | } 487 | 20% { 488 | -webkit-transform: scale3d(1.1, 1.1, 1.1); 489 | transform: scale3d(1.1, 1.1, 1.1); 490 | } 491 | 40% { 492 | -webkit-transform: scale3d(0.9, 0.9, 0.9); 493 | transform: scale3d(0.9, 0.9, 0.9); 494 | } 495 | 60% { 496 | opacity: 1; 497 | -webkit-transform: scale3d(1.03, 1.03, 1.03); 498 | transform: scale3d(1.03, 1.03, 1.03); 499 | } 500 | 80% { 501 | -webkit-transform: scale3d(0.97, 0.97, 0.97); 502 | transform: scale3d(0.97, 0.97, 0.97); 503 | } 504 | 100% { 505 | opacity: 1; 506 | -webkit-transform: scale3d(1, 1, 1); 507 | transform: scale3d(1, 1, 1); 508 | } 509 | } 510 | @-webkit-keyframes ajs-pulseOut { 511 | 20% { 512 | -webkit-transform: scale3d(0.9, 0.9, 0.9); 513 | transform: scale3d(0.9, 0.9, 0.9); 514 | } 515 | 50%, 516 | 55% { 517 | opacity: 1; 518 | -webkit-transform: scale3d(1.1, 1.1, 1.1); 519 | transform: scale3d(1.1, 1.1, 1.1); 520 | } 521 | 100% { 522 | opacity: 0; 523 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 524 | transform: scale3d(0.3, 0.3, 0.3); 525 | } 526 | } 527 | @keyframes ajs-pulseOut { 528 | 20% { 529 | -webkit-transform: scale3d(0.9, 0.9, 0.9); 530 | transform: scale3d(0.9, 0.9, 0.9); 531 | } 532 | 50%, 533 | 55% { 534 | opacity: 1; 535 | -webkit-transform: scale3d(1.1, 1.1, 1.1); 536 | transform: scale3d(1.1, 1.1, 1.1); 537 | } 538 | 100% { 539 | opacity: 0; 540 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 541 | transform: scale3d(0.3, 0.3, 0.3); 542 | } 543 | } 544 | @-webkit-keyframes ajs-zoomIn { 545 | 0% { 546 | opacity: 0; 547 | -webkit-transform: scale3d(0.25, 0.25, 0.25); 548 | transform: scale3d(0.25, 0.25, 0.25); 549 | } 550 | 100% { 551 | opacity: 1; 552 | -webkit-transform: scale3d(1, 1, 1); 553 | transform: scale3d(1, 1, 1); 554 | } 555 | } 556 | @keyframes ajs-zoomIn { 557 | 0% { 558 | opacity: 0; 559 | -webkit-transform: scale3d(0.25, 0.25, 0.25); 560 | transform: scale3d(0.25, 0.25, 0.25); 561 | } 562 | 100% { 563 | opacity: 1; 564 | -webkit-transform: scale3d(1, 1, 1); 565 | transform: scale3d(1, 1, 1); 566 | } 567 | } 568 | @-webkit-keyframes ajs-zoomOut { 569 | 0% { 570 | opacity: 1; 571 | -webkit-transform: scale3d(1, 1, 1); 572 | transform: scale3d(1, 1, 1); 573 | } 574 | 100% { 575 | opacity: 0; 576 | -webkit-transform: scale3d(0.25, 0.25, 0.25); 577 | transform: scale3d(0.25, 0.25, 0.25); 578 | } 579 | } 580 | @keyframes ajs-zoomOut { 581 | 0% { 582 | opacity: 1; 583 | -webkit-transform: scale3d(1, 1, 1); 584 | transform: scale3d(1, 1, 1); 585 | } 586 | 100% { 587 | opacity: 0; 588 | -webkit-transform: scale3d(0.25, 0.25, 0.25); 589 | transform: scale3d(0.25, 0.25, 0.25); 590 | } 591 | } 592 | @-webkit-keyframes ajs-fadeIn { 593 | 0% { 594 | opacity: 0; 595 | } 596 | 100% { 597 | opacity: 1; 598 | } 599 | } 600 | @keyframes ajs-fadeIn { 601 | 0% { 602 | opacity: 0; 603 | } 604 | 100% { 605 | opacity: 1; 606 | } 607 | } 608 | @-webkit-keyframes ajs-fadeOut { 609 | 0% { 610 | opacity: 1; 611 | } 612 | 100% { 613 | opacity: 0; 614 | } 615 | } 616 | @keyframes ajs-fadeOut { 617 | 0% { 618 | opacity: 1; 619 | } 620 | 100% { 621 | opacity: 0; 622 | } 623 | } 624 | @-webkit-keyframes ajs-flipInX { 625 | 0% { 626 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 627 | transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 628 | transition-timing-function: ease-in; 629 | opacity: 0; 630 | } 631 | 40% { 632 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 633 | transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 634 | transition-timing-function: ease-in; 635 | } 636 | 60% { 637 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); 638 | transform: perspective(400px) rotate3d(1, 0, 0, 10deg); 639 | opacity: 1; 640 | } 641 | 80% { 642 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); 643 | transform: perspective(400px) rotate3d(1, 0, 0, -5deg); 644 | } 645 | 100% { 646 | -webkit-transform: perspective(400px); 647 | transform: perspective(400px); 648 | } 649 | } 650 | @keyframes ajs-flipInX { 651 | 0% { 652 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 653 | transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 654 | transition-timing-function: ease-in; 655 | opacity: 0; 656 | } 657 | 40% { 658 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 659 | transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 660 | transition-timing-function: ease-in; 661 | } 662 | 60% { 663 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); 664 | transform: perspective(400px) rotate3d(1, 0, 0, 10deg); 665 | opacity: 1; 666 | } 667 | 80% { 668 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); 669 | transform: perspective(400px) rotate3d(1, 0, 0, -5deg); 670 | } 671 | 100% { 672 | -webkit-transform: perspective(400px); 673 | transform: perspective(400px); 674 | } 675 | } 676 | @-webkit-keyframes ajs-flipOutX { 677 | 0% { 678 | -webkit-transform: perspective(400px); 679 | transform: perspective(400px); 680 | } 681 | 30% { 682 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 683 | transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 684 | opacity: 1; 685 | } 686 | 100% { 687 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 688 | transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 689 | opacity: 0; 690 | } 691 | } 692 | @keyframes ajs-flipOutX { 693 | 0% { 694 | -webkit-transform: perspective(400px); 695 | transform: perspective(400px); 696 | } 697 | 30% { 698 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 699 | transform: perspective(400px) rotate3d(1, 0, 0, -20deg); 700 | opacity: 1; 701 | } 702 | 100% { 703 | -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 704 | transform: perspective(400px) rotate3d(1, 0, 0, 90deg); 705 | opacity: 0; 706 | } 707 | } 708 | @-webkit-keyframes ajs-flipInY { 709 | 0% { 710 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 711 | transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 712 | transition-timing-function: ease-in; 713 | opacity: 0; 714 | } 715 | 40% { 716 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); 717 | transform: perspective(400px) rotate3d(0, 1, 0, -20deg); 718 | transition-timing-function: ease-in; 719 | } 720 | 60% { 721 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); 722 | transform: perspective(400px) rotate3d(0, 1, 0, 10deg); 723 | opacity: 1; 724 | } 725 | 80% { 726 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); 727 | transform: perspective(400px) rotate3d(0, 1, 0, -5deg); 728 | } 729 | 100% { 730 | -webkit-transform: perspective(400px); 731 | transform: perspective(400px); 732 | } 733 | } 734 | @keyframes ajs-flipInY { 735 | 0% { 736 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 737 | transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 738 | transition-timing-function: ease-in; 739 | opacity: 0; 740 | } 741 | 40% { 742 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); 743 | transform: perspective(400px) rotate3d(0, 1, 0, -20deg); 744 | transition-timing-function: ease-in; 745 | } 746 | 60% { 747 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); 748 | transform: perspective(400px) rotate3d(0, 1, 0, 10deg); 749 | opacity: 1; 750 | } 751 | 80% { 752 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); 753 | transform: perspective(400px) rotate3d(0, 1, 0, -5deg); 754 | } 755 | 100% { 756 | -webkit-transform: perspective(400px); 757 | transform: perspective(400px); 758 | } 759 | } 760 | @-webkit-keyframes ajs-flipOutY { 761 | 0% { 762 | -webkit-transform: perspective(400px); 763 | transform: perspective(400px); 764 | } 765 | 30% { 766 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); 767 | transform: perspective(400px) rotate3d(0, 1, 0, -15deg); 768 | opacity: 1; 769 | } 770 | 100% { 771 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 772 | transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 773 | opacity: 0; 774 | } 775 | } 776 | @keyframes ajs-flipOutY { 777 | 0% { 778 | -webkit-transform: perspective(400px); 779 | transform: perspective(400px); 780 | } 781 | 30% { 782 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); 783 | transform: perspective(400px) rotate3d(0, 1, 0, -15deg); 784 | opacity: 1; 785 | } 786 | 100% { 787 | -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 788 | transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 789 | opacity: 0; 790 | } 791 | } 792 | @-webkit-keyframes ajs-slideIn { 793 | 0% { 794 | margin-top: -100%; 795 | } 796 | 100% { 797 | margin-top: 5%; 798 | } 799 | } 800 | @keyframes ajs-slideIn { 801 | 0% { 802 | margin-top: -100%; 803 | } 804 | 100% { 805 | margin-top: 5%; 806 | } 807 | } 808 | @-webkit-keyframes ajs-slideOut { 809 | 0% { 810 | margin-top: 5%; 811 | } 812 | 100% { 813 | margin-top: -100%; 814 | } 815 | } 816 | @keyframes ajs-slideOut { 817 | 0% { 818 | margin-top: 5%; 819 | } 820 | 100% { 821 | margin-top: -100%; 822 | } 823 | } 824 | .alertify-notifier { 825 | position: fixed; 826 | width: 0; 827 | overflow: visible; 828 | z-index: 1982; 829 | -webkit-transform: translate3d(0, 0, 0); 830 | transform: translate3d(0, 0, 0); 831 | } 832 | .alertify-notifier .ajs-message { 833 | position: relative; 834 | width: 260px; 835 | max-height: 0; 836 | padding: 0; 837 | opacity: 0; 838 | margin: 0; 839 | -webkit-transform: translate3d(0, 0, 0); 840 | transform: translate3d(0, 0, 0); 841 | transition-duration: 250ms; 842 | transition-timing-function: linear; 843 | } 844 | .alertify-notifier .ajs-message.ajs-visible { 845 | transition-duration: 500ms; 846 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); 847 | opacity: 1; 848 | max-height: 100%; 849 | padding: 15px; 850 | margin-top: 10px; 851 | } 852 | .alertify-notifier .ajs-message.ajs-success { 853 | background: rgba(91, 189, 114, 0.95); 854 | } 855 | .alertify-notifier .ajs-message.ajs-error { 856 | background: rgba(217, 92, 92, 0.95); 857 | } 858 | .alertify-notifier .ajs-message.ajs-warning { 859 | background: rgba(252, 248, 215, 0.95); 860 | } 861 | .alertify-notifier .ajs-message .ajs-close { 862 | position: absolute; 863 | top: 0; 864 | right: 0; 865 | width: 16px; 866 | height: 16px; 867 | cursor: pointer; 868 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAABGdBTUEAALGPC/xhBQAAAFBJREFUGBl1j0EKADEIA+ve/P9f9bh1hEihNBfjVCO1v7RKVqJK4h8gM5cAPR42AkQEpSXPwMTyoi13n5N9YqJehm3Fnr7nL1D0ZEbD5OubGyC7a9gx+9eNAAAAAElFTkSuQmCC); 869 | background-repeat: no-repeat; 870 | background-position: center center; 871 | background-color: rgba(0, 0, 0, 0.5); 872 | border-top-right-radius: 2px; 873 | } 874 | .alertify-notifier.ajs-top { 875 | top: 10px; 876 | } 877 | .alertify-notifier.ajs-bottom { 878 | bottom: 10px; 879 | } 880 | .alertify-notifier.ajs-right { 881 | right: 10px; 882 | } 883 | .alertify-notifier.ajs-right .ajs-message { 884 | right: -320px; 885 | } 886 | .alertify-notifier.ajs-right .ajs-message.ajs-visible { 887 | right: 290px; 888 | } 889 | .alertify-notifier.ajs-left { 890 | left: 10px; 891 | } 892 | .alertify-notifier.ajs-left .ajs-message { 893 | left: -300px; 894 | } 895 | .alertify-notifier.ajs-left .ajs-message.ajs-visible { 896 | left: 0; 897 | } 898 | -------------------------------------------------------------------------------- /apex/plugins/show_websocket_notify/plugin_files/css/alertify.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * alertifyjs 1.10.0 http://alertifyjs.com 3 | * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications. 4 | * Copyright 2017 Mohammad Younes (http://alertifyjs.com) 5 | * Licensed under GPL 3 */ 6 | .alertify .ajs-dimmer,.alertify .ajs-modal{position:fixed;padding:0;z-index:1981;top:0;right:0;bottom:0;left:0}.alertify .ajs-dimmer{margin:0;background-color:#252525;opacity:.5}.alertify .ajs-modal{overflow-y:auto}.alertify .ajs-dialog{position:relative;margin:5% auto;min-height:110px;max-width:500px;padding:24px 24px 0;outline:0;background-color:#fff}.alertify .ajs-dialog.ajs-capture:before{content:'';position:absolute;top:0;right:0;bottom:0;left:0;display:block;z-index:1}.alertify .ajs-reset{position:absolute!important;display:inline!important;width:0!important;height:0!important;opacity:0!important}.alertify .ajs-commands{position:absolute;right:4px;margin:-14px 24px 0 0;z-index:2}.alertify .ajs-commands button{display:none;width:10px;height:10px;margin-left:10px;padding:10px;border:0;background-color:transparent;background-repeat:no-repeat;background-position:center;cursor:pointer}.alertify .ajs-commands button.ajs-close{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAh0lEQVQYlY2QsQ0EIQwEB9cBAR1CJUaI/gigDnwR6NBL/7/xWLNrZ2b8EwGotVpr7eOitWa1VjugiNB7R1UPrKrWe0dEAHBbXUqxMQbeewDmnHjvyTm7C3zDwAUd9c63YQdUVdu6EAJzzquz7HXvTiklt+H9DQFYaxFjvDqllFyMkbXWvfpXHjJrWFgdBq/hAAAAAElFTkSuQmCC)}.alertify .ajs-commands button.ajs-maximize{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAOUlEQVQYlWP8//8/AzGAhYGBgaG4uBiv6t7eXkYmooxjYGAgWiELsvHYFMCcRX2rSXcjoSBiJDbAAeD+EGu+8BZcAAAAAElFTkSuQmCC)}.alertify .ajs-header{margin:-24px -24px 0;padding:16px 24px;background-color:#fff}.alertify .ajs-body{min-height:56px}.alertify .ajs-body .ajs-content{padding:16px 24px 16px 16px}.alertify .ajs-footer{padding:4px;margin-left:-24px;margin-right:-24px;min-height:43px;background-color:#fff}.alertify.ajs-maximized .ajs-dialog,.alertify.ajs-no-padding:not(.ajs-maximized) .ajs-body .ajs-content,.alertify.ajs-resizable .ajs-dialog{padding:0}.alertify .ajs-footer .ajs-buttons.ajs-auxiliary .ajs-button,.alertify .ajs-footer .ajs-buttons.ajs-primary .ajs-button{margin:4px}.alertify .ajs-footer .ajs-buttons.ajs-primary{text-align:right}.alertify .ajs-footer .ajs-buttons.ajs-auxiliary{float:left;clear:none;text-align:left}.alertify .ajs-footer .ajs-buttons .ajs-button{min-width:88px;min-height:35px}.alertify .ajs-handle{position:absolute;display:none;width:10px;height:10px;right:0;bottom:0;z-index:1;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMS8xNEDQYmMAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAQ0lEQVQYlaXNMQoAIAxD0dT7H657l0KX3iJuUlBUNOsPPCGJm7VDp6ryeMxMuDsAQH7owW3pyn3RS26iKxERMLN3ugOaAkaL3sWVigAAAABJRU5ErkJggg==);-webkit-transform:scaleX(1);transform:scaleX(1);cursor:se-resize}.alertify.ajs-no-overflow .ajs-body .ajs-content{overflow:hidden!important}.alertify.ajs-no-padding.ajs-maximized .ajs-body .ajs-content{left:0;right:0;padding:0}.alertify.ajs-no-padding:not(.ajs-maximized) .ajs-body{margin-left:-24px;margin-right:-24px}.alertify.ajs-no-padding.ajs-resizable .ajs-body .ajs-content{left:0;right:0}.alertify.ajs-closable .ajs-commands button.ajs-close,.alertify.ajs-maximizable .ajs-commands button.ajs-maximize,.alertify.ajs-maximizable .ajs-commands button.ajs-restore{display:inline-block}.alertify.ajs-maximized .ajs-dialog{width:100%!important;height:100%!important;max-width:none!important;margin:0 auto!important;top:0!important;left:0!important}.alertify.ajs-maximized.ajs-modeless .ajs-modal{position:fixed!important;min-height:100%!important;max-height:none!important;margin:0!important}.alertify.ajs-maximized .ajs-commands button.ajs-maximize{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAASklEQVQYlZWQ0QkAMQhDtXRincOZX78KVtrDCwgqJNEoIB3MPLj7lRUROlpyVXGzby6zWuY+kz6tj5sBMTMAyVV3/595RbOh3cAXsww1raeiOcoAAAAASUVORK5CYII=)}.alertify.ajs-maximized .ajs-commands,.alertify.ajs-resizable .ajs-commands{margin:14px 24px 0 0}.alertify.ajs-maximized .ajs-header,.alertify.ajs-resizable .ajs-header{position:absolute;top:0;left:0;right:0;margin:0;padding:16px 24px}.alertify.ajs-maximized .ajs-body,.alertify.ajs-resizable .ajs-body{min-height:224px;display:inline-block}.alertify.ajs-maximized .ajs-body .ajs-content,.alertify.ajs-resizable .ajs-body .ajs-content{position:absolute;top:50px;right:24px;bottom:50px;left:24px;overflow:auto}.alertify.ajs-maximized .ajs-footer,.alertify.ajs-resizable .ajs-footer{position:absolute;left:0;right:0;bottom:0;margin:0}.alertify.ajs-resizable:not(.ajs-maximized) .ajs-dialog{min-width:548px}.alertify.ajs-resizable:not(.ajs-maximized) .ajs-handle{display:block}.alertify.ajs-movable:not(.ajs-maximized) .ajs-header{cursor:move}.alertify.ajs-modeless .ajs-dimmer,.alertify.ajs-modeless .ajs-reset{display:none}.alertify.ajs-modeless .ajs-modal{overflow:visible;max-width:none;max-height:0}.alertify.ajs-modeless.ajs-pinnable .ajs-commands button.ajs-pin{display:inline-block;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAQklEQVQYlcWPMQ4AIAwCqU9u38GbcbHRWN1MvKQDhQFMEpKImGJA0gCgnYw0V0rwxseg5erT4oSkQVI5d9f+e9+xA0NbLpWfitPXAAAAAElFTkSuQmCC)}.alertify.ajs-modeless.ajs-unpinned .ajs-modal{position:absolute}.alertify.ajs-modeless.ajs-unpinned .ajs-commands button.ajs-pin{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8xMy8xNOrZqugAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAO0lEQVQYlWP8//8/AzGAiShV6AqLi4txGs+CLoBLMYbC3t5eRmyaWfBZhwwYkX2NTxPRvibKjRhW4wMAhxkYGbLu3pEAAAAASUVORK5CYII=)}.alertify.ajs-modeless:not(.ajs-unpinned) .ajs-body{max-height:500px;overflow:auto}.alertify.ajs-basic .ajs-header{opacity:0}.alertify.ajs-basic .ajs-footer{visibility:hidden}.alertify.ajs-frameless .ajs-header{position:absolute;top:0;left:0;right:0;min-height:60px;margin:0;padding:0;opacity:0;z-index:1}.alertify.ajs-frameless .ajs-footer{display:none}.alertify.ajs-frameless .ajs-body .ajs-content{position:absolute;top:0;right:0;bottom:0;left:0}.alertify.ajs-frameless:not(.ajs-resizable) .ajs-dialog{padding-top:0}.alertify.ajs-frameless:not(.ajs-resizable) .ajs-dialog .ajs-commands{margin-top:0}.ajs-no-overflow{overflow:hidden!important;outline:0}.ajs-no-overflow.ajs-fixed{position:fixed;top:0;right:0;bottom:0;left:0;overflow-y:scroll!important}.ajs-no-selection,.ajs-no-selection *{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media screen and (max-width:568px){.alertify .ajs-dialog{min-width:150px}.alertify:not(.ajs-maximized) .ajs-modal{padding:0 5%}.alertify:not(.ajs-maximized).ajs-resizable .ajs-dialog{min-width:initial;min-width:auto}}@-moz-document url-prefix(){.alertify button:focus{outline:#3593D2 dotted 1px}}.alertify .ajs-dimmer,.alertify .ajs-modal{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);transition-property:opacity,visibility;transition-timing-function:linear;transition-duration:250ms}.alertify.ajs-hidden .ajs-dimmer,.alertify.ajs-hidden .ajs-modal{visibility:hidden;opacity:0}.alertify.ajs-in:not(.ajs-hidden) .ajs-dialog{-webkit-animation-duration:.5s;animation-duration:.5s}.alertify.ajs-out.ajs-hidden .ajs-dialog{-webkit-animation-duration:250ms;animation-duration:250ms}.alertify .ajs-dialog.ajs-shake{-webkit-animation-name:ajs-shake;animation-name:ajs-shake;-webkit-animation-duration:.1s;animation-duration:.1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}@-webkit-keyframes ajs-shake{0%,100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes ajs-shake{0%,100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.alertify.ajs-slide.ajs-in:not(.ajs-hidden) .ajs-dialog{-webkit-animation-name:ajs-slideIn;animation-name:ajs-slideIn;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1.275);animation-timing-function:cubic-bezier(.175,.885,.32,1.275)}.alertify.ajs-slide.ajs-out.ajs-hidden .ajs-dialog{-webkit-animation-name:ajs-slideOut;animation-name:ajs-slideOut;-webkit-animation-timing-function:cubic-bezier(.6,-.28,.735,.045);animation-timing-function:cubic-bezier(.6,-.28,.735,.045)}.alertify.ajs-zoom.ajs-in:not(.ajs-hidden) .ajs-dialog{-webkit-animation-name:ajs-zoomIn;animation-name:ajs-zoomIn}.alertify.ajs-zoom.ajs-out.ajs-hidden .ajs-dialog{-webkit-animation-name:ajs-zoomOut;animation-name:ajs-zoomOut}.alertify.ajs-fade.ajs-in:not(.ajs-hidden) .ajs-dialog{-webkit-animation-name:ajs-fadeIn;animation-name:ajs-fadeIn}.alertify.ajs-fade.ajs-out.ajs-hidden .ajs-dialog{-webkit-animation-name:ajs-fadeOut;animation-name:ajs-fadeOut}.alertify.ajs-pulse.ajs-in:not(.ajs-hidden) .ajs-dialog{-webkit-animation-name:ajs-pulseIn;animation-name:ajs-pulseIn}.alertify.ajs-pulse.ajs-out.ajs-hidden .ajs-dialog{-webkit-animation-name:ajs-pulseOut;animation-name:ajs-pulseOut}.alertify.ajs-flipx.ajs-in:not(.ajs-hidden) .ajs-dialog{-webkit-animation-name:ajs-flipInX;animation-name:ajs-flipInX}.alertify.ajs-flipx.ajs-out.ajs-hidden .ajs-dialog{-webkit-animation-name:ajs-flipOutX;animation-name:ajs-flipOutX}.alertify.ajs-flipy.ajs-in:not(.ajs-hidden) .ajs-dialog{-webkit-animation-name:ajs-flipInY;animation-name:ajs-flipInY}.alertify.ajs-flipy.ajs-out.ajs-hidden .ajs-dialog{-webkit-animation-name:ajs-flipOutY;animation-name:ajs-flipOutY}@-webkit-keyframes ajs-pulseIn{0%,100%,20%,40%,60%,80%{transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes ajs-pulseIn{0%,100%,20%,40%,60%,80%{transition-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@-webkit-keyframes ajs-pulseOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes ajs-pulseOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@-webkit-keyframes ajs-zoomIn{0%{opacity:0;-webkit-transform:scale3d(.25,.25,.25);transform:scale3d(.25,.25,.25)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes ajs-zoomIn{0%{opacity:0;-webkit-transform:scale3d(.25,.25,.25);transform:scale3d(.25,.25,.25)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@-webkit-keyframes ajs-zoomOut{0%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}100%{opacity:0;-webkit-transform:scale3d(.25,.25,.25);transform:scale3d(.25,.25,.25)}}@keyframes ajs-zoomOut{0%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}100%{opacity:0;-webkit-transform:scale3d(.25,.25,.25);transform:scale3d(.25,.25,.25)}}@-webkit-keyframes ajs-fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes ajs-fadeIn{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes ajs-fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes ajs-fadeOut{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes ajs-flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1,0,0,10deg);transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-5deg);transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes ajs-flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1,0,0,10deg);transform:perspective(400px) rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-5deg);transform:perspective(400px) rotate3d(1,0,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@-webkit-keyframes ajs-flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}@keyframes ajs-flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(1,0,0,90deg);transform:perspective(400px) rotate3d(1,0,0,90deg);opacity:0}}@-webkit-keyframes ajs-flipInY{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-20deg);transform:perspective(400px) rotate3d(0,1,0,-20deg);transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(0,1,0,10deg);transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-5deg);transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes ajs-flipInY{0%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-20deg);transform:perspective(400px) rotate3d(0,1,0,-20deg);transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(0,1,0,10deg);transform:perspective(400px) rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-5deg);transform:perspective(400px) rotate3d(0,1,0,-5deg)}100%{-webkit-transform:perspective(400px);transform:perspective(400px)}}@-webkit-keyframes ajs-flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-15deg);transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}@keyframes ajs-flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(0,1,0,-15deg);transform:perspective(400px) rotate3d(0,1,0,-15deg);opacity:1}100%{-webkit-transform:perspective(400px) rotate3d(0,1,0,90deg);transform:perspective(400px) rotate3d(0,1,0,90deg);opacity:0}}@-webkit-keyframes ajs-slideIn{0%{margin-top:-100%}100%{margin-top:5%}}@keyframes ajs-slideIn{0%{margin-top:-100%}100%{margin-top:5%}}@-webkit-keyframes ajs-slideOut{0%{margin-top:5%}100%{margin-top:-100%}}@keyframes ajs-slideOut{0%{margin-top:5%}100%{margin-top:-100%}}.alertify-notifier{position:fixed;width:0;overflow:visible;z-index:1982;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.alertify-notifier .ajs-message{position:relative;width:260px;max-height:0;padding:0;opacity:0;margin:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);transition-duration:250ms;transition-timing-function:linear}.alertify-notifier .ajs-message.ajs-visible{transition-duration:.5s;transition-timing-function:cubic-bezier(.175,.885,.32,1.275);opacity:1;max-height:100%;padding:15px;margin-top:10px}.alertify-notifier .ajs-message.ajs-success{background:rgba(91,189,114,.95)}.alertify-notifier .ajs-message.ajs-error{background:rgba(217,92,92,.95)}.alertify-notifier .ajs-message.ajs-warning{background:rgba(252,248,215,.95)}.alertify-notifier .ajs-message .ajs-close{position:absolute;top:0;right:0;width:16px;height:16px;cursor:pointer;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAABGdBTUEAALGPC/xhBQAAAFBJREFUGBl1j0EKADEIA+ve/P9f9bh1hEihNBfjVCO1v7RKVqJK4h8gM5cAPR42AkQEpSXPwMTyoi13n5N9YqJehm3Fnr7nL1D0ZEbD5OubGyC7a9gx+9eNAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center center;background-color:rgba(0,0,0,.5);border-top-right-radius:2px}.alertify-notifier.ajs-top{top:10px}.alertify-notifier.ajs-bottom{bottom:10px}.alertify-notifier.ajs-right{right:10px}.alertify-notifier.ajs-right .ajs-message{right:-320px}.alertify-notifier.ajs-right .ajs-message.ajs-visible{right:290px}.alertify-notifier.ajs-left{left:10px}.alertify-notifier.ajs-left .ajs-message{left:-300px}.alertify-notifier.ajs-left .ajs-message.ajs-visible{left:0} -------------------------------------------------------------------------------- /apex/plugins/show_websocket_notify/plugin_files/css/default.css: -------------------------------------------------------------------------------- 1 | /** 2 | * alertifyjs 1.10.0 http://alertifyjs.com 3 | * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications. 4 | * Copyright 2017 Mohammad Younes (http://alertifyjs.com) 5 | * Licensed under GPL 3 */ 6 | .alertify .ajs-dialog { 7 | background-color: white; 8 | box-shadow: 0px 15px 20px 0px rgba(0, 0, 0, 0.25); 9 | border-radius: 2px; 10 | } 11 | .alertify .ajs-header { 12 | color: black; 13 | font-weight: bold; 14 | background: #fafafa; 15 | border-bottom: #eee 1px solid; 16 | border-radius: 2px 2px 0 0; 17 | } 18 | .alertify .ajs-body { 19 | color: black; 20 | } 21 | .alertify .ajs-body .ajs-content .ajs-input { 22 | display: block; 23 | width: 100%; 24 | padding: 8px; 25 | margin: 4px; 26 | border-radius: 2px; 27 | border: 1px solid #CCC; 28 | } 29 | .alertify .ajs-body .ajs-content p { 30 | margin: 0; 31 | } 32 | .alertify .ajs-footer { 33 | background: #fbfbfb; 34 | border-top: #eee 1px solid; 35 | border-radius: 0 0 2px 2px; 36 | } 37 | .alertify .ajs-footer .ajs-buttons .ajs-button { 38 | background-color: transparent; 39 | color: #000; 40 | border: 0; 41 | font-size: 14px; 42 | font-weight: bold; 43 | text-transform: uppercase; 44 | } 45 | .alertify .ajs-footer .ajs-buttons .ajs-button.ajs-ok { 46 | color: #3593D2; 47 | } 48 | .alertify-notifier .ajs-message { 49 | background: rgba(255, 255, 255, 0.95); 50 | color: #000; 51 | text-align: center; 52 | border: solid 1px #ddd; 53 | border-radius: 2px; 54 | } 55 | .alertify-notifier .ajs-message.ajs-success { 56 | color: #fff; 57 | background: rgba(91, 189, 114, 0.95); 58 | text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5); 59 | } 60 | .alertify-notifier .ajs-message.ajs-error { 61 | color: #fff; 62 | background: rgba(217, 92, 92, 0.95); 63 | text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5); 64 | } 65 | .alertify-notifier .ajs-message.ajs-warning { 66 | background: rgba(252, 248, 215, 0.95); 67 | border-color: #999; 68 | } 69 | -------------------------------------------------------------------------------- /apex/plugins/show_websocket_notify/plugin_files/css/default.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * alertifyjs 1.10.0 http://alertifyjs.com 3 | * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications. 4 | * Copyright 2017 Mohammad Younes (http://alertifyjs.com) 5 | * Licensed under GPL 3 */ 6 | .alertify .ajs-dialog{background-color:#fff;box-shadow:0 15px 20px 0 rgba(0,0,0,.25);border-radius:2px}.alertify .ajs-header{color:#000;font-weight:700;background:#fafafa;border-bottom:#eee 1px solid;border-radius:2px 2px 0 0}.alertify .ajs-body{color:#000}.alertify .ajs-body .ajs-content .ajs-input{display:block;width:100%;padding:8px;margin:4px;border-radius:2px;border:1px solid #CCC}.alertify .ajs-body .ajs-content p{margin:0}.alertify .ajs-footer{background:#fbfbfb;border-top:#eee 1px solid;border-radius:0 0 2px 2px}.alertify .ajs-footer .ajs-buttons .ajs-button{background-color:transparent;color:#000;border:0;font-size:14px;font-weight:700;text-transform:uppercase}.alertify .ajs-footer .ajs-buttons .ajs-button.ajs-ok{color:#3593D2}.alertify-notifier .ajs-message{background:rgba(255,255,255,.95);color:#000;text-align:center;border:1px solid #ddd;border-radius:2px}.alertify-notifier .ajs-message.ajs-success{color:#fff;background:rgba(91,189,114,.95);text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-error{color:#fff;background:rgba(217,92,92,.95);text-shadow:-1px -1px 0 rgba(0,0,0,.5)}.alertify-notifier .ajs-message.ajs-warning{background:rgba(252,248,215,.95);border-color:#999} -------------------------------------------------------------------------------- /apex/plugins/show_websocket_notify/plugin_files/js/showNotifyWebsocket.js: -------------------------------------------------------------------------------- 1 | // Websocket Notify 2 | // Author: Daniel Hochleitner 3 | // Version: 1.2 4 | 5 | // global namespace 6 | var showNotifyWebsocket = { 7 | // parse string to boolean 8 | parseBoolean: function(pString) { 9 | var pBoolean; 10 | if (pString.toLowerCase() == 'true') { 11 | pBoolean = true; 12 | } 13 | if (pString.toLowerCase() == 'false') { 14 | pBoolean = false; 15 | } 16 | if (!(pString.toLowerCase() == 'true') && !(pString.toLowerCase() == 'false')) { 17 | pBoolean = undefined; 18 | } 19 | return pBoolean; 20 | }, 21 | // function that gets called from plugin 22 | showNotify: function() { 23 | // plugin attributes 24 | var daThis = this; 25 | var data = daThis.data; 26 | var vNotifyIcon = daThis.action.attribute01; 27 | var vNotifyWaitTime = parseInt(daThis.action.attribute02); 28 | var vNotifyPosition = daThis.action.attribute03; 29 | var vLogging = showNotifyWebsocket.parseBoolean(daThis.action.attribute04); 30 | var vNotifyType = data.type; 31 | var vNotifyTitle = data.title; 32 | var vNotifyMessage = data.message; 33 | var vNotifyTime = data.time; 34 | var vNotifyOptParam = data.optparam; 35 | var vCallingEvent = daThis.browserEvent.type; 36 | // Logging 37 | if (vLogging) { 38 | console.log('showNotify: Attribute Notify Icon Class:', vNotifyIcon); 39 | console.log('showNotify: Attribute Notify Wait Time:', vNotifyWaitTime); 40 | console.log('showNotify: Attribute Notify Position:', vNotifyPosition); 41 | console.log('showNotify: Attribute Logging:', vLogging); 42 | console.log('showNotify: Websocket Data Object Notify Type:', vNotifyType); 43 | console.log('showNotify: Websocket Data Object Notify Title:', vNotifyTitle); 44 | console.log('showNotify: Websocket Data Object Notify Message:', vNotifyMessage); 45 | console.log('showNotify: Websocket Data Object Notify Time:', vNotifyTime); 46 | console.log('showNotify: Websocket Data Object Notify OptParam:', vNotifyOptParam); 47 | console.log('showNotify: Calling Event:', vCallingEvent); 48 | } 49 | // Alertify Notification 50 | var notification; 51 | alertify.set('notifier', 'position', vNotifyPosition); 52 | // info 53 | if (vNotifyType === 'info') { 54 | notification = alertify.message(' ' + vNotifyTitle + ' (' + vNotifyTime + '):
' + vNotifyMessage, vNotifyWaitTime); 55 | // success 56 | } else if (vNotifyType === 'success') { 57 | notification = alertify.success(' ' + vNotifyTitle + ' (' + vNotifyTime + '):
' + vNotifyMessage, vNotifyWaitTime); 58 | // warn 59 | } else if (vNotifyType === 'warn') { 60 | notification = alertify.warning(' ' + vNotifyTitle + ' (' + vNotifyTime + '):
' + vNotifyMessage, vNotifyWaitTime); 61 | // error 62 | } else if (vNotifyType === 'error') { 63 | notification = alertify.error(' ' + vNotifyTitle + ' (' + vNotifyTime + '):
' + vNotifyMessage, vNotifyWaitTime); 64 | } 65 | // add values to notification object 66 | notification.type = vNotifyType; 67 | notification.title = vNotifyTitle; 68 | notification.message = vNotifyMessage; 69 | notification.time = vNotifyTime; 70 | if (vNotifyOptParam) { 71 | notification.optparam = vNotifyOptParam; 72 | } 73 | // onclick 74 | notification.callback = function(isClicked) { 75 | if (isClicked) { 76 | setTimeout(function() { 77 | // private 78 | if (vCallingEvent.indexOf('private') > -1) { 79 | apex.event.trigger('body', 'ws-private-notify-clicked', notification); 80 | // public 81 | } else if (vCallingEvent.indexOf('public') > -1) { 82 | apex.event.trigger('body', 'ws-public-notify-clicked', notification); 83 | } 84 | }, 150); 85 | } 86 | }; 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /apex/plugins/show_websocket_notify/plugin_files/js/showNotifyWebsocket.min.js: -------------------------------------------------------------------------------- 1 | var showNotifyWebsocket={parseBoolean:function(a){var c;"true"==a.toLowerCase()&&(c=!0);"false"==a.toLowerCase()&&(c=!1);"true"!=a.toLowerCase()&&"false"!=a.toLowerCase()&&(c=void 0);return c},showNotify:function(){var a=this.data,c=this.action.attribute01,h=parseInt(this.action.attribute02),l=this.action.attribute03,m=showNotifyWebsocket.parseBoolean(this.action.attribute04),d=a.type,e=a.title,f=a.message,g=a.time,a=a.optparam,k=this.browserEvent.type;m&&(console.log("showNotify: Attribute Notify Icon Class:", 2 | c),console.log("showNotify: Attribute Notify Wait Time:",h),console.log("showNotify: Attribute Notify Position:",l),console.log("showNotify: Attribute Logging:",m),console.log("showNotify: Websocket Data Object Notify Type:",d),console.log("showNotify: Websocket Data Object Notify Title:",e),console.log("showNotify: Websocket Data Object Notify Message:",f),console.log("showNotify: Websocket Data Object Notify Time:",g),console.log("showNotify: Websocket Data Object Notify OptParam:",a),console.log("showNotify: Calling Event:", 3 | k));var b;alertify.set("notifier","position",l);"info"===d?b=alertify.message(' '+e+" ("+g+"):
"+f,h):"success"===d?b=alertify.success(' '+e+" ("+g+"):
"+f,h):"warn"===d?b=alertify.warning(' '+e+" ("+g+"):
"+f,h):"error"===d&&(b=alertify.error(' '+e+" ("+g+"):
"+f,h));b.type=d;b.title=e;b.message=f;b.time=g;a&&(b.optparam=a); 4 | b.callback=function(a){a&&setTimeout(function(){-1 2 | 3 | 4 | 5 | PL/SQL Doc - Index 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/plsqldoc/frame_Index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PL/SQL Doc - Index 6 | 7 | 8 | 9 | 10 | 11 | 12 |
 Packages
13 | 14 | 15 | 16 | 19 | 20 |
17 |    ws_notify_api 18 |
21 | 22 | 23 |
 Package bodies
24 | 25 | 26 | 27 | 30 | 31 |
28 |    ws_notify_api 29 |
32 | 33 | 34 | 35 |

 

36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/plsqldoc/frame_home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PL/SQL Doc - Index 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/plsqldoc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PL/SQL Doc - Index 6 | 7 | 8 | 9 |
10 | PL/SQL Doc - Index 11 | 12 |
13 | 14 | [Packages]  15 | [Package bodies]  16 | [Functions]  17 | [Procedures]  18 | [Types]  19 | [Type bodies]  20 | [Triggers]  21 | [Tables]  22 | [Views]  23 | 24 |
25 | 26 |
27 | 28 | 29 |
30 | 31 | 32 |
Packages
33 | 34 | 35 | 36 | 39 | 40 |
37 | ws_notify_api 38 | API Package Spec for Node Notify Websocket REST Calls
41 | 42 |
43 | 44 | 45 |
Package bodies
46 | 47 | 48 | 49 | 52 | 53 |
50 | ws_notify_api 51 | API Package Body for Node Notify Websocket REST Calls
54 | 55 | 56 | 57 |

 

58 |

 

59 |

 

60 |

 

61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/plsqldoc/plsqldoc.css: -------------------------------------------------------------------------------- 1 | /* PL/SQL Developer, plsqldoc styles */ 2 | 3 | BODY 4 | { 5 | font-family: Verdana,Arial,Helvetica,sans-serif; 6 | background-color : #ECECEC; 7 | font-size : 12px; 8 | font-weight: normal; 9 | font-style: normal; 10 | margin-left: 15px; 11 | margin-top: 25px; 12 | } 13 | 14 | A {color: #333300; text-decoration: underline;} 15 | A:active {color: #2020C0; text-decoration: underline;} 16 | A:hover {color: #4040C0; text-decoration: underline;} 17 | A:visited {color: #101060; text-decoration: underline;} 18 | 19 | 20 | /* Style for Title of document (Package ...) */ 21 | 22 | .MAIN_TITLE 23 | { 24 | font-family: Verdana,Arial,Helvetica,sans-serif; 25 | font-size : medium; 26 | color: #992266; 27 | font-weight: bold; 28 | margin-bottom : 12px; 29 | } 30 | 31 | 32 | /* Style for smaller Titles (Program Units, Exceptions, ...) */ 33 | 34 | .SUB_TITLE 35 | { 36 | font-family: Verdana,Arial,Helvetica,sans-serif; 37 | font-size: medium; 38 | color: #992266; 39 | font-weight: normal; 40 | margin-bottom: 12px; 41 | } 42 | 43 | 44 | /* Type name in description */ 45 | 46 | .TYPE_TITLE 47 | { 48 | font-family: Verdana,Arial,Helvetica,sans-serif; 49 | font-size: small; 50 | color: #992266; 51 | font-weight: bold; 52 | margin-bottom: 0px; 53 | } 54 | 55 | 56 | /* Type name in list */ 57 | 58 | .TYPE_ITEM 59 | { 60 | font-family: Verdana,Arial,Helvetica,sans-serif; 61 | font-size: x-small; 62 | color: #101060; 63 | font-weight: bold; 64 | margin-bottom: 0px; 65 | } 66 | 67 | /* 'See Also' list title */ 68 | 69 | .LIST_TITLE 70 | { 71 | font-family: Verdana,Arial,Helvetica,sans-serif; 72 | font-size: small; 73 | color: #301080; 74 | font-weight: bold; 75 | font-style: normal; 76 | margin-bottom: 0px; 77 | } 78 | 79 | 80 | /* 'See Also' list item description */ 81 | /* Specific style can be specified for the different groups like Usage, Parameter, ets */ 82 | /* with a .LIST_ITEM_[GROUP] tag like .LIST_ITEM_USAGE */ 83 | 84 | .LIST_ITEM 85 | { 86 | font-family: Verdana,Arial,Helvetica,sans-serif; 87 | font-size: x-small; 88 | color: #101060; 89 | font-weight: bold; 90 | margin-bottom: 0px; 91 | } 92 | 93 | 94 | /* Description */ 95 | /* Specific style can be specified for the different groups like Usage, Parameter, ets */ 96 | /* with a .DESC_TEXT_[GROUP] tag like .DESC_TEXT_USAGE */ 97 | 98 | .DESC_TEXT 99 | { 100 | font-family: Verdana,Arial,Helvetica,sans-serif; 101 | font-size: x-small; 102 | color: #101060; 103 | font-weight: normal; 104 | margin-bottom: 0px; 105 | } 106 | 107 | 108 | /* Formatted text */ 109 | 110 | .DECL_TEXT 111 | { 112 | font-family: Courier; 113 | font-size: x-small; 114 | color: #101080; 115 | font-weight: normal; 116 | margin-bottom : 0px; 117 | } 118 | 119 | 120 | /* Table that holds the Title of the document */ 121 | 122 | .MAIN_TABLE 123 | { 124 | font-family: Verdana,Arial,Tahoma,Helvetica,sans-serif; 125 | font-size: x-small; 126 | font-weight: normal; 127 | font-style: normal; 128 | background-color: #E0E0E0; 129 | width: 100%; 130 | } 131 | 132 | 133 | /* Table that holds Program Units list */ 134 | 135 | .SUB_TABLE 136 | { 137 | font-family: Verdana,Arial,Tahoma,Helvetica,sans-serif; 138 | font-size: x-small; 139 | font-weight: normal; 140 | font-style: normal; 141 | background-color: #E8E8E8; 142 | VALIGN: "TOP"; 143 | width: 100%; 144 | } 145 | 146 | 147 | /* Table that holds the See Also list */ 148 | 149 | .LIST_TABLE 150 | { 151 | font-family: Verdana,Arial,Tahoma,Helvetica,sans-serif; 152 | font-size: x-small; 153 | font-weight: normal; 154 | font-style: normal; 155 | background-color: #ECECEC; 156 | width: 100%; 157 | } 158 | 159 | 160 | /* Table that holds the See Also list */ 161 | 162 | .SIMPLE_TABLE 163 | { 164 | font-family: Verdana,Arial,Tahoma,Helvetica,sans-serif; 165 | font-size: x-small; 166 | font-weight: normal; 167 | font-style: normal; 168 | } -------------------------------------------------------------------------------- /docs/plsqldoc/ws_notify_api.body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Package body ws_notify_api 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

index

15 |
16 |

Package body ws_notify_api

17 | API Package Body for Node Notify Websocket REST Calls
18 | 19 |
20 |
21 |
22 | 23 |
24 | Program units 25 |
26 | 27 | 32 | 37 | 42 | 47 | 52 | 57 | 62 | 67 | 72 | 77 | 82 | 87 | 92 | 97 | 102 |
28 | check_error_http_status   29 | 30 | Purpose: Check Server response HTTP error (2XX Status codes) Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 31 |
33 | set_http_headers   34 | 35 | Purpose: Set HTTP headers for REST calls Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 36 |
38 | do_rest_notify_user   39 | 40 | Purpose: Send Websocket Notification over REST to connected users Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 41 |
43 | do_notify_user_private_info   44 | 45 | Purpose: Send Websocket Notification to User / Room: Private / Type: Info Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 46 |
48 | do_notify_user_private_success   49 | 50 | Purpose: Send Websocket Notification to User / Room: Private / Type: Success Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 51 |
53 | do_notify_user_private_warn   54 | 55 | Purpose: Send Websocket Notification to User / Room: Private / Type: Warn Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 56 |
58 | do_notify_user_private_error   59 | 60 | Purpose: Send Websocket Notification to User / Room: Private / Type: Error Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 61 |
63 | do_notify_user_public_info   64 | 65 | Purpose: Send Websocket Notification to User / Room: Public / Type: Info Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 66 |
68 | do_notify_user_public_success   69 | 70 | Purpose: Send Websocket Notification to User / Room: Public / Type: Success Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 71 |
73 | do_notify_user_public_warn   74 | 75 | Purpose: Send Websocket Notification to User / Room: Public / Type: Warn Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 76 |
78 | do_notify_user_public_error   79 | 80 | Purpose: Send Websocket Notification to User / Room: Public / Type: Error Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 81 |
83 | do_notify_all_public_info   84 | 85 | Purpose: Send Websocket Notification to all Users / Room: Public / Type: Info Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 86 |
88 | do_notify_all_public_success   89 | 90 | Purpose: Send Websocket Notification to all Users / Room: Public / Type: Success Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 91 |
93 | do_notify_all_public_warn   94 | 95 | Purpose: Send Websocket Notification to all Users / Room: Public / Type: Warn Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 96 |
98 | do_notify_all_public_error   99 | 100 | Purpose: Send Websocket Notification to all Users / Room: Public / Type: Error Author:  Daniel Hochleitner Created: 17.06.2016 Changed: 101 |
103 |
104 |
105 | 106 |

107 | check_error_http_status 108 |

109 |
110 | PROCEDURE check_error_http_status
111 | 
112 | 
113 |

114 | Purpose: Check Server response HTTP error (2XX Status codes) Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
115 | 116 |
117 |

118 |
119 | 120 |

121 | set_http_headers 122 |

123 |
124 | PROCEDURE set_http_headers(i_title   IN VARCHAR2,
125 |                            i_message IN VARCHAR2)
126 | 
127 | 
128 |

129 | Purpose: Set HTTP headers for REST calls Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
130 | 131 |
132 |

133 |
134 | 135 |

136 | do_rest_notify_user 137 |

138 |
139 | PROCEDURE do_rest_notify_user(i_userid   IN VARCHAR2,
140 |                               i_room     IN VARCHAR2,
141 |                               i_type     IN VARCHAR2,
142 |                               i_title    IN VARCHAR2,
143 |                               i_message  IN VARCHAR2,
144 |                               i_optparam IN VARCHAR2 := NULL)
145 | 
146 | 
147 |

148 | Purpose: Send Websocket Notification over REST to connected users Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
149 | 150 |
151 |

152 |
153 | 154 |

155 | do_notify_user_private_info 156 |

157 |
158 | PROCEDURE do_notify_user_private_info(i_userid   IN VARCHAR2,
159 |                                       i_title    IN VARCHAR2,
160 |                                       i_message  IN VARCHAR2,
161 |                                       i_optparam IN VARCHAR2 := NULL)
162 | 
163 | 
164 |

165 | Purpose: Send Websocket Notification to User / Room: Private / Type: Info Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
166 | 167 |
168 |

169 |
170 | 171 |

172 | do_notify_user_private_success 173 |

174 |
175 | PROCEDURE do_notify_user_private_success(i_userid   IN VARCHAR2,
176 |                                          i_title    IN VARCHAR2,
177 |                                          i_message  IN VARCHAR2,
178 |                                          i_optparam IN VARCHAR2 := NULL)
179 | 
180 | 
181 |

182 | Purpose: Send Websocket Notification to User / Room: Private / Type: Success Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
183 | 184 |
185 |

186 |
187 | 188 |

189 | do_notify_user_private_warn 190 |

191 |
192 | PROCEDURE do_notify_user_private_warn(i_userid   IN VARCHAR2,
193 |                                       i_title    IN VARCHAR2,
194 |                                       i_message  IN VARCHAR2,
195 |                                       i_optparam IN VARCHAR2 := NULL)
196 | 
197 | 
198 |

199 | Purpose: Send Websocket Notification to User / Room: Private / Type: Warn Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
200 | 201 |
202 |

203 |
204 | 205 |

206 | do_notify_user_private_error 207 |

208 |
209 | PROCEDURE do_notify_user_private_error(i_userid   IN VARCHAR2,
210 |                                        i_title    IN VARCHAR2,
211 |                                        i_message  IN VARCHAR2,
212 |                                        i_optparam IN VARCHAR2 := NULL)
213 | 
214 | 
215 |

216 | Purpose: Send Websocket Notification to User / Room: Private / Type: Error Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
217 | 218 |
219 |

220 |
221 | 222 |

223 | do_notify_user_public_info 224 |

225 |
226 | PROCEDURE do_notify_user_public_info(i_userid   IN VARCHAR2,
227 |                                      i_title    IN VARCHAR2,
228 |                                      i_message  IN VARCHAR2,
229 |                                      i_optparam IN VARCHAR2 := NULL)
230 | 
231 | 
232 |

233 | Purpose: Send Websocket Notification to User / Room: Public / Type: Info Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
234 | 235 |
236 |

237 |
238 | 239 |

240 | do_notify_user_public_success 241 |

242 |
243 | PROCEDURE do_notify_user_public_success(i_userid   IN VARCHAR2,
244 |                                         i_title    IN VARCHAR2,
245 |                                         i_message  IN VARCHAR2,
246 |                                         i_optparam IN VARCHAR2 := NULL)
247 | 
248 | 
249 |

250 | Purpose: Send Websocket Notification to User / Room: Public / Type: Success Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
251 | 252 |
253 |

254 |
255 | 256 |

257 | do_notify_user_public_warn 258 |

259 |
260 | PROCEDURE do_notify_user_public_warn(i_userid   IN VARCHAR2,
261 |                                      i_title    IN VARCHAR2,
262 |                                      i_message  IN VARCHAR2,
263 |                                      i_optparam IN VARCHAR2 := NULL)
264 | 
265 | 
266 |

267 | Purpose: Send Websocket Notification to User / Room: Public / Type: Warn Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
268 | 269 |
270 |

271 |
272 | 273 |

274 | do_notify_user_public_error 275 |

276 |
277 | PROCEDURE do_notify_user_public_error(i_userid   IN VARCHAR2,
278 |                                       i_title    IN VARCHAR2,
279 |                                       i_message  IN VARCHAR2,
280 |                                       i_optparam IN VARCHAR2 := NULL)
281 | 
282 | 
283 |

284 | Purpose: Send Websocket Notification to User / Room: Public / Type: Error Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
285 | 286 |
287 |

288 |
289 | 290 |

291 | do_notify_all_public_info 292 |

293 |
294 | PROCEDURE do_notify_all_public_info(i_title    IN VARCHAR2,
295 |                                     i_message  IN VARCHAR2,
296 |                                     i_optparam IN VARCHAR2 := NULL)
297 | 
298 | 
299 |

300 | Purpose: Send Websocket Notification to all Users / Room: Public / Type: Info Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
301 | 302 |
303 |

304 |
305 | 306 |

307 | do_notify_all_public_success 308 |

309 |
310 | PROCEDURE do_notify_all_public_success(i_title    IN VARCHAR2,
311 |                                        i_message  IN VARCHAR2,
312 |                                        i_optparam IN VARCHAR2 := NULL)
313 | 
314 | 
315 |

316 | Purpose: Send Websocket Notification to all Users / Room: Public / Type: Success Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
317 | 318 |
319 |

320 |
321 | 322 |

323 | do_notify_all_public_warn 324 |

325 |
326 | PROCEDURE do_notify_all_public_warn(i_title    IN VARCHAR2,
327 |                                     i_message  IN VARCHAR2,
328 |                                     i_optparam IN VARCHAR2 := NULL)
329 | 
330 | 
331 |

332 | Purpose: Send Websocket Notification to all Users / Room: Public / Type: Warn Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
333 | 334 |
335 |

336 |
337 | 338 |

339 | do_notify_all_public_error 340 |

341 |
342 | PROCEDURE do_notify_all_public_error(i_title    IN VARCHAR2,
343 |                                      i_message  IN VARCHAR2,
344 |                                      i_optparam IN VARCHAR2 := NULL)
345 | 
346 | 
347 |

348 | Purpose: Send Websocket Notification to all Users / Room: Public / Type: Error Author:  Daniel Hochleitner Created: 17.06.2016 Changed:
349 | 350 |
351 |

352 |

 

353 |

 

354 | 355 | 356 | -------------------------------------------------------------------------------- /docs/plsqldoc/ws_notify_api.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Package ws_notify_api 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

index

15 |
16 |

Package ws_notify_api

17 | API Package Spec for Node Notify Websocket REST Calls
18 | 19 |
20 |
21 |
22 | 23 |
24 | Program units 25 |
26 | 27 | 32 | 37 | 42 | 47 | 52 | 57 | 62 | 67 | 72 | 77 | 82 | 87 | 92 |
28 | do_rest_notify_user   29 | 30 | Send Websocket Notification over REST to connected users 31 |
33 | do_notify_user_private_info   34 | 35 | Send Websocket Notification to User / Room: Private / Type: Info 36 |
38 | do_notify_user_private_success   39 | 40 | Send Websocket Notification to User / Room: Private / Type: Success 41 |
43 | do_notify_user_private_warn   44 | 45 | Send Websocket Notification to User / Room: Private / Type: Warn 46 |
48 | do_notify_user_private_error   49 | 50 | Send Websocket Notification to User / Room: Private / Type: Error 51 |
53 | do_notify_user_public_info   54 | 55 | Send Websocket Notification to User / Room: Public / Type: Info 56 |
58 | do_notify_user_public_success   59 | 60 | Send Websocket Notification to User / Room: Public / Type: Success 61 |
63 | do_notify_user_public_warn   64 | 65 | Send Websocket Notification to User / Room: Public / Type: Warn 66 |
68 | do_notify_user_public_error   69 | 70 | Send Websocket Notification to User / Room: Public / Type: Error 71 |
73 | do_notify_all_public_info   74 | 75 | Send Websocket Notification to all Users / Room: Public / Type: Info 76 |
78 | do_notify_all_public_success   79 | 80 | Send Websocket Notification to all Users / Room: Public / Type: Success 81 |
83 | do_notify_all_public_warn   84 | 85 | Send Websocket Notification to all Users / Room: Public / Type: Warn 86 |
88 | do_notify_all_public_error   89 | 90 | Send Websocket Notification to all Users / Room: Public / Type: Error 91 |
93 |
94 | 95 |
96 | Variables 97 |
98 | 99 | 104 | 109 | 114 | 119 | 124 | 129 | 134 | 139 | 144 |
100 | g_ws_rest_host   101 | 102 | Websocket REST Call defaults 103 |
105 | g_ws_rest_port   106 | 107 | 108 |
110 | g_ws_rest_path   111 | 112 | 113 |
115 | g_ws_rest_proto   116 | 117 | http or https 118 |
120 | g_ws_rest_base_url   121 | 122 | 123 |
125 | g_ws_basic_auth_user   126 | 127 | security defaults 128 |
130 | g_ws_basic_auth_pwd   131 | 132 | 133 |
135 | g_ssl_wallet_path   136 | 137 | wallet info: only if g_ws_rest_proto = https 138 |
140 | g_ssl_wallet_pwd   141 | 142 | set your wallet password 143 |
145 |
146 | 147 |
148 | Constants 149 |
150 | 151 | 156 |
152 | error_http_status_code   153 | 154 | Exceptions Error Codes 155 |
157 |
158 |
159 | 160 |

161 | g_ws_rest_host 162 |

163 |
164 | g_ws_rest_host     VARCHAR2(50) := 'localhost';
165 | 
166 |

167 | Websocket REST Call defaults
168 | 169 |
170 |

171 |
172 | 173 |

174 | g_ws_rest_port 175 |

176 |
177 | g_ws_rest_port     VARCHAR2(50) := '8080';
178 | 
179 |
180 | 181 |

182 | g_ws_rest_path 183 |

184 |
185 | g_ws_rest_path     VARCHAR2(50) := '/notifyuser';
186 | 
187 |
188 | 189 |

190 | g_ws_rest_proto 191 |

192 |
193 | g_ws_rest_proto    VARCHAR2(50) := 'http';
194 | 
195 |

196 | http or https
197 | 198 |
199 |

200 |
201 | 202 |

203 | g_ws_rest_base_url 204 |

205 |
206 | g_ws_rest_base_url VARCHAR2(200) := ws_notify_api.g_ws_rest_proto ||
207 |                                     '://' || ws_notify_api.g_ws_rest_host || ':' ||
208 |                                     ws_notify_api.g_ws_rest_port ||
209 |                                     ws_notify_api.g_ws_rest_path;
210 | 
211 |
212 | 213 |

214 | g_ws_basic_auth_user 215 |

216 |
217 | g_ws_basic_auth_user VARCHAR2(100) := '';
218 | 
219 |

220 | security defaults
221 | 222 |
223 |

224 |
225 | 226 |

227 | g_ws_basic_auth_pwd 228 |

229 |
230 | g_ws_basic_auth_pwd  VARCHAR2(100) := '';
231 | 
232 |
233 | 234 |

235 | g_ssl_wallet_path 236 |

237 |
238 | g_ssl_wallet_path VARCHAR2(200) := 'file:/home/oracle/wallet';
239 | 
240 |

241 | wallet info: only if g_ws_rest_proto = https
242 | 243 |
244 |

245 |
246 | 247 |

248 | g_ssl_wallet_pwd 249 |

250 |
251 | g_ssl_wallet_pwd  VARCHAR2(100) := 'pwd';
252 | 
253 |

254 | set your wallet password
255 | 256 |
257 |

258 |
259 | 260 |

261 | error_http_status_code 262 |

263 |
264 | error_http_status_code CONSTANT NUMBER := -20002;
265 | 
266 |

267 | Exceptions Error Codes
268 | 269 |
270 |

271 |
272 | 273 |

274 | do_rest_notify_user 275 |

276 |
277 | PROCEDURE do_rest_notify_user(i_userid   IN VARCHAR2,
278 |                               i_room     IN VARCHAR2,
279 |                               i_type     IN VARCHAR2,
280 |                               i_title    IN VARCHAR2,
281 |                               i_message  IN VARCHAR2,
282 |                               i_optparam IN VARCHAR2 := NULL)
283 | 
284 | 
285 |

286 | Send Websocket Notification over REST to connected users
287 | 288 |
289 |

290 | 291 |
292 | Parameters 293 |
294 | 295 | 301 | 308 | 315 | 321 | 327 | 334 |
296 | 297 | i_userid   298 | 299 | 300 |
302 | 303 | i_room   304 | 305 | ("private" or "public")
306 | 307 |
309 | 310 | i_type   311 | 312 | (info, success, warn, error)
313 | 314 |
316 | 317 | i_title   318 | 319 | 320 |
322 | 323 | i_message   324 | 325 | 326 |
328 | 329 | i_optparam   330 | 331 | (Optional Parameter String)
332 | 333 |
335 |
336 |
337 | 338 |

339 | do_notify_user_private_info 340 |

341 |
342 | PROCEDURE do_notify_user_private_info(i_userid   IN VARCHAR2,
343 |                                       i_title    IN VARCHAR2,
344 |                                       i_message  IN VARCHAR2,
345 |                                       i_optparam IN VARCHAR2 := NULL)
346 | 
347 | 
348 |

349 | Send Websocket Notification to User / Room: Private / Type: Info
350 | 351 |
352 |

353 | 354 |
355 | Parameters 356 |
357 | 358 | 364 | 370 | 376 | 383 |
359 | 360 | i_userid   361 | 362 | 363 |
365 | 366 | i_title   367 | 368 | 369 |
371 | 372 | i_message   373 | 374 | 375 |
377 | 378 | i_optparam   379 | 380 | (Optional Parameter String)
381 | 382 |
384 |
385 |
386 | 387 |

388 | do_notify_user_private_success 389 |

390 |
391 | PROCEDURE do_notify_user_private_success(i_userid   IN VARCHAR2,
392 |                                          i_title    IN VARCHAR2,
393 |                                          i_message  IN VARCHAR2,
394 |                                          i_optparam IN VARCHAR2 := NULL)
395 | 
396 | 
397 |

398 | Send Websocket Notification to User / Room: Private / Type: Success
399 | 400 |
401 |

402 | 403 |
404 | Parameters 405 |
406 | 407 | 413 | 419 | 425 | 432 |
408 | 409 | i_userid   410 | 411 | 412 |
414 | 415 | i_title   416 | 417 | 418 |
420 | 421 | i_message   422 | 423 | 424 |
426 | 427 | i_optparam   428 | 429 | (Optional Parameter String)
430 | 431 |
433 |
434 |
435 | 436 |

437 | do_notify_user_private_warn 438 |

439 |
440 | PROCEDURE do_notify_user_private_warn(i_userid   IN VARCHAR2,
441 |                                       i_title    IN VARCHAR2,
442 |                                       i_message  IN VARCHAR2,
443 |                                       i_optparam IN VARCHAR2 := NULL)
444 | 
445 | 
446 |

447 | Send Websocket Notification to User / Room: Private / Type: Warn
448 | 449 |
450 |

451 | 452 |
453 | Parameters 454 |
455 | 456 | 462 | 468 | 474 | 481 |
457 | 458 | i_userid   459 | 460 | 461 |
463 | 464 | i_title   465 | 466 | 467 |
469 | 470 | i_message   471 | 472 | 473 |
475 | 476 | i_optparam   477 | 478 | (Optional Parameter String)
479 | 480 |
482 |
483 |
484 | 485 |

486 | do_notify_user_private_error 487 |

488 |
489 | PROCEDURE do_notify_user_private_error(i_userid   IN VARCHAR2,
490 |                                        i_title    IN VARCHAR2,
491 |                                        i_message  IN VARCHAR2,
492 |                                        i_optparam IN VARCHAR2 := NULL)
493 | 
494 | 
495 |

496 | Send Websocket Notification to User / Room: Private / Type: Error
497 | 498 |
499 |

500 | 501 |
502 | Parameters 503 |
504 | 505 | 511 | 517 | 523 | 530 |
506 | 507 | i_userid   508 | 509 | 510 |
512 | 513 | i_title   514 | 515 | 516 |
518 | 519 | i_message   520 | 521 | 522 |
524 | 525 | i_optparam   526 | 527 | (Optional Parameter String)
528 | 529 |
531 |
532 |
533 | 534 |

535 | do_notify_user_public_info 536 |

537 |
538 | PROCEDURE do_notify_user_public_info(i_userid   IN VARCHAR2,
539 |                                      i_title    IN VARCHAR2,
540 |                                      i_message  IN VARCHAR2,
541 |                                      i_optparam IN VARCHAR2 := NULL)
542 | 
543 | 
544 |

545 | Send Websocket Notification to User / Room: Public / Type: Info
546 | 547 |
548 |

549 | 550 |
551 | Parameters 552 |
553 | 554 | 560 | 566 | 572 | 579 |
555 | 556 | i_userid   557 | 558 | 559 |
561 | 562 | i_title   563 | 564 | 565 |
567 | 568 | i_message   569 | 570 | 571 |
573 | 574 | i_optparam   575 | 576 | (Optional Parameter String)
577 | 578 |
580 |
581 |
582 | 583 |

584 | do_notify_user_public_success 585 |

586 |
587 | PROCEDURE do_notify_user_public_success(i_userid   IN VARCHAR2,
588 |                                         i_title    IN VARCHAR2,
589 |                                         i_message  IN VARCHAR2,
590 |                                         i_optparam IN VARCHAR2 := NULL)
591 | 
592 | 
593 |

594 | Send Websocket Notification to User / Room: Public / Type: Success
595 | 596 |
597 |

598 | 599 |
600 | Parameters 601 |
602 | 603 | 609 | 615 | 621 | 628 |
604 | 605 | i_userid   606 | 607 | 608 |
610 | 611 | i_title   612 | 613 | 614 |
616 | 617 | i_message   618 | 619 | 620 |
622 | 623 | i_optparam   624 | 625 | (Optional Parameter String)
626 | 627 |
629 |
630 |
631 | 632 |

633 | do_notify_user_public_warn 634 |

635 |
636 | PROCEDURE do_notify_user_public_warn(i_userid   IN VARCHAR2,
637 |                                      i_title    IN VARCHAR2,
638 |                                      i_message  IN VARCHAR2,
639 |                                      i_optparam IN VARCHAR2 := NULL)
640 | 
641 | 
642 |

643 | Send Websocket Notification to User / Room: Public / Type: Warn
644 | 645 |
646 |

647 | 648 |
649 | Parameters 650 |
651 | 652 | 658 | 664 | 670 | 677 |
653 | 654 | i_userid   655 | 656 | 657 |
659 | 660 | i_title   661 | 662 | 663 |
665 | 666 | i_message   667 | 668 | 669 |
671 | 672 | i_optparam   673 | 674 | (Optional Parameter String)
675 | 676 |
678 |
679 |
680 | 681 |

682 | do_notify_user_public_error 683 |

684 |
685 | PROCEDURE do_notify_user_public_error(i_userid   IN VARCHAR2,
686 |                                       i_title    IN VARCHAR2,
687 |                                       i_message  IN VARCHAR2,
688 |                                       i_optparam IN VARCHAR2 := NULL)
689 | 
690 | 
691 |

692 | Send Websocket Notification to User / Room: Public / Type: Error
693 | 694 |
695 |

696 | 697 |
698 | Parameters 699 |
700 | 701 | 707 | 713 | 719 | 726 |
702 | 703 | i_userid   704 | 705 | 706 |
708 | 709 | i_title   710 | 711 | 712 |
714 | 715 | i_message   716 | 717 | 718 |
720 | 721 | i_optparam   722 | 723 | (Optional Parameter String)
724 | 725 |
727 |
728 |
729 | 730 |

731 | do_notify_all_public_info 732 |

733 |
734 | PROCEDURE do_notify_all_public_info(i_title    IN VARCHAR2,
735 |                                     i_message  IN VARCHAR2,
736 |                                     i_optparam IN VARCHAR2 := NULL)
737 | 
738 | 
739 |

740 | Send Websocket Notification to all Users / Room: Public / Type: Info
741 | 742 |
743 |

744 | 745 |
746 | Parameters 747 |
748 | 749 | 755 | 761 | 768 |
750 | 751 | i_title   752 | 753 | 754 |
756 | 757 | i_message   758 | 759 | 760 |
762 | 763 | i_optparam   764 | 765 | (Optional Parameter String)
766 | 767 |
769 |
770 |
771 | 772 |

773 | do_notify_all_public_success 774 |

775 |
776 | PROCEDURE do_notify_all_public_success(i_title    IN VARCHAR2,
777 |                                        i_message  IN VARCHAR2,
778 |                                        i_optparam IN VARCHAR2 := NULL)
779 | 
780 | 
781 |

782 | Send Websocket Notification to all Users / Room: Public / Type: Success
783 | 784 |
785 |

786 | 787 |
788 | Parameters 789 |
790 | 791 | 797 | 803 | 810 |
792 | 793 | i_title   794 | 795 | 796 |
798 | 799 | i_message   800 | 801 | 802 |
804 | 805 | i_optparam   806 | 807 | (Optional Parameter String)
808 | 809 |
811 |
812 |
813 | 814 |

815 | do_notify_all_public_warn 816 |

817 |
818 | PROCEDURE do_notify_all_public_warn(i_title    IN VARCHAR2,
819 |                                     i_message  IN VARCHAR2,
820 |                                     i_optparam IN VARCHAR2 := NULL)
821 | 
822 | 
823 |

824 | Send Websocket Notification to all Users / Room: Public / Type: Warn
825 | 826 |
827 |

828 | 829 |
830 | Parameters 831 |
832 | 833 | 839 | 845 | 852 |
834 | 835 | i_title   836 | 837 | 838 |
840 | 841 | i_message   842 | 843 | 844 |
846 | 847 | i_optparam   848 | 849 | (Optional Parameter String)
850 | 851 |
853 |
854 |
855 | 856 |

857 | do_notify_all_public_error 858 |

859 |
860 | PROCEDURE do_notify_all_public_error(i_title    IN VARCHAR2,
861 |                                      i_message  IN VARCHAR2,
862 |                                      i_optparam IN VARCHAR2 := NULL)
863 | 
864 | 
865 |

866 | Send Websocket Notification to all Users / Room: Public / Type: Error
867 | 868 |
869 |

870 | 871 |
872 | Parameters 873 |
874 | 875 | 881 | 887 | 894 |
876 | 877 | i_title   878 | 879 | 880 |
882 | 883 | i_message   884 | 885 | 886 |
888 | 889 | i_optparam   890 | 891 | (Optional Parameter String)
892 | 893 |
895 |
896 |

 

897 |

 

898 | 899 | 900 | -------------------------------------------------------------------------------- /docs/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dani3lSun/apex-websocket-notify-bundle/47866690e12fe473e66aa7de54f43ce93080fa29/docs/preview.gif -------------------------------------------------------------------------------- /node/node-notify-server/certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFQjCCAyoCCQDFBeK5Hx0IRTANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJE 3 | RTETMBEGA1UECAwKU29tZS1TdGF0ZTErMCkGA1UECgwiQVBFWCBXZWJzb2NrZXQg 4 | Tm90aWZpY2F0aW9uIEJ1bmRsZTESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTE2MDcx 5 | NjEyMTY1NFoXDTQzMTIwMTEyMTY1NFowYzELMAkGA1UEBhMCREUxEzARBgNVBAgM 6 | ClNvbWUtU3RhdGUxKzApBgNVBAoMIkFQRVggV2Vic29ja2V0IE5vdGlmaWNhdGlv 7 | biBCdW5kbGUxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQAD 8 | ggIPADCCAgoCggIBAMDGQ3twCasWoH59lhEV2xMiPNpDmtU11XScOz7xExYwK+FG 9 | ItGKsbVP0WfDZpX8bX8xf+GK5Oi3zAsAxeG4oB5SK/T3RLa+BpNaxbKelAa7DykL 10 | kFKMW6W0Z+0pVHDEpkt0ESVKJa76n0pZJUHSC/k83JnTE+bSPkG3PYyMctSgydOQ 11 | F39cALPB0VQRnCKtKt8Qopr5grZgShIC77Mc9JkKYvuEaJEXcbfh/O/5BqXbQsqN 12 | 2LlxTOO/EL8T0N52lXcKRlPLZ+6kWGeDMP3uQXL81FXZSSS1qxqWUY557A2FkOEs 13 | V2pgZNDFw6o5ce0bPJMXHfURVRs4WHbBQRISaSH47JcBLiebVFZiWQ5j9hT003/4 14 | SGzSlHdLFyc1JaW6RC4uk/gB72T0NxGw+hFgwHfSAHP5UNebP6ZWL+8H0JworWvZ 15 | 9vAnQyZpmU1Lo3h7A4Ez1HHvt+SlnVep6hXtPbUaMZSPrl7RfEjURCx0Ufbyh2mp 16 | C3OUwx6I/RHal3AAWHr5X5EC4UU/IZC9JJ13Y7Nb6ZNw25h0WdRYubxOKIiCbw5e 17 | 8fkKldeVXM5FApCvS6tC10Ng5uzUyysEQhVLpjLMtLtOjY2WTsUL0zdNyCaeBCr7 18 | 7wTo0K3umiPp1+gxMlRenHnGvIjvlAy/nCTvU+vOrRZwAsAjlHANKoO9DaWPAgMB 19 | AAEwDQYJKoZIhvcNAQELBQADggIBALlY8Kbcgd2C/q4Csei+0gRiXFvs25BTXUj9 20 | miYh9uke8ngXeyQxhERUA/mEWsZBA0/5XYI39moP9XfRmsdEQv8GUc8SFjkv5dPT 21 | M98fmjTt/3aApW7nvjdmqox1ZfAxb8V4YgYUqULLAE4kOxvl+TnWGoVRDYeNRFSm 22 | Aq8auG5lIRvSbkdyfL7+h+ohiLOleSYtKH1VYZgcGpi7GBRyP83W+nmum7R/rQpf 23 | plb/rOPxNuxrhngFZW3ZUiZ0IK5K2QOcJ09G0FCbdbsR/kcgBcFIle7oYThG2FCg 24 | 12Svo4NExPfJNOCIqbRsi8JSRdTgkh2b88WQvXHVwPHZC7RWd9AIIj/XkusbpE00 25 | c26rMbJqmP2IW0QRUyWp5YRqDQbEum3Szt8QwGZsKzauxKPjNXZvjhnUlnRGNVrT 26 | /xkhBy+NH57OTEAmszAXpNXopHS505UV5ze+HvDWEXY6whbixSDFpiRN8M/+Dn1m 27 | Ig+dUBM5sNU5lBIWCEu1XH84/UD1k4jdzmS8UztNxyHPl1oiZW5BvMNMNh3sEVxL 28 | MxmkMQGLecH0coNugfmfesfVj755N4twClJPVorpYpvMhOEZL5TKLHkSLxSB5pJR 29 | Dy5fPaQjgBhK4F3jH+ereUGRjY0fvvJXyT0grLdelAtrisIXMYa+v+am5XErcmv+ 30 | FFjK2SNz 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /node/node-notify-server/certs/create_cert.sh: -------------------------------------------------------------------------------- 1 | !/bin/sh 2 | 3 | echo "How many days should the certificate be valid?" 4 | read certdays 5 | 6 | if [ -z "$certdays" ] ; then 7 | $certdays=9999 8 | fi 9 | 10 | openssl genrsa -out key.pem 4096 11 | openssl req -new -sha256 -key key.pem -out csr.pem 12 | openssl x509 -req -sha256 -days $certdays -in csr.pem -signkey key.pem -out cert.pem 13 | rm -f csr.pem 14 | -------------------------------------------------------------------------------- /node/node-notify-server/certs/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEAwMZDe3AJqxagfn2WERXbEyI82kOa1TXVdJw7PvETFjAr4UYi 3 | 0YqxtU/RZ8NmlfxtfzF/4Yrk6LfMCwDF4bigHlIr9PdEtr4Gk1rFsp6UBrsPKQuQ 4 | UoxbpbRn7SlUcMSmS3QRJUolrvqfSlklQdIL+TzcmdMT5tI+Qbc9jIxy1KDJ05AX 5 | f1wAs8HRVBGcIq0q3xCimvmCtmBKEgLvsxz0mQpi+4RokRdxt+H87/kGpdtCyo3Y 6 | uXFM478QvxPQ3naVdwpGU8tn7qRYZ4Mw/e5BcvzUVdlJJLWrGpZRjnnsDYWQ4SxX 7 | amBk0MXDqjlx7Rs8kxcd9RFVGzhYdsFBEhJpIfjslwEuJ5tUVmJZDmP2FPTTf/hI 8 | bNKUd0sXJzUlpbpELi6T+AHvZPQ3EbD6EWDAd9IAc/lQ15s/plYv7wfQnCita9n2 9 | 8CdDJmmZTUujeHsDgTPUce+35KWdV6nqFe09tRoxlI+uXtF8SNRELHRR9vKHaakL 10 | c5TDHoj9EdqXcABYevlfkQLhRT8hkL0knXdjs1vpk3DbmHRZ1Fi5vE4oiIJvDl7x 11 | +QqV15VczkUCkK9Lq0LXQ2Dm7NTLKwRCFUumMsy0u06NjZZOxQvTN03IJp4EKvvv 12 | BOjQre6aI+nX6DEyVF6ceca8iO+UDL+cJO9T686tFnACwCOUcA0qg70NpY8CAwEA 13 | AQKCAgAddvBfkJtZtCapzsY+LEoE3anRbp3e0tCidzKzByP0sz4xu4ec4YcSAHYw 14 | VvR6HlAaLdWvAozEaTtdvyt/ubl3iPXBBn5RNDBLG4e2KnjToonJq0PO6jcj17Q+ 15 | 0p9ecSA9zirEuVWTLilkssV4DwPXf470UAftstrjLn/Q2LLAfyb6GD8PL1/IGerF 16 | 378uqasw1xkTeOmauJL5t8stZFKahbTlXrKg4qV2Bqujfjue5CmMTiv/BCtNcCm+ 17 | GuhE6Hkl7cMcEa38EtCUWcAGQAFu8uf9iy2b18ykTq9+iHIlCvI/CIj/8GXYZzHl 18 | 9cvNAum6TcE9cP3CIFWdKSKqAXhh/cmWHxZdd6nw4HE3WCl18duvvSAZ91SCRxfP 19 | de6iglAK2npTmrYOplDX0s91f6jUvnVDdQ+4/D0teCt3wS9s1206EI6Vzpcfo0me 20 | UfkavlOmFpYl0z4Qc3cxaiSI4q9HBwmkmZdkUG88DFRpzeRPQlVpWXxcZ5A+QKBW 21 | m3Bl4AFDXhIZF7pjcwuBNzDykrm3ttPvbARXwpYTQV2Y9eYKgrTYKXgjbIAQH6// 22 | 9mwDY4LlzCY/646rQFaTM9rkORBTdlPKY/OYtWltOlAetgBc3Tt2KlKc3H+ytB7L 23 | UHZyLH8N/RY76L21bdqJMFSrHgtNXTr57M6+7EYSJost/vLBoQKCAQEA8NIEVsIT 24 | cnkxdmkQ4Ckzsp1rV8HsooG9UHl6ZrDb3dzPUhrdwBqDPlOwY67I8oRRc7a3wrFr 25 | NI6oTqi/90tn1lbln7CDD1s104VafDLR9Jg9iAOrynriLc9nJ2A7dx3DwPUSuzt7 26 | Lb2itFgtggxXeJ62OH8aRogntpsjvZ+KNyVA99pp5E3hq1+v2JZ61porZaaoTjzw 27 | 085uTCdL7hctQuW0Q45IiGaypHXNT8p5v1HOTGutbu9t3VJxX7TJH6olxAUW2/XJ 28 | t/Ni11QTD6UTuSzqRAoqKLJHyDfTRcZyiIVHTJSImMYDRTXm+ZBFEI7db022YVER 29 | 1dxcz3dYGgR8vwKCAQEAzOz08GIzIiZX5ax5rznTJK1jjO13O2HvrvddR/MacS4R 30 | ihL5+m9ptoO35Xnj41orI9cKIIMA3DI9P1hdvVrOLQYPSv9/VhY+EC47CyekhUjU 31 | PRKsgJSMdbfI9yD38rV0ug2Rq0B38uEcxPRMX/u8L3ITVJjFqtSqfNrvN8BheCgg 32 | Ilkw/RwjNvZxEKlic35bqJQUaQi4XlTqheSLOPda3w0tEX3JE96gDaZ7faP+hHpT 33 | +m5yP2NZeZwohYyoGZHgI9IZjenCXEvUMaamwcblDkkFOmq9lmv1PufWPnYi22zq 34 | 0i9gP7wLXO6rNTILZn1pIsTu2O52FCPpuLH0nD57MQKCAQEAyQsLhEJRaeezW18r 35 | L3g2KI8y5fiHTMNzAJPpk4FijD2i9rBnH9ECcC39u7pXF99PAIMxtd/X1/CgocpP 36 | OGxDK7UvzbjWMSO8M/HIMJdcoA3YkIxABrG/0O6s9zpnanH20kfGc3icxPrjshtL 37 | Ip6bB5+4EEJL9UYFoXSyycM+5/q52jEE5gv+scsYSoCV3EKoqBwRtf86LhjQC9Ni 38 | mEnRB2AstZk7BGR4DsQpussWCJqK11GtYEeSOIpzC6spnL22+YYmJ0tyM5eElK8j 39 | mpP9e0H78QFrYL41Hvk776xUQZTCiTQXNTNZ15XAVegTQFeElT8TWvsLILbu5GSF 40 | sHK+kQKCAQAo39q7V9r7JMrDdPOQj+gdmLgSlAsEPTShoZVplIPhr16wO7jAyxg6 41 | 32r+yqE55/JS48GRWpABW8P7rpSEAGi/ckX6n5u2gBlBAPel8fJO44fx5pehzf92 42 | O4JKSLksICV4NtynW+SQ/XXPkyrouj1++TIB3bcPq/uVi9XLOQQ4A0jlkQTEd6hO 43 | fjlvKVoJz1pY9bBcgvDDYAynSD68zhnCA87Jfl1tKSqjjmFzXhKWOhAmE4GWQyrF 44 | pvAuMzAUaZRbkLyoIcgywK1Cegso+5MUllwEmuXV6xKvyW5iZw3s1nzAD9MS418g 45 | 2josEh9UAg/Gb6F0g5vpyIAF5vjzcUMBAoIBAHoJcdRbef0ojJRKrLUOiG9ZsSfU 46 | IYbmqDqT0w6e0/bmQXCdggoT3UvxZ37kfOLaRWtVeAPivtgdYPfWvKz1sm70rRaE 47 | IE7peddJsaQpInkBSu/87j5csQyntdk9hcgaTvVxaCwJi9Ir8hNZZWktHMc9L4Ob 48 | r+1elqCUsyEriGnUQYXhflNyNRepYBd6y+WaoeYWvbeoNEQsI3TTesksf0jKIsxZ 49 | +co/LPUhzXPycXswNtoUwPLX/Xhaaor22P5s0YkqIT9fZ9Cpcs6ZSys2DW1GkUKh 50 | jhes+p4XPGFNfhODDusBDFsxuNVapYWRzQRel9jiuQ9cGLzFvTtJB1eSfIc= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /node/node-notify-server/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client Test 6 | 7 | 8 | 9 |
10 |

Please open up your browser console to get informations...

11 |
12 | 13 | 14 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /node/node-notify-server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Overview Services 6 | 7 | 8 | 86 | 87 | 88 | 89 |
90 | 149 |
150 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /node/node-notify-server/localstore.js: -------------------------------------------------------------------------------- 1 | // 2 | // LocalStorage (in Memory with alasql DB) 3 | // 4 | var prefs = require("./prefs"); 5 | var alasql = require('alasql'); 6 | // create user table with indexes 7 | // TABLE users 8 | alasql.promise("CREATE TABLE users (userid string, room string, session string, created DATE)") 9 | .then(function(res) { 10 | // logging 11 | prefs.doLog('alasql - Create table users DONE'); 12 | }).catch(function(err) { 13 | // logging 14 | prefs.doLog('alasql - Create table users ERROR', err); 15 | }); 16 | // INDEX i_users_userid 17 | alasql.promise("CREATE INDEX i_users_userid ON users(userid)") 18 | .then(function(res) { 19 | // logging 20 | prefs.doLog('alasql - Create index i_users_userid DONE'); 21 | }).catch(function(err) { 22 | // logging 23 | prefs.doLog('alasql - Create index i_users_userid ERROR', err); 24 | }); 25 | // INDEX i_users_room 26 | alasql.promise("CREATE INDEX i_users_room ON users(room)") 27 | .then(function(res) { 28 | // logging 29 | prefs.doLog('alasql - Create index i_users_room DONE'); 30 | }).catch(function(err) { 31 | // logging 32 | prefs.doLog('alasql - Create index i_users_room ERROR', err); 33 | }); 34 | // INDEX i_users_created 35 | alasql.promise("CREATE INDEX i_users_created ON users(created)") 36 | .then(function(res) { 37 | // logging 38 | prefs.doLog('alasql - Create index i_users_created DONE'); 39 | }).catch(function(err) { 40 | // logging 41 | prefs.doLog('alasql - Create index i_users_created ERROR', err); 42 | }); 43 | // 44 | // Name space 45 | // 46 | module.exports = { 47 | // format date to YYYYMMDDmmss 48 | dateFormat: function(pDate) { 49 | function pad2(number) { 50 | return (number < 10 ? '0' : '') + number; 51 | } 52 | pDate = new Date(); 53 | var yyyy = pDate.getFullYear().toString(); 54 | var MM = pad2(pDate.getMonth() + 1); 55 | var dd = pad2(pDate.getDate()); 56 | var hh = pad2(pDate.getHours()); 57 | var mm = pad2(pDate.getMinutes()); 58 | var ss = pad2(pDate.getSeconds()); 59 | 60 | return yyyy + MM + dd + hh + mm + ss; 61 | }, 62 | // format date to DD.MM.YYYY HH24:MI 63 | dateTimeFormat: function(pDate) { 64 | function pad2(number) { 65 | return (number < 10 ? '0' : '') + number; 66 | } 67 | pDate = new Date(); 68 | var yyyy = pDate.getFullYear().toString(); 69 | var MM = pad2(pDate.getMonth() + 1); 70 | var dd = pad2(pDate.getDate()); 71 | var hh = pad2(pDate.getHours()); 72 | var mm = pad2(pDate.getMinutes()); 73 | 74 | return dd + '.' + MM + '.' + yyyy + ' ' + hh + ':' + mm; 75 | }, 76 | // Save Client Session in user DB 77 | saveUserSession: function(pUserId, pSocketRoom, pSocketSessionid, callback) { 78 | var lDate = new Date(); 79 | var lDateFormat = module.exports.dateFormat(lDate); 80 | alasql.promise("INSERT INTO users VALUES (UPPER('" + pUserId + "'),UPPER('" + pSocketRoom + "'),'" + pSocketSessionid + "','" + lDateFormat + "')") 81 | .then(function(res) { 82 | // reindex users table indexes 83 | alasql("REINDEX i_users_userid"); 84 | alasql("REINDEX i_users_room"); 85 | alasql("REINDEX i_users_created"); 86 | // logging 87 | prefs.doLog('alasql - Insert users DONE'); 88 | // callback 89 | callback(res); 90 | }).catch(function(err) { 91 | // logging 92 | prefs.doLog('alasql - Insert users ERROR', err); 93 | // callback 94 | callback(err); 95 | }); 96 | }, 97 | // Get all User Sessions from user DB 98 | getUserSession: function(pUserid, pSocketRoom, callback) { 99 | var sqlString = ""; 100 | // all users public 101 | if (pUserid.toUpperCase() === 'ALL' && pSocketRoom.toUpperCase() === 'PUBLIC') { 102 | sqlString = "SELECT session FROM users WHERE room = UPPER('" + pSocketRoom + "')"; 103 | // specific user and room 104 | } else { 105 | sqlString = "SELECT session FROM users WHERE userid = UPPER('" + pUserid + "') AND room = UPPER('" + pSocketRoom + "')"; 106 | } 107 | alasql.promise(sqlString) 108 | .then(function(res) { 109 | // logging 110 | prefs.doLog('alasql - Select user session DONE'); 111 | // callback 112 | callback(res); 113 | }).catch(function(err) { 114 | // logging 115 | prefs.doLog('alasql - Select user session ERROR', err); 116 | // callback 117 | callback(err); 118 | }); 119 | }, 120 | // Delete Sessions older than 2 hours 121 | deleteOldSessions: function(callback) { 122 | var lDate = new Date(); 123 | lDate = lDate.setHours(lDate.getHours() - 2); 124 | var lDateFormat = module.exports.dateFormat(lDate); 125 | alasql.promise("DELETE FROM users WHERE created < '" + lDateFormat + "'") 126 | .then(function(res) { 127 | // reindex users table 128 | alasql("REINDEX i_users_userid"); 129 | alasql("REINDEX i_users_room"); 130 | alasql("REINDEX i_users_created"); 131 | // logging 132 | prefs.doLog('alasql - Delete users DONE'); 133 | // callback 134 | callback(res); 135 | }).catch(function(err) { 136 | // logging 137 | prefs.doLog('alasql - Delete ERROR', err); 138 | callback(err); 139 | }); 140 | }, 141 | // Get DB stats 142 | getDbStats: function(callback) { 143 | alasql.promise("SELECT COUNT(*) AS counter, room FROM users GROUP BY room") 144 | .then(function(res) { 145 | // logging 146 | prefs.doLog('alasql - Select DB stats DONE'); 147 | // callback 148 | callback(res); 149 | }).catch(function(err) { 150 | // logging 151 | prefs.doLog('alasql - Select DB stats ERROR', err); 152 | // callback 153 | callback(err); 154 | }); 155 | } 156 | }; 157 | -------------------------------------------------------------------------------- /node/node-notify-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-notify-server", 3 | "version": "1.2.0", 4 | "description": "Websocket nofification services with GET interface", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Daniel Hochleitner", 11 | "license": "MIT", 12 | "dependencies": { 13 | "alasql": "^0.2.7", 14 | "encoding": "^0.1.12", 15 | "fs": "0.0.2", 16 | "http": "0.0.0", 17 | "socket.io": "^1.7.4", 18 | "url": "^0.11.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /node/node-notify-server/prefs.js: -------------------------------------------------------------------------------- 1 | // 2 | // Local Preferences functions 3 | // 4 | var fs = require('fs'); 5 | var jsonObject; 6 | // 7 | module.exports = { 8 | // read prefs from local json file 9 | readPrefs: function(pType) { 10 | if (!(jsonObject)) { 11 | var data = fs.readFileSync('./prefs.json'); 12 | jsonObject = JSON.parse(data); 13 | } 14 | if (pType == 'server') { 15 | var ip = jsonObject.server.ip; 16 | var port = jsonObject.server.port; 17 | var authUser = jsonObject.server.authUser; 18 | var authPwd = jsonObject.server.authPwd; 19 | var sslKeyPath = jsonObject.server.sslKeyPath; 20 | var sslCertPath = jsonObject.server.sslCertPath; 21 | var logging = jsonObject.server.logging; 22 | return { 23 | ip: ip, 24 | port: port, 25 | authUser: authUser, 26 | authPwd: authPwd, 27 | sslKeyPath: sslKeyPath, 28 | sslCertPath: sslCertPath, 29 | logging: logging 30 | }; 31 | } else if (pType == 'socket') { 32 | var lPrivate = jsonObject.socket.private; 33 | var lPublic = jsonObject.socket.public; 34 | var lAuthToken = jsonObject.socket.authToken; 35 | return { 36 | private: lPrivate, 37 | public: lPublic, 38 | authToken: lAuthToken 39 | }; 40 | } 41 | }, 42 | // central logging function 43 | doLog: function(pMsg, pObj1, pObj2) { 44 | var serverPrefs = module.exports.readPrefs('server'); 45 | var logging = serverPrefs.logging; 46 | if (logging) { 47 | if (pMsg && pObj1 && pObj2) { 48 | console.log(pMsg, pObj1, pObj2); 49 | } else if (pMsg && pObj1) { 50 | console.log(pMsg, pObj1); 51 | } else if (pMsg) { 52 | console.log(pMsg); 53 | } 54 | } 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /node/node-notify-server/prefs.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "ip": "0.0.0.0", 4 | "port": "8080", 5 | "authUser": "", 6 | "authPwd": "", 7 | "sslKeyPath": "", 8 | "sslCertPath": "", 9 | "logging": true 10 | }, 11 | "socket": { 12 | "private": true, 13 | "public": true, 14 | "authToken":"please-change-me" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /node/node-notify-server/server.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); 2 | var https = require('https'); 3 | var url = require("url"); 4 | var fs = require('fs'); 5 | var io = require('socket.io'); 6 | var util = require('util'); 7 | var srvHelper = require("./srvhelper"); 8 | var localstore = require("./localstore"); 9 | var prefs = require("./prefs"); 10 | var serverPrefs = prefs.readPrefs('server'); 11 | var socketPrefs = prefs.readPrefs('socket'); 12 | var ip = serverPrefs.ip; 13 | var port = serverPrefs.port; 14 | var sslKeyPath = serverPrefs.sslKeyPath; 15 | var sslCertPath = serverPrefs.sslCertPath; 16 | var isPrivate = socketPrefs.private; 17 | var isPublic = socketPrefs.public; 18 | var socketAuthToken = socketPrefs.authToken; 19 | var server; 20 | // Create HTTP Server 21 | // SSL HTTP 22 | if ((sslKeyPath) && (sslCertPath)) { 23 | var sslOptions = { 24 | key: fs.readFileSync(sslKeyPath), 25 | cert: fs.readFileSync(sslCertPath) 26 | }; 27 | server = https.createServer(sslOptions, function(req, res) { 28 | var lItems; 29 | var lUserId; 30 | var lRoom; 31 | var lTitle; 32 | var lMessage; 33 | var lTime; 34 | var lOptParam; 35 | var lType; 36 | var lJsonString; 37 | // parse URL and Path 38 | var parsedUrl = url.parse(req.url, true); 39 | var path = parsedUrl.pathname; 40 | var fullPath = parsedUrl.path; 41 | // logging IP and path 42 | prefs.doLog('Remote IP:', req.connection.remoteAddress); 43 | prefs.doLog('Path:', path); 44 | prefs.doLog('Full Path:', fullPath); 45 | prefs.doLog('User Agent:', req.headers['user-agent']); 46 | // HTTP Basic Auth 47 | if (srvHelper.doBasicAuth(req, res)) { 48 | // index html with overview of services 49 | if (path == '/' && fullPath.length == path.length) { 50 | srvHelper.serveIndex(res); 51 | // Test client 52 | } else if (path == '/testclient') { 53 | srvHelper.serveClient(res); 54 | // Path notifyuser get interface 55 | } else if (path == '/notifyuser') { 56 | lItems = srvHelper.getNotifyInfo(req, res); 57 | if (lItems) { 58 | lUserId = lItems.userid; 59 | lRoom = lItems.room; 60 | lType = lItems.type; 61 | lTitle = lItems.title; 62 | lMessage = lItems.message; 63 | lTime = lItems.time; 64 | lOptParam = lItems.optparam; 65 | // logging 66 | prefs.doLog(lUserId + ' ' + lRoom + ' ' + lType + ' ' + lTitle + ' ' + lMessage + ' ' + lOptParam + ' ' + lTime); 67 | // call notify 68 | socketio.sendNotify(lUserId, lRoom, lType, lTitle, lMessage, lOptParam, lTime, function() { 69 | res.end(); 70 | }); 71 | } 72 | // socket status 73 | } else if (path == '/status') { 74 | res.writeHead(200, { 75 | "Content-Type": "text/plain" 76 | }); 77 | socketio.getSocketInfo(function(returnText) { 78 | lStatusText = returnText; 79 | res.write(lStatusText); 80 | res.end(); 81 | }); 82 | // path not found 83 | } else { 84 | srvHelper.throwHttpError(404, 'Not Found', res); 85 | } 86 | } 87 | }); 88 | // Standard HTTP 89 | } else { 90 | server = http.createServer(function(req, res) { 91 | var lItems; 92 | var lUserId; 93 | var lType; 94 | var lRoom; 95 | var lTitle; 96 | var lMessage; 97 | var lTime; 98 | var lOptParam; 99 | var lStatusText; 100 | // parse URL and Path 101 | var parsedUrl = url.parse(req.url, true); 102 | var path = parsedUrl.pathname; 103 | var fullPath = parsedUrl.path; 104 | // logging IP and path 105 | prefs.doLog('Remote IP:', req.connection.remoteAddress); 106 | prefs.doLog('Path:', path); 107 | prefs.doLog('Full Path:', fullPath); 108 | prefs.doLog('User Agent:', req.headers['user-agent']); 109 | // HTTP Basic Auth 110 | if (srvHelper.doBasicAuth(req, res)) { 111 | // index html with overview of services 112 | if (path == '/' && fullPath.length == path.length) { 113 | srvHelper.serveIndex(res); 114 | // Test client 115 | } else if (path == '/testclient') { 116 | srvHelper.serveClient(res); 117 | // Path notifyuser get interface 118 | } else if (path == '/notifyuser') { 119 | lItems = srvHelper.getNotifyInfo(req, res); 120 | if (lItems) { 121 | lUserId = lItems.userid; 122 | lRoom = lItems.room; 123 | lType = lItems.type; 124 | lTitle = lItems.title; 125 | lMessage = lItems.message; 126 | lTime = lItems.time; 127 | lOptParam = lItems.optparam; 128 | // logging 129 | prefs.doLog(lUserId + ' ' + lRoom + ' ' + lType + ' ' + lTitle + ' ' + lMessage + ' ' + lOptParam + ' ' + lTime); 130 | // call notify 131 | socketio.sendNotify(lUserId, lRoom, lType, lTitle, lMessage, lOptParam, lTime, function() { 132 | res.end(); 133 | }); 134 | } 135 | // socket status 136 | } else if (path == '/status') { 137 | res.writeHead(200, { 138 | "Content-Type": "text/plain" 139 | }); 140 | socketio.getSocketInfo(function(returnText) { 141 | lStatusText = returnText; 142 | res.write(lStatusText); 143 | res.end(); 144 | }); 145 | // path not found 146 | } else { 147 | srvHelper.throwHttpError(404, 'Not Found', res); 148 | } 149 | } 150 | }); 151 | } 152 | server.listen(port, ip); 153 | // Log started HTTP Server 154 | if ((sslKeyPath) && (sslCertPath)) { 155 | prefs.doLog("HTTPS Server listening on " + ip + ":" + port); 156 | } else { 157 | prefs.doLog("HTTP Server listening on " + ip + ":" + port); 158 | } 159 | // 160 | // Socket.io 161 | // 162 | var listener = io.listen(server); 163 | if (isPrivate) { 164 | var ioPrivate = listener.of('/private'); 165 | } 166 | if (isPublic) { 167 | var ioPublic = listener.of('/public'); 168 | } 169 | var socketio = { 170 | // CONNECT ALL SOCKETS 171 | connectSockets: function() { 172 | // Private connect 173 | if (isPrivate) { 174 | ioPrivate.on('connection', function(socket) { 175 | var userid = socket.handshake.query.userid; 176 | var authToken = socket.handshake.query.authtoken; 177 | // check authToken 178 | if (authToken == socketAuthToken) { 179 | // token success 180 | socket.userid = userid; 181 | // logging 182 | prefs.doLog(userid + ' connected to Private'); 183 | // save session 184 | localstore.saveUserSession(userid, 'private', socket.id, function() { 185 | // logging 186 | prefs.doLog(userid + ' private session saved in DB'); 187 | }); 188 | } else { 189 | // token error 190 | // logging 191 | prefs.doLog(userid + ' with wrong authToken: ' + authToken); 192 | // disconnect 193 | socket.disconnect(); 194 | } 195 | }); 196 | } 197 | // Public connect 198 | if (isPublic) { 199 | ioPublic.on('connection', function(socket) { 200 | var userid = socket.handshake.query.userid; 201 | var authToken = socket.handshake.query.authtoken; 202 | // check authToken 203 | if (authToken == socketAuthToken) { 204 | // token success 205 | socket.userid = userid; 206 | // logging 207 | prefs.doLog(userid + ' connected to Public'); 208 | // save session 209 | localstore.saveUserSession(userid, 'public', socket.id, function() { 210 | // logging 211 | prefs.doLog(userid + ' public session saved in DB'); 212 | }); 213 | } else { 214 | // token error 215 | // logging 216 | prefs.doLog(userid + ' with wrong authToken: ' + authToken); 217 | // disconnect 218 | socket.disconnect(); 219 | } 220 | }); 221 | } 222 | }, 223 | // SEND MESSAGE TO CLIENTS 224 | sendNotify: function(pUserId, pRoom, pType, pTitle, pMessage, pOptParam, pTime, callback) { 225 | // get user session 226 | localstore.getUserSession(pUserId, pRoom, function(dbres, err) { 227 | if (dbres) { 228 | dbres.forEach(function(dbItem) { 229 | lSessionid = dbItem.session; 230 | // logging 231 | prefs.doLog(lSessionid); 232 | // private 233 | if (pRoom === 'private') { 234 | if (isPrivate) { 235 | ioPrivate.to(lSessionid).emit('message', { 236 | 'type': pType, 237 | 'title': pTitle, 238 | 'message': pMessage, 239 | 'time': pTime, 240 | 'optparam': pOptParam 241 | }); 242 | } 243 | // public 244 | } else if (pRoom === 'public') { 245 | if (isPublic) { 246 | ioPublic.to(lSessionid).emit('message', { 247 | 'type': pType, 248 | 'title': pTitle, 249 | 'message': pMessage, 250 | 'time': pTime, 251 | 'optparam': pOptParam 252 | }); 253 | } 254 | } 255 | }); 256 | } 257 | if (err) { 258 | prefs.doLog(pUserId, 'Error receiving User DB session: ' + err); 259 | } 260 | }); 261 | callback(); 262 | }, 263 | // GET SOCKET INFO 264 | getSocketInfo: function(callback) { 265 | var lReturnText; 266 | var lCount; 267 | // socket counter 268 | lReturnText = 'CONNECTED CLIENTS COUNTER:' + '\n'; 269 | // private 270 | if (isPrivate) { 271 | lCount = Object.keys(ioPrivate.connected).length; 272 | lReturnText = lReturnText + 'Connected to Private: ' + lCount + '\n'; 273 | } 274 | // public 275 | if (isPublic) { 276 | lCount = Object.keys(ioPublic.connected).length; 277 | lReturnText = lReturnText + 'Connected to Public: ' + lCount + '\n' + '\n'; 278 | } 279 | // DB stats 280 | lReturnText = lReturnText + 'DATABASE STATS:' + '\n'; 281 | // DB stats info 282 | localstore.getDbStats(function(dbres, err) { 283 | if (dbres) { 284 | dbres.forEach(function(dbItem) { 285 | lReturnText = lReturnText + dbItem.room + ': ' + dbItem.counter + ' entries' + '\n'; 286 | }); 287 | } 288 | if (err) { 289 | prefs.doLog(pUserId, 'Error receiving DB stats: ' + err); 290 | } 291 | callback(lReturnText); 292 | }); 293 | // logging 294 | prefs.doLog(lReturnText); 295 | } 296 | }; 297 | // connect sockets 298 | socketio.connectSockets(); 299 | // delete user session older than 3 hours 300 | srvHelper.deleteOldSessions(); 301 | -------------------------------------------------------------------------------- /node/node-notify-server/srvhelper.js: -------------------------------------------------------------------------------- 1 | // 2 | // Helper functions for server 3 | // 4 | var fs = require('fs'); 5 | var url = require("url"); 6 | var encoding = require("encoding"); 7 | var localstore = require("./localstore"); 8 | var prefs = require("./prefs"); 9 | var serverPrefs = prefs.readPrefs('server'); 10 | var socketPrefs = prefs.readPrefs('socket'); 11 | var isPrivate = socketPrefs.private; 12 | var isPublic = socketPrefs.public; 13 | // 14 | module.exports = { 15 | // 16 | // HTTP Error function 17 | // 18 | throwHttpError: function(errID, errMsg, res) { 19 | res.writeHead(errID, { 20 | "Content-Type": "text/plain" 21 | }); 22 | res.write(errID + " " + errMsg + "\n"); 23 | res.end(); 24 | }, 25 | // 26 | // HTTP Basic Auth 27 | // 28 | doBasicAuth: function(req, res) { 29 | var authUser = serverPrefs.authUser; 30 | var authPwd = serverPrefs.authPwd; 31 | // check if enabled in prefs 32 | if ((authUser) && (authPwd)) { 33 | var auth = req.headers['authorization']; 34 | // logging 35 | prefs.doLog("Authorization Header is: ", auth); 36 | // No Authorization header was passed in so it's the first time the browser hit us 37 | if (!auth) { 38 | res.statusCode = 401; 39 | res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"'); 40 | res.end(); 41 | } 42 | // The Authorization was passed in so now we validate it 43 | else if (auth) { 44 | // split, original auth looks like "Basic Y2hhcmxlczoxMjM0NQ==" 45 | var tmp = auth.split(' '); 46 | // create a buffer and tell it the data coming in is base64 47 | var buf = new Buffer(tmp[1], 'base64'); 48 | var plain_auth = buf.toString(); 49 | // logging 50 | prefs.doLog("Decoded Authorization ", plain_auth); 51 | // At this point plain_auth = "username:password" 52 | var creds = plain_auth.split(':'); 53 | var username = creds[0]; 54 | var password = creds[1]; 55 | // Is the username/password correct? 56 | if ((username == authUser) && (password == authPwd)) { 57 | res.statusCode = 200; 58 | return true; 59 | } else { 60 | module.exports.throwHttpError(403, 'Forbidden', res); 61 | return false; 62 | } 63 | // not enabled 64 | } 65 | } else { 66 | return true; 67 | } 68 | }, 69 | // 70 | // Get Notify User Info from GET call 71 | // 72 | getNotifyInfo: function(req, res) { 73 | // parse URL 74 | var parsedUrl = url.parse(req.url, true); 75 | // get JSON 76 | var queryAsObject = parsedUrl.query; 77 | var jsonObject = JSON.stringify(queryAsObject); 78 | // logging 79 | prefs.doLog('notifyuser JSON:', jsonObject); 80 | // parse JSON content to vars 81 | var jsonContent = JSON.parse(jsonObject); 82 | var lUserId = jsonContent.userid; 83 | var lRoom = jsonContent.room; 84 | var lType = jsonContent.type; 85 | var lOptParam = jsonContent.optparam; 86 | // Admin Header vars 87 | var lNotifyTitle = req.headers['notify-title']; 88 | var lNotifyMessage = req.headers['notify-message']; 89 | // date time 90 | var lDate = new Date(); 91 | var lDateFormat = localstore.dateTimeFormat(lDate); 92 | // check enabled sockets 93 | if (lRoom === 'private') { 94 | if (!(isPrivate)) { 95 | module.exports.throwHttpError(404, 'Room private is not enabled', res); 96 | } 97 | } else if (lRoom === 'public') { 98 | if (!(isPublic)) { 99 | module.exports.throwHttpError(404, 'Room public is not enabled', res); 100 | } 101 | } 102 | // check parameter 103 | if (lUserId && lRoom && lType && lNotifyTitle && lNotifyMessage) { 104 | if (lRoom === 'private' || lRoom === 'public') { 105 | if (lType === 'info' || lType === 'success' || lType === 'warn' || lType === 'error') { 106 | lNotifyTitle = encoding.convert(lNotifyTitle, "Latin_1").toString(); 107 | lNotifyMessage = encoding.convert(lNotifyMessage, "Latin_1").toString(); 108 | return { 109 | userid: lUserId, 110 | room: lRoom, 111 | type: lType, 112 | title: lNotifyTitle, 113 | message: lNotifyMessage, 114 | time: lDateFormat, 115 | optparam: lOptParam 116 | }; 117 | } else { 118 | module.exports.throwHttpError(404, 'Check valid values: type', res); 119 | } 120 | } else { 121 | module.exports.throwHttpError(404, 'Check valid values: room', res); 122 | } 123 | } else { 124 | module.exports.throwHttpError(404, 'Check Parameter', res); 125 | } 126 | }, 127 | // 128 | // Server index.html file (Overview) 129 | // 130 | serveIndex: function(res) { 131 | fs.readFile('./index.html', function(err, html) { 132 | // HTTP 404 when error 133 | if (err) { 134 | module.exports.throwHttpError(404, 'Not Found', res); 135 | // write index.html 136 | } else { 137 | res.write(html); 138 | res.end(); 139 | } 140 | }); 141 | }, 142 | // 143 | // Server client.html file (testclient) 144 | // 145 | serveClient: function(res) { 146 | fs.readFile('./client.html', function(err, html) { 147 | // HTTP 404 when error 148 | if (err) { 149 | module.exports.throwHttpError(404, 'Not Found', res); 150 | // write index.html 151 | } else { 152 | res.write(html); 153 | res.end(); 154 | } 155 | }); 156 | }, 157 | // 158 | // Delete user session older than 3 hours 159 | // 160 | deleteOldSessions: function() { 161 | setInterval(function() { 162 | localstore.deleteOldSessions(function(dbres, err) { 163 | if (dbres) { 164 | // logging 165 | prefs.doLog('Old Sessions deleted'); 166 | } 167 | if (err) { 168 | prefs.doLog('Error deleting User DB sessions: ' + err); 169 | } 170 | }); 171 | }, 3600000); 172 | } 173 | }; 174 | -------------------------------------------------------------------------------- /plsql/ws_notify_api.pkb: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PACKAGE BODY ws_notify_api IS 2 | -- 3 | -- API Package Body for Node Notify Websocket REST Calls 4 | -- 5 | 6 | -- 7 | /**************************************************************************** 8 | * Purpose: Check Server response HTTP error (2XX Status codes) 9 | * Author: Daniel Hochleitner 10 | * Created: 17.06.2016 11 | * Changed: 12 | ****************************************************************************/ 13 | PROCEDURE check_error_http_status IS 14 | -- 15 | l_status_code VARCHAR2(100); 16 | l_name VARCHAR2(200); 17 | l_value VARCHAR2(200); 18 | l_error_msg CLOB; 19 | -- 20 | BEGIN 21 | -- 22 | -- get http headers from response 23 | FOR i IN 1 .. apex_web_service.g_headers.count LOOP 24 | l_status_code := apex_web_service.g_status_code; 25 | l_name := apex_web_service.g_headers(i).name; 26 | l_value := apex_web_service.g_headers(i).value; 27 | -- If not successful throw error 28 | IF l_status_code NOT LIKE '2%' THEN 29 | l_error_msg := 'Response HTTP Status NOT OK' || chr(10) || 'Name: ' || 30 | l_name || chr(10) || 'Value: ' || l_value || chr(10) || 31 | 'Status Code: ' || l_status_code; 32 | raise_application_error(error_http_status_code, 33 | l_error_msg); 34 | END IF; 35 | END LOOP; 36 | -- 37 | EXCEPTION 38 | WHEN OTHERS THEN 39 | -- Insert your own exception handling here 40 | NULL; 41 | RAISE; 42 | END check_error_http_status; 43 | -- 44 | /**************************************************************************** 45 | * Purpose: Set HTTP headers for REST calls 46 | * Author: Daniel Hochleitner 47 | * Created: 17.06.2016 48 | * Changed: 49 | ****************************************************************************/ 50 | PROCEDURE set_http_headers(i_title IN VARCHAR2, 51 | i_message IN VARCHAR2) IS 52 | -- 53 | l_user_agent VARCHAR2(100); 54 | l_server ws_notify_api.g_ws_rest_host%TYPE; 55 | -- 56 | BEGIN 57 | -- Clients Envs 58 | l_user_agent := 'Mozilla/5.0'; 59 | l_server := ws_notify_api.g_ws_rest_host; 60 | -- 61 | -- set http headers 62 | -- Host 63 | apex_web_service.g_request_headers(1).name := 'Host'; 64 | apex_web_service.g_request_headers(1).value := l_server; 65 | -- User-Agent 66 | apex_web_service.g_request_headers(2).name := 'User-Agent'; 67 | apex_web_service.g_request_headers(2).value := l_user_agent; 68 | -- Title 69 | apex_web_service.g_request_headers(3).name := 'notify-title'; 70 | apex_web_service.g_request_headers(3).value := i_title; 71 | -- Message 72 | apex_web_service.g_request_headers(4).name := 'notify-message'; 73 | apex_web_service.g_request_headers(4).value := i_message; 74 | -- 75 | EXCEPTION 76 | WHEN OTHERS THEN 77 | -- Insert your own exception handling here 78 | NULL; 79 | RAISE; 80 | END set_http_headers; 81 | -- 82 | /**************************************************************************** 83 | * Purpose: Send Websocket Notification over REST to connected users 84 | * Author: Daniel Hochleitner 85 | * Created: 17.06.2016 86 | * Changed: 87 | ****************************************************************************/ 88 | PROCEDURE do_rest_notify_user(i_userid IN VARCHAR2, 89 | i_room IN VARCHAR2, 90 | i_type IN VARCHAR2, 91 | i_title IN VARCHAR2, 92 | i_message IN VARCHAR2, 93 | i_optparam IN VARCHAR2 := NULL) IS 94 | -- 95 | l_response CLOB; 96 | l_url VARCHAR2(200); 97 | l_http_auth_user ws_notify_api.g_ws_basic_auth_user%TYPE; 98 | l_http_auth_pwd ws_notify_api.g_ws_basic_auth_pwd%TYPE; 99 | l_param_name VARCHAR2(400); 100 | l_param_value VARCHAR2(400); 101 | l_title VARCHAR2(1000); 102 | l_message VARCHAR2(4000); 103 | l_spacer VARCHAR2(10) := '||'; 104 | -- 105 | BEGIN 106 | -- vars 107 | l_http_auth_user := ws_notify_api.g_ws_basic_auth_user; 108 | l_http_auth_pwd := ws_notify_api.g_ws_basic_auth_pwd; 109 | l_title := REPLACE(REPLACE(substr(i_title, 110 | 1, 111 | 1000), 112 | chr(10), 113 | ' '), 114 | chr(13), 115 | ' '); 116 | l_message := REPLACE(REPLACE(substr(i_message, 117 | 1, 118 | 4000), 119 | chr(10), 120 | ' '), 121 | chr(13), 122 | ' '); 123 | -- 124 | -- set HTTP Header (with title und message) 125 | ws_notify_api.set_http_headers(i_title => l_title, 126 | i_message => l_message); 127 | -- build request params 128 | IF i_optparam IS NULL THEN 129 | l_param_name := 'userid' || l_spacer || 'room' || l_spacer || 'type'; 130 | l_param_value := lower(i_userid) || l_spacer || lower(i_room) || 131 | l_spacer || lower(i_type); 132 | ELSE 133 | l_param_name := 'userid' || l_spacer || 'room' || l_spacer || 'type' || 134 | l_spacer || 'optparam'; 135 | l_param_value := lower(i_userid) || l_spacer || lower(i_room) || 136 | l_spacer || lower(i_type) || l_spacer || i_optparam; 137 | END IF; 138 | -- URL 139 | l_url := ws_notify_api.g_ws_rest_base_url; 140 | -- REST call 141 | -- HTTP 142 | IF lower(ws_notify_api.g_ws_rest_proto) = 'http' THEN 143 | l_response := apex_web_service.make_rest_request(p_url => l_url, 144 | p_http_method => 'GET', 145 | p_username => l_http_auth_user, 146 | p_password => l_http_auth_pwd, 147 | p_parm_name => apex_util.string_to_table(l_param_name, 148 | l_spacer), 149 | p_parm_value => apex_util.string_to_table(l_param_value, 150 | l_spacer)); 151 | -- HTTPS 152 | ELSIF lower(ws_notify_api.g_ws_rest_proto) = 'https' THEN 153 | l_response := apex_web_service.make_rest_request(p_url => l_url, 154 | p_http_method => 'GET', 155 | p_username => l_http_auth_user, 156 | p_password => l_http_auth_pwd, 157 | p_parm_name => apex_util.string_to_table(l_param_name, 158 | l_spacer), 159 | p_parm_value => apex_util.string_to_table(l_param_value, 160 | l_spacer), 161 | p_wallet_path => ws_notify_api.g_ssl_wallet_path, 162 | p_wallet_pwd => ws_notify_api.g_ssl_wallet_pwd); 163 | END IF; 164 | -- 165 | -- check http status (2XX) 166 | ws_notify_api.check_error_http_status; 167 | -- 168 | EXCEPTION 169 | WHEN OTHERS THEN 170 | -- Insert your own exception handling here 171 | NULL; 172 | RAISE; 173 | END do_rest_notify_user; 174 | -- 175 | /**************************************************************************** 176 | * Purpose: Send Websocket Notification to User / Room: Private / Type: Info 177 | * Author: Daniel Hochleitner 178 | * Created: 17.06.2016 179 | * Changed: 180 | ****************************************************************************/ 181 | PROCEDURE do_notify_user_private_info(i_userid IN VARCHAR2, 182 | i_title IN VARCHAR2, 183 | i_message IN VARCHAR2, 184 | i_optparam IN VARCHAR2 := NULL) IS 185 | -- 186 | BEGIN 187 | -- REST call 188 | ws_notify_api.do_rest_notify_user(i_userid => i_userid, 189 | i_room => 'private', 190 | i_type => 'info', 191 | i_title => i_title, 192 | i_message => i_message, 193 | i_optparam => i_optparam); 194 | -- 195 | EXCEPTION 196 | WHEN OTHERS THEN 197 | -- Insert your own exception handling here 198 | NULL; 199 | RAISE; 200 | END do_notify_user_private_info; 201 | -- 202 | /**************************************************************************** 203 | * Purpose: Send Websocket Notification to User / Room: Private / Type: Success 204 | * Author: Daniel Hochleitner 205 | * Created: 17.06.2016 206 | * Changed: 207 | ****************************************************************************/ 208 | PROCEDURE do_notify_user_private_success(i_userid IN VARCHAR2, 209 | i_title IN VARCHAR2, 210 | i_message IN VARCHAR2, 211 | i_optparam IN VARCHAR2 := NULL) IS 212 | -- 213 | BEGIN 214 | -- REST call 215 | ws_notify_api.do_rest_notify_user(i_userid => i_userid, 216 | i_room => 'private', 217 | i_type => 'success', 218 | i_title => i_title, 219 | i_message => i_message, 220 | i_optparam => i_optparam); 221 | -- 222 | EXCEPTION 223 | WHEN OTHERS THEN 224 | -- Insert your own exception handling here 225 | NULL; 226 | RAISE; 227 | END do_notify_user_private_success; 228 | -- 229 | /**************************************************************************** 230 | * Purpose: Send Websocket Notification to User / Room: Private / Type: Warn 231 | * Author: Daniel Hochleitner 232 | * Created: 17.06.2016 233 | * Changed: 234 | ****************************************************************************/ 235 | PROCEDURE do_notify_user_private_warn(i_userid IN VARCHAR2, 236 | i_title IN VARCHAR2, 237 | i_message IN VARCHAR2, 238 | i_optparam IN VARCHAR2 := NULL) IS 239 | -- 240 | BEGIN 241 | -- REST call 242 | ws_notify_api.do_rest_notify_user(i_userid => i_userid, 243 | i_room => 'private', 244 | i_type => 'warn', 245 | i_title => i_title, 246 | i_message => i_message, 247 | i_optparam => i_optparam); 248 | -- 249 | EXCEPTION 250 | WHEN OTHERS THEN 251 | -- Insert your own exception handling here 252 | NULL; 253 | RAISE; 254 | END do_notify_user_private_warn; 255 | -- 256 | /**************************************************************************** 257 | * Purpose: Send Websocket Notification to User / Room: Private / Type: Error 258 | * Author: Daniel Hochleitner 259 | * Created: 17.06.2016 260 | * Changed: 261 | ****************************************************************************/ 262 | PROCEDURE do_notify_user_private_error(i_userid IN VARCHAR2, 263 | i_title IN VARCHAR2, 264 | i_message IN VARCHAR2, 265 | i_optparam IN VARCHAR2 := NULL) IS 266 | -- 267 | BEGIN 268 | -- REST call 269 | ws_notify_api.do_rest_notify_user(i_userid => i_userid, 270 | i_room => 'private', 271 | i_type => 'error', 272 | i_title => i_title, 273 | i_message => i_message, 274 | i_optparam => i_optparam); 275 | -- 276 | EXCEPTION 277 | WHEN OTHERS THEN 278 | -- Insert your own exception handling here 279 | NULL; 280 | RAISE; 281 | END do_notify_user_private_error; 282 | -- 283 | /**************************************************************************** 284 | * Purpose: Send Websocket Notification to User / Room: Public / Type: Info 285 | * Author: Daniel Hochleitner 286 | * Created: 17.06.2016 287 | * Changed: 288 | ****************************************************************************/ 289 | PROCEDURE do_notify_user_public_info(i_userid IN VARCHAR2, 290 | i_title IN VARCHAR2, 291 | i_message IN VARCHAR2, 292 | i_optparam IN VARCHAR2 := NULL) IS 293 | -- 294 | BEGIN 295 | -- REST call 296 | ws_notify_api.do_rest_notify_user(i_userid => i_userid, 297 | i_room => 'public', 298 | i_type => 'info', 299 | i_title => i_title, 300 | i_message => i_message, 301 | i_optparam => i_optparam); 302 | -- 303 | EXCEPTION 304 | WHEN OTHERS THEN 305 | -- Insert your own exception handling here 306 | NULL; 307 | RAISE; 308 | END do_notify_user_public_info; 309 | -- 310 | /**************************************************************************** 311 | * Purpose: Send Websocket Notification to User / Room: Public / Type: Success 312 | * Author: Daniel Hochleitner 313 | * Created: 17.06.2016 314 | * Changed: 315 | ****************************************************************************/ 316 | PROCEDURE do_notify_user_public_success(i_userid IN VARCHAR2, 317 | i_title IN VARCHAR2, 318 | i_message IN VARCHAR2, 319 | i_optparam IN VARCHAR2 := NULL) IS 320 | -- 321 | BEGIN 322 | -- REST call 323 | ws_notify_api.do_rest_notify_user(i_userid => i_userid, 324 | i_room => 'public', 325 | i_type => 'success', 326 | i_title => i_title, 327 | i_message => i_message, 328 | i_optparam => i_optparam); 329 | -- 330 | EXCEPTION 331 | WHEN OTHERS THEN 332 | -- Insert your own exception handling here 333 | NULL; 334 | RAISE; 335 | END do_notify_user_public_success; 336 | -- 337 | /**************************************************************************** 338 | * Purpose: Send Websocket Notification to User / Room: Public / Type: Warn 339 | * Author: Daniel Hochleitner 340 | * Created: 17.06.2016 341 | * Changed: 342 | ****************************************************************************/ 343 | PROCEDURE do_notify_user_public_warn(i_userid IN VARCHAR2, 344 | i_title IN VARCHAR2, 345 | i_message IN VARCHAR2, 346 | i_optparam IN VARCHAR2 := NULL) IS 347 | -- 348 | BEGIN 349 | -- REST call 350 | ws_notify_api.do_rest_notify_user(i_userid => i_userid, 351 | i_room => 'public', 352 | i_type => 'warn', 353 | i_title => i_title, 354 | i_message => i_message, 355 | i_optparam => i_optparam); 356 | -- 357 | EXCEPTION 358 | WHEN OTHERS THEN 359 | -- Insert your own exception handling here 360 | NULL; 361 | RAISE; 362 | END do_notify_user_public_warn; 363 | -- 364 | /**************************************************************************** 365 | * Purpose: Send Websocket Notification to User / Room: Public / Type: Error 366 | * Author: Daniel Hochleitner 367 | * Created: 17.06.2016 368 | * Changed: 369 | ****************************************************************************/ 370 | PROCEDURE do_notify_user_public_error(i_userid IN VARCHAR2, 371 | i_title IN VARCHAR2, 372 | i_message IN VARCHAR2, 373 | i_optparam IN VARCHAR2 := NULL) IS 374 | -- 375 | BEGIN 376 | -- REST call 377 | ws_notify_api.do_rest_notify_user(i_userid => i_userid, 378 | i_room => 'public', 379 | i_type => 'error', 380 | i_title => i_title, 381 | i_message => i_message, 382 | i_optparam => i_optparam); 383 | -- 384 | EXCEPTION 385 | WHEN OTHERS THEN 386 | -- Insert your own exception handling here 387 | NULL; 388 | RAISE; 389 | END do_notify_user_public_error; 390 | -- 391 | /**************************************************************************** 392 | * Purpose: Send Websocket Notification to all Users / Room: Public / Type: Info 393 | * Author: Daniel Hochleitner 394 | * Created: 17.06.2016 395 | * Changed: 396 | ****************************************************************************/ 397 | PROCEDURE do_notify_all_public_info(i_title IN VARCHAR2, 398 | i_message IN VARCHAR2, 399 | i_optparam IN VARCHAR2 := NULL) IS 400 | -- 401 | BEGIN 402 | -- REST call 403 | ws_notify_api.do_rest_notify_user(i_userid => 'all', 404 | i_room => 'public', 405 | i_type => 'info', 406 | i_title => i_title, 407 | i_message => i_message, 408 | i_optparam => i_optparam); 409 | -- 410 | EXCEPTION 411 | WHEN OTHERS THEN 412 | -- Insert your own exception handling here 413 | NULL; 414 | RAISE; 415 | END do_notify_all_public_info; 416 | -- 417 | /**************************************************************************** 418 | * Purpose: Send Websocket Notification to all Users / Room: Public / Type: Success 419 | * Author: Daniel Hochleitner 420 | * Created: 17.06.2016 421 | * Changed: 422 | ****************************************************************************/ 423 | PROCEDURE do_notify_all_public_success(i_title IN VARCHAR2, 424 | i_message IN VARCHAR2, 425 | i_optparam IN VARCHAR2 := NULL) IS 426 | -- 427 | BEGIN 428 | -- REST call 429 | ws_notify_api.do_rest_notify_user(i_userid => 'all', 430 | i_room => 'public', 431 | i_type => 'success', 432 | i_title => i_title, 433 | i_message => i_message, 434 | i_optparam => i_optparam); 435 | -- 436 | EXCEPTION 437 | WHEN OTHERS THEN 438 | -- Insert your own exception handling here 439 | NULL; 440 | RAISE; 441 | END do_notify_all_public_success; 442 | -- 443 | /**************************************************************************** 444 | * Purpose: Send Websocket Notification to all Users / Room: Public / Type: Warn 445 | * Author: Daniel Hochleitner 446 | * Created: 17.06.2016 447 | * Changed: 448 | ****************************************************************************/ 449 | PROCEDURE do_notify_all_public_warn(i_title IN VARCHAR2, 450 | i_message IN VARCHAR2, 451 | i_optparam IN VARCHAR2 := NULL) IS 452 | -- 453 | BEGIN 454 | -- REST call 455 | ws_notify_api.do_rest_notify_user(i_userid => 'all', 456 | i_room => 'public', 457 | i_type => 'warn', 458 | i_title => i_title, 459 | i_message => i_message, 460 | i_optparam => i_optparam); 461 | -- 462 | EXCEPTION 463 | WHEN OTHERS THEN 464 | -- Insert your own exception handling here 465 | NULL; 466 | RAISE; 467 | END do_notify_all_public_warn; 468 | -- 469 | /**************************************************************************** 470 | * Purpose: Send Websocket Notification to all Users / Room: Public / Type: Error 471 | * Author: Daniel Hochleitner 472 | * Created: 17.06.2016 473 | * Changed: 474 | ****************************************************************************/ 475 | PROCEDURE do_notify_all_public_error(i_title IN VARCHAR2, 476 | i_message IN VARCHAR2, 477 | i_optparam IN VARCHAR2 := NULL) IS 478 | -- 479 | BEGIN 480 | -- REST call 481 | ws_notify_api.do_rest_notify_user(i_userid => 'all', 482 | i_room => 'public', 483 | i_type => 'error', 484 | i_title => i_title, 485 | i_message => i_message, 486 | i_optparam => i_optparam); 487 | -- 488 | EXCEPTION 489 | WHEN OTHERS THEN 490 | -- Insert your own exception handling here 491 | NULL; 492 | RAISE; 493 | END do_notify_all_public_error; 494 | -- 495 | END ws_notify_api; 496 | / 497 | -------------------------------------------------------------------------------- /plsql/ws_notify_api.pks: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PACKAGE ws_notify_api IS 2 | -- 3 | -- API Package Spec for Node Notify Websocket REST Calls 4 | -- 5 | 6 | -- 7 | -- Websocket REST Call defaults 8 | -- 9 | g_ws_rest_host VARCHAR2(50) := 'localhost'; 10 | g_ws_rest_port VARCHAR2(50) := '8080'; 11 | g_ws_rest_path VARCHAR2(50) := '/notifyuser'; 12 | g_ws_rest_proto VARCHAR2(50) := 'http'; -- http or https 13 | g_ws_rest_base_url VARCHAR2(200) := ws_notify_api.g_ws_rest_proto || 14 | '://' || ws_notify_api.g_ws_rest_host || ':' || 15 | ws_notify_api.g_ws_rest_port || 16 | ws_notify_api.g_ws_rest_path; 17 | -- security defaults 18 | g_ws_basic_auth_user VARCHAR2(100) := ''; 19 | g_ws_basic_auth_pwd VARCHAR2(100) := ''; 20 | -- wallet info: only if g_ws_rest_proto = https 21 | g_ssl_wallet_path VARCHAR2(200) := 'file:/home/oracle/wallet'; -- set your local wallet path 22 | g_ssl_wallet_pwd VARCHAR2(100) := 'pwd'; -- set your wallet password 23 | -- 24 | -- Exceptions Error Codes 25 | -- 26 | error_http_status_code CONSTANT NUMBER := -20002; 27 | -- 28 | -- Public Functions and Procedures 29 | -- 30 | 31 | -- Send Websocket Notification over REST to connected users 32 | -- #param i_userid 33 | -- #param i_room ("private" or "public") 34 | -- #param i_type (info, success, warn, error) 35 | -- #param i_title 36 | -- #param i_message 37 | -- #param i_optparam (Optional Parameter String) 38 | PROCEDURE do_rest_notify_user(i_userid IN VARCHAR2, 39 | i_room IN VARCHAR2, 40 | i_type IN VARCHAR2, 41 | i_title IN VARCHAR2, 42 | i_message IN VARCHAR2, 43 | i_optparam IN VARCHAR2 := NULL); 44 | -- 45 | -- Send Websocket Notification to User / Room: Private / Type: Info 46 | -- #param i_userid 47 | -- #param i_title 48 | -- #param i_message 49 | -- #param i_optparam (Optional Parameter String) 50 | PROCEDURE do_notify_user_private_info(i_userid IN VARCHAR2, 51 | i_title IN VARCHAR2, 52 | i_message IN VARCHAR2, 53 | i_optparam IN VARCHAR2 := NULL); 54 | -- 55 | -- Send Websocket Notification to User / Room: Private / Type: Success 56 | -- #param i_userid 57 | -- #param i_title 58 | -- #param i_message 59 | -- #param i_optparam (Optional Parameter String) 60 | PROCEDURE do_notify_user_private_success(i_userid IN VARCHAR2, 61 | i_title IN VARCHAR2, 62 | i_message IN VARCHAR2, 63 | i_optparam IN VARCHAR2 := NULL); 64 | -- 65 | -- Send Websocket Notification to User / Room: Private / Type: Warn 66 | -- #param i_userid 67 | -- #param i_title 68 | -- #param i_message 69 | -- #param i_optparam (Optional Parameter String) 70 | PROCEDURE do_notify_user_private_warn(i_userid IN VARCHAR2, 71 | i_title IN VARCHAR2, 72 | i_message IN VARCHAR2, 73 | i_optparam IN VARCHAR2 := NULL); 74 | -- 75 | -- Send Websocket Notification to User / Room: Private / Type: Error 76 | -- #param i_userid 77 | -- #param i_title 78 | -- #param i_message 79 | -- #param i_optparam (Optional Parameter String) 80 | PROCEDURE do_notify_user_private_error(i_userid IN VARCHAR2, 81 | i_title IN VARCHAR2, 82 | i_message IN VARCHAR2, 83 | i_optparam IN VARCHAR2 := NULL); 84 | -- 85 | -- Send Websocket Notification to User / Room: Public / Type: Info 86 | -- #param i_userid 87 | -- #param i_title 88 | -- #param i_message 89 | -- #param i_optparam (Optional Parameter String) 90 | PROCEDURE do_notify_user_public_info(i_userid IN VARCHAR2, 91 | i_title IN VARCHAR2, 92 | i_message IN VARCHAR2, 93 | i_optparam IN VARCHAR2 := NULL); 94 | -- 95 | -- Send Websocket Notification to User / Room: Public / Type: Success 96 | -- #param i_userid 97 | -- #param i_title 98 | -- #param i_message 99 | -- #param i_optparam (Optional Parameter String) 100 | PROCEDURE do_notify_user_public_success(i_userid IN VARCHAR2, 101 | i_title IN VARCHAR2, 102 | i_message IN VARCHAR2, 103 | i_optparam IN VARCHAR2 := NULL); 104 | -- 105 | -- Send Websocket Notification to User / Room: Public / Type: Warn 106 | -- #param i_userid 107 | -- #param i_title 108 | -- #param i_message 109 | -- #param i_optparam (Optional Parameter String) 110 | PROCEDURE do_notify_user_public_warn(i_userid IN VARCHAR2, 111 | i_title IN VARCHAR2, 112 | i_message IN VARCHAR2, 113 | i_optparam IN VARCHAR2 := NULL); 114 | -- 115 | -- Send Websocket Notification to User / Room: Public / Type: Error 116 | -- #param i_userid 117 | -- #param i_title 118 | -- #param i_message 119 | -- #param i_optparam (Optional Parameter String) 120 | PROCEDURE do_notify_user_public_error(i_userid IN VARCHAR2, 121 | i_title IN VARCHAR2, 122 | i_message IN VARCHAR2, 123 | i_optparam IN VARCHAR2 := NULL); 124 | -- 125 | -- Send Websocket Notification to all Users / Room: Public / Type: Info 126 | -- #param i_title 127 | -- #param i_message 128 | -- #param i_optparam (Optional Parameter String) 129 | PROCEDURE do_notify_all_public_info(i_title IN VARCHAR2, 130 | i_message IN VARCHAR2, 131 | i_optparam IN VARCHAR2 := NULL); 132 | -- 133 | -- Send Websocket Notification to all Users / Room: Public / Type: Success 134 | -- #param i_title 135 | -- #param i_message 136 | -- #param i_optparam (Optional Parameter String) 137 | PROCEDURE do_notify_all_public_success(i_title IN VARCHAR2, 138 | i_message IN VARCHAR2, 139 | i_optparam IN VARCHAR2 := NULL); 140 | -- 141 | -- Send Websocket Notification to all Users / Room: Public / Type: Warn 142 | -- #param i_title 143 | -- #param i_message 144 | -- #param i_optparam (Optional Parameter String) 145 | PROCEDURE do_notify_all_public_warn(i_title IN VARCHAR2, 146 | i_message IN VARCHAR2, 147 | i_optparam IN VARCHAR2 := NULL); 148 | -- 149 | -- Send Websocket Notification to all Users / Room: Public / Type: Error 150 | -- #param i_title 151 | -- #param i_message 152 | -- #param i_optparam (Optional Parameter String) 153 | PROCEDURE do_notify_all_public_error(i_title IN VARCHAR2, 154 | i_message IN VARCHAR2, 155 | i_optparam IN VARCHAR2 := NULL); 156 | END ws_notify_api; 157 | / 158 | --------------------------------------------------------------------------------