├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .mocharc.yaml ├── .npmrc ├── README.md ├── babel.config.cjs ├── coverage ├── clover.xml ├── coverage-final.json ├── lcov-report │ ├── Blockchain.js.html │ ├── InvalidAuthException.js.html │ ├── base.css │ ├── block-navigation.js │ ├── favicon.png │ ├── index.html │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ ├── sorter.js │ └── src │ │ ├── Blockchain.js.html │ │ ├── Channel.js.html │ │ ├── InvalidAuthException.js.html │ │ ├── Logger.js.html │ │ ├── PieSocket.js.html │ │ ├── Portal.js.html │ │ ├── index.html │ │ └── misc │ │ ├── DefaultOptions.js.html │ │ └── index.html └── lcov.info ├── dist ├── main.js └── piesocket.js ├── examples ├── chatroom.html ├── index.html ├── videoroom-one-to-many.html ├── videoroom.html ├── webrtc.html └── websocket.html ├── index.html ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── Blockchain.js ├── Channel.js ├── InvalidAuthException.js ├── Logger.js ├── PieSocket.js ├── Portal.js ├── index.js └── misc │ ├── DefaultOptions.js │ ├── RTCIceCandidate.js │ ├── RTCPeerConnection.js │ ├── RTCSessionDescription.js │ └── WebSocket.js ├── test ├── Blockchain.test.js ├── Channel.test.js ├── InvalidAuthException.test.js ├── PieSocket.test.js ├── Portal.test.js └── e2e │ ├── Chatroom.html.test.js │ ├── Index.html.test.js │ └── Videoroom.html.test.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["google"], 7 | "parserOptions": { 8 | "ecmaVersion": 13, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "require-jsdoc": "off", //TODO: turn it on 13 | "max-len": ["error", 210], 14 | "prefer-promise-reject-errors": "off", 15 | "no-invalid-this": "off" 16 | } 17 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: true 14 | max-parallel: 1 15 | matrix: 16 | node-version: [18.x, 16.x, 14.x] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - name: Install dependencies 24 | run: npm ci 25 | - run: npm run build --if-present 26 | - run: npm start & 27 | - run: npm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.mocharc.yaml: -------------------------------------------------------------------------------- 1 | require: 2 | - '@babel/register' 3 | - '@babel/polyfill' -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-options="--openssl-legacy-provider" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PieSocket JavaScript Client 2 | 3 | A JavaScript Library for PieSocket Realtime. 4 | 5 | Note: This package is PieSocket Client SDK (to be used with frontend on browsers), if you are looking for the NodeJS Server SDK, please see [PieSocket-NodeJS](https://github.com/piesocket/piesocket-nodejs). 6 | 7 | ## Installation 8 | 9 | Yarn 10 | ``` 11 | yarn add piesocket-js 12 | ``` 13 | 14 | NPM 15 | ``` 16 | npm i piesocket-js 17 | ``` 18 | 19 | CDN 20 | ```html 21 | 22 | ``` 23 | 24 | ## Importing 25 | 26 | Import module: 27 | 28 | ```javascript 29 | import PieSocket from 'piesocket-js'; 30 | ``` 31 | 32 | With CDN/Browser: 33 | 34 | 35 | Use the `PieSocket.default` global variable, e.g. `var piesocket = new PieSocket.default({...})` 36 | 37 | 38 | ## How To Use 39 | PieSocketJS offers Channels and Portals. 40 | 41 | ### Channels 42 | Channels are realtime PubSub connections over WebSocket, they allow you to: 43 | 44 | - Subscribe to events on client side 45 | - Publish events from server 46 | - Publish events from client with C2C feature. 47 | 48 | 49 | See the [Channels documentation](https://www.piesocket.com/docs/3.0/channels) to learn more about how to use Channels. 50 | 51 | ### Portals 52 | Portals are programmable video streams over WebRTC, they allow you to build powerful video applications. 53 | 54 | See the [Portals documentation](https://www.piesocket.com/docs/3.0/portals) to learn more about how to use Portals. 55 | 56 | ## Configuration 57 | Complete list of allowed configuration options 58 | 59 | | Option | Description | Default | 60 | | ----------------------------- | ----------------------------------------------------------------------------- | -------------- | 61 | | apiKey | Required, Your PieSocket API key. | Demo key | 62 | | clusterId | Your API key's cluster ID. | `demo` | 63 | | clusterDomain | Custom domain, if you have configured one in your account. | `null` | 64 | | consoleLogs | Logs useful connection info if set to `true`. | `false` | 65 | | notifySelf | Receive messages sent by self, pass `0` to disabled. | `1` | 66 | | jwt | JWT authentication token, skips authentication endpoint call. | `null` | 67 | | presence | Enable presence events on any channel, pass `1` to enabled. | `0` | 68 | | authEndpoint | Authentication endpoint for private channels. | `/broadcasting/auth` | 69 | | authHeaders | Headers to include with authEndpoint call. | `{}` | 70 | | forceAuth | Force authentication on all channels. | `false` | 71 | | userId | User ID, used when `user` does not exists in JWT payload. | `anonymous` | 72 | | blockchainTestMode | Enable/disable test mode, defaults to `false` i.e., Ethereum main network. Set to `true` for Rinkeby test network. | `false` | 73 | | blockchainGasFee | Gas fee to set on Ethereum contract calls | `41000` | 74 | 75 | 76 | ## PieSocket Object 77 | List of available methods on the `PieSocket` object 78 | 79 | | Method | Description | Returns | 80 | | ----------------------------- | ----------------------------------------------------------------------------- | -------------- | 81 | | subscribe(channelId) | Subscribe to a channel. | Channel Object | 82 | | unsubscribe(channelId) | Un-subscribe from a channel. | Boolean | 83 | | getConnections() | Get list of all active connections/channels for this client. | Object | 84 | 85 | 86 | ## Channel Object 87 | List of available methods on the `Channel` object 88 | 89 | | Method | Description 90 | | ----------------------------- | ----------------------------------------------------------------------------- 91 | | listen("event-name", callback) | Listen to an event. 92 | | publish("event-name", data, meta) | Publish message from client. 93 | | getMemberByUUID(uuid) | Get a Presence member from their uuid. 94 | | on("lifecycle-event", callback) | Listen to lifecycle events on the native [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) connection. 95 | | confirmOnBlockchain(event, transaction_hash) | Create a proof-of-witness for a Blockchain message, on receiver's end. 96 | 97 | 98 | 99 | ## Development 100 | - Clone the repo `git clone git@github.com:piesocket/piesocket-js.git` 101 | - Run `npm install` 102 | - Run `npm start` 103 | - Open `http://localhost:8080` in browser 104 | 105 | Now you can interactively test the SDK, add features and fix bugs. 106 | 107 | 108 | Documentation: [PieSocket Realtime Docs](https://piesocket.com/docs) -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | var jestConfig = (api) => { 2 | // Cache configuration is a required option 3 | api.cache(false); 4 | 5 | const presets = [ 6 | [ 7 | "@babel/preset-env", 8 | { 9 | useBuiltIns: false, 10 | }, 11 | ], 12 | ]; 13 | 14 | return { presets }; 15 | }; 16 | 17 | module.exports = jestConfig; 18 | -------------------------------------------------------------------------------- /coverage/clover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /coverage/coverage-final.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/InvalidAuthException.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for InvalidAuthException.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files InvalidAuthException.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 2/2 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 4/4 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 2/2 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7  73 |   74 | 2x 75 | 2x 76 |   77 |   78 |  
export default class InvalidAuthException {
 79 |   constructor(message=null, name='InvalidAuthException') {
 80 |     this.message = message || 'Auth endpoint did not return a valid JWT Token, please see: https://www.piesocket.com/docs/3.0/authentication';
 81 |     this.name = name;
 82 |   }
 83 | }
 84 |  
85 | 86 |
87 |
88 | 93 | 94 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* yellow */ 156 | .cbranch-no { background: yellow !important; color: #111; } 157 | /* dark red */ 158 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 159 | .low .chart { border:1px solid #C21F39 } 160 | .highlighted, 161 | .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ 162 | background: #C21F39 !important; 163 | } 164 | /* medium red */ 165 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 166 | /* light red */ 167 | .low, .cline-no { background:#FCE1E5 } 168 | /* light green */ 169 | .high, .cline-yes { background:rgb(230,245,208) } 170 | /* medium green */ 171 | .cstat-yes { background:rgb(161,215,106) } 172 | /* dark green */ 173 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 174 | .high .chart { border:1px solid rgb(77,146,33) } 175 | /* dark yellow (gold) */ 176 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 177 | .medium .chart { border:1px solid #f9cd0b; } 178 | /* light yellow */ 179 | .medium { background: #fff4c2; } 180 | 181 | .cstat-skip { background: #ddd; color: #111; } 182 | .fstat-skip { background: #ddd; color: #111 !important; } 183 | .cbranch-skip { background: #ddd !important; color: #111; } 184 | 185 | span.cline-neutral { background: #eaeaea; } 186 | 187 | .coverage-summary td.empty { 188 | opacity: .5; 189 | padding-top: 4px; 190 | padding-bottom: 4px; 191 | line-height: 1; 192 | color: #888; 193 | } 194 | 195 | .cover-fill, .cover-empty { 196 | display:inline-block; 197 | height: 12px; 198 | } 199 | .chart { 200 | line-height: 0; 201 | } 202 | .cover-empty { 203 | background: white; 204 | } 205 | .cover-full { 206 | border-right: none !important; 207 | } 208 | pre.prettyprint { 209 | border: none !important; 210 | padding: 0 !important; 211 | margin: 0 !important; 212 | } 213 | .com { color: #999 !important; } 214 | .ignore-none { color: #999; font-weight: normal; } 215 | 216 | .wrapper { 217 | min-height: 100%; 218 | height: auto !important; 219 | height: 100%; 220 | margin: 0 auto -48px; 221 | } 222 | .footer, .push { 223 | height: 48px; 224 | } 225 | -------------------------------------------------------------------------------- /coverage/lcov-report/block-navigation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var jumpToCode = (function init() { 3 | // Classes of code we would like to highlight in the file view 4 | var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; 5 | 6 | // Elements to highlight in the file listing view 7 | var fileListingElements = ['td.pct.low']; 8 | 9 | // We don't want to select elements that are direct descendants of another match 10 | var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` 11 | 12 | // Selecter that finds elements on the page to which we can jump 13 | var selector = 14 | fileListingElements.join(', ') + 15 | ', ' + 16 | notSelector + 17 | missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` 18 | 19 | // The NodeList of matching elements 20 | var missingCoverageElements = document.querySelectorAll(selector); 21 | 22 | var currentIndex; 23 | 24 | function toggleClass(index) { 25 | missingCoverageElements 26 | .item(currentIndex) 27 | .classList.remove('highlighted'); 28 | missingCoverageElements.item(index).classList.add('highlighted'); 29 | } 30 | 31 | function makeCurrent(index) { 32 | toggleClass(index); 33 | currentIndex = index; 34 | missingCoverageElements.item(index).scrollIntoView({ 35 | behavior: 'smooth', 36 | block: 'center', 37 | inline: 'center' 38 | }); 39 | } 40 | 41 | function goToPrevious() { 42 | var nextIndex = 0; 43 | if (typeof currentIndex !== 'number' || currentIndex === 0) { 44 | nextIndex = missingCoverageElements.length - 1; 45 | } else if (missingCoverageElements.length > 1) { 46 | nextIndex = currentIndex - 1; 47 | } 48 | 49 | makeCurrent(nextIndex); 50 | } 51 | 52 | function goToNext() { 53 | var nextIndex = 0; 54 | 55 | if ( 56 | typeof currentIndex === 'number' && 57 | currentIndex < missingCoverageElements.length - 1 58 | ) { 59 | nextIndex = currentIndex + 1; 60 | } 61 | 62 | makeCurrent(nextIndex); 63 | } 64 | 65 | return function jump(event) { 66 | if ( 67 | document.getElementById('fileSearch') === document.activeElement && 68 | document.activeElement != null 69 | ) { 70 | // if we're currently focused on the search input, we don't want to navigate 71 | return; 72 | } 73 | 74 | switch (event.which) { 75 | case 78: // n 76 | case 74: // j 77 | goToNext(); 78 | break; 79 | case 66: // b 80 | case 75: // k 81 | case 80: // p 82 | goToPrevious(); 83 | break; 84 | } 85 | }; 86 | })(); 87 | window.addEventListener('keydown', jumpToCode); 88 | -------------------------------------------------------------------------------- /coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piesocket/piesocket-js/5f82831d10b8a5f730f93f43acd08b0003c7567e/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for All files 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files

23 |
24 | 25 |
26 | Unknown% 27 | Statements 28 | 0/0 29 |
30 | 31 | 32 |
33 | Unknown% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | Unknown% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | Unknown% 48 | Lines 49 | 0/0 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
FileStatementsBranchesFunctionsLines
83 |
84 |
85 |
86 | 91 | 92 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piesocket/piesocket-js/5f82831d10b8a5f730f93f43acd08b0003c7567e/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var addSorting = (function() { 3 | 'use strict'; 4 | var cols, 5 | currentSort = { 6 | index: 0, 7 | desc: false 8 | }; 9 | 10 | // returns the summary table element 11 | function getTable() { 12 | return document.querySelector('.coverage-summary'); 13 | } 14 | // returns the thead element of the summary table 15 | function getTableHeader() { 16 | return getTable().querySelector('thead tr'); 17 | } 18 | // returns the tbody element of the summary table 19 | function getTableBody() { 20 | return getTable().querySelector('tbody'); 21 | } 22 | // returns the th element for nth column 23 | function getNthColumn(n) { 24 | return getTableHeader().querySelectorAll('th')[n]; 25 | } 26 | 27 | function onFilterInput() { 28 | const searchValue = document.getElementById('fileSearch').value; 29 | const rows = document.getElementsByTagName('tbody')[0].children; 30 | for (let i = 0; i < rows.length; i++) { 31 | const row = rows[i]; 32 | if ( 33 | row.textContent 34 | .toLowerCase() 35 | .includes(searchValue.toLowerCase()) 36 | ) { 37 | row.style.display = ''; 38 | } else { 39 | row.style.display = 'none'; 40 | } 41 | } 42 | } 43 | 44 | // loads the search box 45 | function addSearchBox() { 46 | var template = document.getElementById('filterTemplate'); 47 | var templateClone = template.content.cloneNode(true); 48 | templateClone.getElementById('fileSearch').oninput = onFilterInput; 49 | template.parentElement.appendChild(templateClone); 50 | } 51 | 52 | // loads all columns 53 | function loadColumns() { 54 | var colNodes = getTableHeader().querySelectorAll('th'), 55 | colNode, 56 | cols = [], 57 | col, 58 | i; 59 | 60 | for (i = 0; i < colNodes.length; i += 1) { 61 | colNode = colNodes[i]; 62 | col = { 63 | key: colNode.getAttribute('data-col'), 64 | sortable: !colNode.getAttribute('data-nosort'), 65 | type: colNode.getAttribute('data-type') || 'string' 66 | }; 67 | cols.push(col); 68 | if (col.sortable) { 69 | col.defaultDescSort = col.type === 'number'; 70 | colNode.innerHTML = 71 | colNode.innerHTML + ''; 72 | } 73 | } 74 | return cols; 75 | } 76 | // attaches a data attribute to every tr element with an object 77 | // of data values keyed by column name 78 | function loadRowData(tableRow) { 79 | var tableCols = tableRow.querySelectorAll('td'), 80 | colNode, 81 | col, 82 | data = {}, 83 | i, 84 | val; 85 | for (i = 0; i < tableCols.length; i += 1) { 86 | colNode = tableCols[i]; 87 | col = cols[i]; 88 | val = colNode.getAttribute('data-value'); 89 | if (col.type === 'number') { 90 | val = Number(val); 91 | } 92 | data[col.key] = val; 93 | } 94 | return data; 95 | } 96 | // loads all row data 97 | function loadData() { 98 | var rows = getTableBody().querySelectorAll('tr'), 99 | i; 100 | 101 | for (i = 0; i < rows.length; i += 1) { 102 | rows[i].data = loadRowData(rows[i]); 103 | } 104 | } 105 | // sorts the table using the data for the ith column 106 | function sortByIndex(index, desc) { 107 | var key = cols[index].key, 108 | sorter = function(a, b) { 109 | a = a.data[key]; 110 | b = b.data[key]; 111 | return a < b ? -1 : a > b ? 1 : 0; 112 | }, 113 | finalSorter = sorter, 114 | tableBody = document.querySelector('.coverage-summary tbody'), 115 | rowNodes = tableBody.querySelectorAll('tr'), 116 | rows = [], 117 | i; 118 | 119 | if (desc) { 120 | finalSorter = function(a, b) { 121 | return -1 * sorter(a, b); 122 | }; 123 | } 124 | 125 | for (i = 0; i < rowNodes.length; i += 1) { 126 | rows.push(rowNodes[i]); 127 | tableBody.removeChild(rowNodes[i]); 128 | } 129 | 130 | rows.sort(finalSorter); 131 | 132 | for (i = 0; i < rows.length; i += 1) { 133 | tableBody.appendChild(rows[i]); 134 | } 135 | } 136 | // removes sort indicators for current column being sorted 137 | function removeSortIndicators() { 138 | var col = getNthColumn(currentSort.index), 139 | cls = col.className; 140 | 141 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 142 | col.className = cls; 143 | } 144 | // adds sort indicators for current column being sorted 145 | function addSortIndicators() { 146 | getNthColumn(currentSort.index).className += currentSort.desc 147 | ? ' sorted-desc' 148 | : ' sorted'; 149 | } 150 | // adds event listeners for all sorter widgets 151 | function enableUI() { 152 | var i, 153 | el, 154 | ithSorter = function ithSorter(i) { 155 | var col = cols[i]; 156 | 157 | return function() { 158 | var desc = col.defaultDescSort; 159 | 160 | if (currentSort.index === i) { 161 | desc = !currentSort.desc; 162 | } 163 | sortByIndex(i, desc); 164 | removeSortIndicators(); 165 | currentSort.index = i; 166 | currentSort.desc = desc; 167 | addSortIndicators(); 168 | }; 169 | }; 170 | for (i = 0; i < cols.length; i += 1) { 171 | if (cols[i].sortable) { 172 | // add the click event handler on the th so users 173 | // dont have to click on those tiny arrows 174 | el = getNthColumn(i).querySelector('.sorter').parentElement; 175 | if (el.addEventListener) { 176 | el.addEventListener('click', ithSorter(i)); 177 | } else { 178 | el.attachEvent('onclick', ithSorter(i)); 179 | } 180 | } 181 | } 182 | } 183 | // adds sorting functionality to the UI 184 | return function() { 185 | if (!getTable()) { 186 | return; 187 | } 188 | cols = loadColumns(); 189 | loadData(); 190 | addSearchBox(); 191 | addSortIndicators(); 192 | enableUI(); 193 | }; 194 | })(); 195 | 196 | window.addEventListener('load', addSorting); 197 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/InvalidAuthException.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/InvalidAuthException.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src InvalidAuthException.js

23 |
24 | 25 |
26 | 0% 27 | Statements 28 | 0/2 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/4 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/1 43 |
44 | 45 | 46 |
47 | 0% 48 | Lines 49 | 0/2 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7  73 |   74 |   75 |   76 |   77 |   78 |  
export default class InvalidAuthException {
 79 |   constructor(message=null, name='InvalidAuthException') {
 80 |     this.message = message || 'Auth endpoint did not return a valid JWT Token, please see: https://www.piesocket.com/docs/3.0/authentication';
 81 |     this.name = name;
 82 |   }
 83 | }
 84 |  
85 | 86 |
87 |
88 | 93 | 94 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/Logger.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/Logger.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src Logger.js

23 |
24 | 25 |
26 | 42.85% 27 | Statements 28 | 3/7 29 |
30 | 31 | 32 |
33 | 33.33% 34 | Branches 35 | 2/6 36 |
37 | 38 | 39 |
40 | 75% 41 | Functions 42 | 3/4 43 |
44 | 45 | 46 |
47 | 42.85% 48 | Lines 49 | 3/7 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 24  90 |   91 | 3x 92 |   93 |   94 |   95 | 2x 96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 | 1x 108 |   109 |   110 |   111 |   112 |  
export default class Logger {
113 |   constructor(options) {
114 |     this.options = options;
115 |   }
116 |  
117 |   log(...data) {
118 |     Iif (this.options.consoleLogs) {
119 |       console.log(...data);
120 |     }
121 |   }
122 |  
123 |   warn(...data) {
124 |     if (this.options.consoleLogs) {
125 |       console.warn(...data);
126 |     }
127 |   }
128 |  
129 |   error(...data) {
130 |     Iif (this.options.consoleLogs) {
131 |       console.error(...data);
132 |     }
133 |   }
134 | }
135 |  
136 | 137 |
138 |
139 | 144 | 145 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src

23 |
24 | 25 |
26 | 26.82% 27 | Statements 28 | 88/328 29 |
30 | 31 | 32 |
33 | 15.34% 34 | Branches 35 | 27/176 36 |
37 | 38 | 39 |
40 | 27.14% 41 | Functions 42 | 19/70 43 |
44 | 45 | 46 |
47 | 26.91% 48 | Lines 49 | 88/327 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 |
FileStatementsBranchesFunctionsLines
Blockchain.js 84 |
85 |
4.41%3/680%0/220%0/144.41%3/68
Channel.js 99 |
100 |
22.42%24/1078%6/7521.05%4/1922.64%24/106
InvalidAuthException.js 114 |
115 |
0%0/20%0/40%0/10%0/2
Logger.js 129 |
130 |
42.85%3/733.33%2/675%3/442.85%3/7
PieSocket.js 144 |
145 |
18.75%12/6416.12%5/3121.42%3/1418.75%12/64
Portal.js 159 |
160 |
57.5%46/8036.84%14/3850%9/1857.5%46/80
173 |
174 |
175 |
176 | 181 | 182 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/misc/DefaultOptions.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/misc/DefaultOptions.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/misc DefaultOptions.js

23 |
24 | 25 |
26 | 0% 27 | Statements 28 | 0/0 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 0% 48 | Lines 49 | 0/0 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16  82 |   83 |   84 |   85 |   86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |  
export default {
 97 |   version: 3,
 98 |   clusterId: 'demo',
 99 |   apiKey: 'oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm',
100 |   consoleLogs: false,
101 |   notifySelf: 0,
102 |   jwt: null,
103 |   presence: 0,
104 |   authEndpoint: '/broadcasting/auth',
105 |   authHeaders: {},
106 |   forceAuth: false,
107 |   userId: null,
108 |   blockchainTestMode: false,
109 |   blockchainGasFee: 41000,
110 | };
111 |  
112 | 113 |
114 |
115 | 120 | 121 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /coverage/lcov-report/src/misc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/misc 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/misc

23 |
24 | 25 |
26 | 0% 27 | Statements 28 | 0/0 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 0% 48 | Lines 49 | 0/0 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
DefaultOptions.js 84 |
85 |
0%0/00%0/00%0/00%0/0
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piesocket/piesocket-js/5f82831d10b8a5f730f93f43acd08b0003c7567e/coverage/lcov.info -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). 3 | * This devtool is neither made for production nor for readable output files. 4 | * It uses "eval()" calls to create a separate source file in the browser devtools. 5 | * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) 6 | * or disable the default devtool with "devtool: false". 7 | * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). 8 | */ 9 | /******/ (() => { // webpackBootstrap 10 | /******/ "use strict"; 11 | /******/ 12 | /******/ 13 | /******/ })() 14 | ; -------------------------------------------------------------------------------- /dist/piesocket.js: -------------------------------------------------------------------------------- 1 | var PieSocket;(()=>{"use strict";var t={d:(e,s)=>{for(var n in s)t.o(s,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:s[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};(()=>{t.r(e),t.d(e,{default:()=>M});class s{constructor(t){this.options=t}log(...t){this.options.consoleLogs&&console.log(...t)}warn(...t){this.options.consoleLogs&&console.warn(...t)}error(...t){this.options.consoleLogs&&console.error(...t)}}const n={};class i{constructor(t){this.options=t,this.apiKey=this.options.apiKey,this.channel=this.options.channelId,this.blockchainTestMode=this.options.blockchainTestMode,this.blockchainGasFee=this.options.blockchainGasFee,this.blockchainTestMode?this.contractAddress="0x2321c321828946153a845e69ee168f413e85c90d":this.contractAddress="0x2a840CA40E082DbF24610B62a978900BfCaB23D3"}async init(){const t=new Web3(window.ethereum),e=await ethereum.request({method:"eth_requestAccounts"});this.account=e[0],this.contract=new t.eth.Contract(n.abi,this.contractAddress)}checkWeb3(){return"undefined"==typeof Web3?(console.log("Web3.js is not installed!"),!1):void 0!==window.ethereum||(console.log("MetaMask is not installed!"),!1)}async confirm(t){return new Promise((async(e,s)=>{if(this.checkWeb3()){this.contract||await this.init();const n=this.contract.methods.confirm(t).send({from:this.account,gas:this.blockchainGasFee});n.on("transactionHash",e),n.on("error",(t=>{s(t)}))}}))}async send(t){return new Promise((async(e,s)=>{if(this.checkWeb3()){this.contract||await this.init();const n=await this.getTransactionHash(t),i=this.contract.methods.send(n.payload).send({from:this.account,gas:this.blockchainGasFee});i.on("transactionHash",(t=>{e({hash:t,id:n.transaction_id})})),i.on("error",(t=>{s(t)}))}else"undefined"==typeof Web3?s("Please install Web3.js"):s("Please install MetaMask")}))}async getTransactionHash(t){return new Promise(((e,s)=>{const n=new FormData;n.append("apiKey",this.apiKey),n.append("channel",this.channel),n.append("message",JSON.stringify(t)),n.append("contract",this.contractAddress);const i=new XMLHttpRequest;i.addEventListener("readystatechange",(function(){if(4===this.readyState)try{const t=JSON.parse(this.responseText);t.errors&&(console.error(`PieSocket Error: ${JSON.stringify(t.errors)}`),s()),t.success?e(t.success):s("Unknown error")}catch(t){console.error("Could not connect to Blockchain Messaging API, try later"),s()}})),i.addEventListener("error",(()=>{console.error("Blockchain Messaging API seems unreachable at the moment, try later"),s()})),i.open("POST","https://www.piesocket.com/api/blockchain/payloadHash"),i.setRequestHeader("Accept","application/json"),i.send(n)}))}}let o={};try{o=WebSocket}catch(t){}const r=o;class a{constructor(t,e,s=!0){this.events={},this.listeners={},this.members=[],this.portal=null,this.uuid=null,this.onSocketConnected=()=>{},this.onSocketError=()=>{},s&&this.init(t,e)}init(t,e){this.endpoint=t,this.identity=e,this.connection=this.connect(),this.shouldReconnect=!1,this.logger=new s(e)}getMemberByUUID(t){let e=null;for(let s=0;s{null!=e.candidate&&this.channel.publish("system:portal_candidate",{from:this.channel.uuid,to:t.from,ice:e.candidate})},s.ontrack=e=>{"video"==e.track.kind&&(this.participants[t.from].streams=e.streams,"function"==typeof this.identity.onParticipantJoined&&this.identity.onParticipantJoined(t.from,e.streams[0]))},s.onsignalingstatechange=e=>{this.isNegotiating[t.from]="stable"!=s.signalingState},this.localStream&&this.localStream.getTracks().forEach((t=>{s.addTrack(t,this.localStream)})),this.displayStream&&this.displayStream.getTracks().forEach((t=>{s.addTrack(t,this.displayStream)})),this.isNegotiating[t.from]=!1,s.onnegotiationneeded=async()=>{await this.sendVideoOffer(t,s,e)},this.participants[t.from]={rtc:s}}async onRemoteScreenStopped(t,e){"function"==typeof this.identity.onScreenSharingStopped&&this.identity.onScreenSharingStopped(t,e)}async onLocalScreen(t){t.getVideoTracks()[0].addEventListener("ended",(()=>{this.channel.publish("system:stopped_screen",{from:this.channel.uuid,streamId:t.id})})),this.displayStream=t;const e=this.participants;Object.keys(e).forEach((s=>{const n=e[s].rtc;t.getTracks().forEach((e=>{n.addTrack(e,t)}))}))}async shareScreen(){navigator.mediaDevices.getDisplayMedia().then(this.onLocalScreen.bind(this)).catch(this.errorHandler.bind(this))}async sendVideoOffer(t,e,s){if(this.isNegotiating[t.from])return void console.log("SKIP nested negotiations");this.isNegotiating[t.from]=!0;const n=await e.createOffer();await e.setLocalDescription(n),console.log("Making offer"),this.channel.publish("system:video_offer",{from:this.channel.uuid,to:t.from,sdp:e.localDescription})}removeParticipant(t){delete this.participants[t],"function"==typeof this.identity.onParticipantLeft&&this.identity.onParticipantLeft(t)}addIceCandidate(t){this.participants[t.from].rtc.addIceCandidate(new h(t.ice))}createAnswer(t){return new Promise((async(e,s)=>{if(this.participants[t.from]&&this.participants[t.from].rtc||(console.log("Starting call in createAnswer"),this.shareVideo(t,!1)),await this.participants[t.from].rtc.setRemoteDescription(new p(t.sdp)),"offer"==t.sdp.type){this.logger.log("Got an offer from "+t.from,t);const s=await this.participants[t.from].rtc.createAnswer();await this.participants[t.from].rtc.setLocalDescription(s),this.channel.publish("system:video_answer",{from:this.channel.uuid,to:t.from,sdp:this.participants[t.from].rtc.localDescription}),e()}else this.logger.log("Got an asnwer from "+t.from),e()}))}handleAnswer(t){this.participants[t.from].rtc.setRemoteDescription(new p(t.sdp))}getUserMediaSuccess(t){this.localStream=t,"function"==typeof this.identity.onLocalVideo&&this.identity.onLocalVideo(t,this),this.requestPeerVideo()}requestPeerVideo(){let t="system:portal_broadcaster";this.identity.shouldBroadcast||(t="system:portal_watcher"),this.channel.publish(t,{from:this.channel.uuid,isBroadcasting:this.identity.shouldBroadcast})}requestOfferFromPeer(){this.channel.publish("system:video_request",{from:this.channel.uuid,isBroadcasting:this.identity.shouldBroadcast})}errorHandler(t){this.logger.error("Portal error",t)}}class f{constructor(t=null,e="InvalidAuthException"){this.message=t||"Auth endpoint did not return a valid JWT Token, please see: https://www.piesocket.com/docs/3.0/authentication",this.name=e}}const y={version:3,clusterId:"demo",clusterDomain:null,apiKey:"oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm",consoleLogs:!1,notifySelf:0,jwt:null,presence:0,authEndpoint:"/broadcasting/auth",authHeaders:{},forceAuth:!1,userId:null,blockchainTestMode:!1,blockchainGasFee:41e3};var b,v=new Uint8Array(16);function w(){if(!b&&!(b="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return b(v)}const k=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;for(var S=[],C=0;C<256;++C)S.push((C+256).toString(16).substr(1));const P=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,s=(S[t[e+0]]+S[t[e+1]]+S[t[e+2]]+S[t[e+3]]+"-"+S[t[e+4]]+S[t[e+5]]+"-"+S[t[e+6]]+S[t[e+7]]+"-"+S[t[e+8]]+S[t[e+9]]+"-"+S[t[e+10]]+S[t[e+11]]+S[t[e+12]]+S[t[e+13]]+S[t[e+14]]+S[t[e+15]]).toLowerCase();if(!function(t){return"string"==typeof t&&k.test(t)}(s))throw TypeError("Stringified UUID is invalid");return s},E=function(t,e,s){var n=(t=t||{}).random||(t.rng||w)();if(n[6]=15&n[6]|64,n[8]=63&n[8]|128,e){s=s||0;for(var i=0;i<16;++i)e[s+i]=n[i];return e}return P(n)},M=class{constructor(t){t=t||{},this.options={...y,...t},this.connections={},this.logger=new s(this.options)}async subscribe(t,e={}){return new Promise((async(s,n)=>{(e.video||e.audio||e.portal)&&(this.options.notifySelf=!0);const i=E(),o=await this.getEndpoint(t,i);if(this.connections[t])this.logger.log("Returning existing channel",t),s(this.connections[t]);else{this.logger.log("Creating new channel",t);const r=new a(o,{channelId:t,onSocketConnected:()=>{r.uuid=i,(e.video||e.audio||e.portal)&&(r.portal=new g(r,{...this.options,...e})),this.connections[t]=r,s(r)},onSocketError:()=>{n("Failed to make websocket connection")},...this.options});"undefined"==typeof WebSocket&&(r.uuid=i,this.connections[t]=r,s(r))}}))}unsubscribe(t){return!!this.connections[t]&&(this.connections[t].shouldReconnect=!1,this.connections[t].connection.close(),delete this.connections[t],!0)}getConnections(){return this.connections}async getAuthToken(t){return new Promise(((e,s)=>{const n=new FormData;n.append("channel_name",t);const i=new XMLHttpRequest;i.withCredentials=!0,i.addEventListener("readystatechange",(function(){if(4===this.readyState)try{const t=JSON.parse(this.responseText);e(t)}catch(t){s(new f("Could not fetch auth token","AuthEndpointResponseError"))}})),i.addEventListener("error",(()=>{s(new f("Could not fetch auth token","AuthEndpointError"))})),i.open("POST",this.options.authEndpoint),Object.keys(this.options.authHeaders).forEach((t=>{i.setRequestHeader(t,this.options.authHeaders[t])})),i.send(n)}))}isGuarded(t){return!!this.options.forceAuth||(""+t).startsWith("private-")}async getEndpoint(t,e){let s=`wss://${null==this.options.clusterDomain?`${this.options.clusterId}.piesocket.com`:this.options.clusterDomain}/v${this.options.version}/${t}?api_key=${this.options.apiKey}¬ify_self=${this.options.notifySelf}&source=jssdk&v=5.0.8&presence=${this.options.presence}`;if(this.options.jwt)s=s+"&jwt="+this.options.jwt;else if(this.isGuarded(t)){const e=await this.getAuthToken(t);e.auth&&(s=s+"&jwt="+e.auth)}return this.options.userId&&(s=s+"&user="+this.options.userId),s=s+"&uuid="+e,s}}})(),PieSocket=e})(); -------------------------------------------------------------------------------- /examples/chatroom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Realtime Chatroom - PieSocket Channels 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 37 | 38 | 39 |
40 | 41 |
42 | 43 |
44 |
45 | 46 |
47 |
49 | 50 |
51 |
    52 | 53 | 54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 |
63 | 64 |
65 |
66 | 67 |
68 |
69 | 70 |

71 | Members 72 |

73 |
    75 | 76 |
77 | 78 |
79 | 80 | 81 |
82 |
83 |
84 |
86 | 87 | 90 | 91 | 92 | 98 |
99 | 100 |
101 |
102 | 106 |
107 |
108 |
109 |
110 |
111 | 112 |
113 | 114 |
115 |
116 |
117 |
118 | 119 |
120 |
121 |
  • 122 |
    123 |
    124 |
    125 | 129 | 130 | 131 | 132 | 138 | 139 |
    140 |
    141 |
    142 |
    143 | 144 |
    145 |

    146 | Wrote 147 |

    148 |
    149 |
    150 |

    151 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 152 | Tincidunt nunc ipsum tempor purus vitae id. Morbi in 153 | vestibulum nec varius. Et diam cursus quis sed purus 154 | nam. Scelerisque amet elit non sit ut tincidunt 155 | condimentum. Nisl ultrices eu venenatis diam. 156 |

    157 |
    158 |
    159 |
    160 |
    161 |
  • 162 |
    163 |
    164 |
  • 165 | 168 |
    169 |

    Calvin Hawkins

    170 |

    Online

    171 |
    172 |
  • 173 |
    174 |
    175 | 176 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/videoroom-one-to-many.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Realtime Chatroom - PieSocket Channels 9 | 10 | 11 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
    41 | 42 | 59 | 60 | 61 |
    62 | 63 |
    64 | 65 |
    66 |
    67 | 68 |
    69 |
    71 | 72 |
    73 |
      74 |
      75 | 76 | You 77 |
      78 |
    79 |
    80 | 81 |
    82 |
    83 | 84 |
    85 |
    86 |
    87 | 88 |
    89 |
    90 | 91 |
    92 |
    93 | 94 |

    95 | Members 96 |

    97 |
      98 | 99 |
    100 | 101 |
    102 |
    103 | 104 |
    105 |
    106 |
    107 |
    108 | 109 |
    110 |
    111 |
  • 112 |
    113 |
    114 |
    115 | 119 | 120 | 121 | 122 | 128 | 129 |
    130 |
    131 |
    132 |
    133 | 134 |
    135 |

    136 | Wrote 137 |

    138 |
    139 |
    140 |

    141 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 142 | Tincidunt nunc ipsum tempor purus vitae id. Morbi in 143 | vestibulum nec varius. Et diam cursus quis sed purus 144 | nam. Scelerisque amet elit non sit ut tincidunt 145 | condimentum. Nisl ultrices eu venenatis diam. 146 |

    147 |
    148 |
    149 |
    150 |
    151 |
  • 152 |
    153 |
    154 |
  • 155 | 158 |
    159 |

    Calvin Hawkins

    160 |

    Online

    161 |
    162 |
  • 163 |
    164 |
    165 | 166 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /examples/videoroom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Realtime Chatroom - PieSocket Channels 9 | 10 | 11 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
    41 | 42 | 59 | 60 | 61 | 62 |
    63 | 64 |
    65 | 66 |
    67 |
    68 | 72 | 73 | 74 |
    75 |
    77 | 78 |
    79 |
      80 |
      81 | 82 | You 83 |
      84 |
    85 |
    86 | 87 |
    88 |
    89 | 90 |
    91 |
    92 |
    93 | 94 |
    95 |
    96 | 97 |
    98 |
    99 | 100 |

    101 | Members 102 |

    103 |
      104 | 105 |
    106 | 107 |
    108 |
    109 | 110 |
    111 |
    112 |
    113 |
    114 | 115 |
    116 |
    117 |
  • 118 |
    119 |
    120 |
    121 | 125 | 126 | 127 | 128 | 134 | 135 |
    136 |
    137 |
    138 |
    139 | 140 |
    141 |

    142 | Wrote 143 |

    144 |
    145 |
    146 |

    147 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 148 | Tincidunt nunc ipsum tempor purus vitae id. Morbi in 149 | vestibulum nec varius. Et diam cursus quis sed purus 150 | nam. Scelerisque amet elit non sit ut tincidunt 151 | condimentum. Nisl ultrices eu venenatis diam. 152 |

    153 |
    154 |
    155 |
    156 |
    157 |
  • 158 |
    159 |
    160 |
  • 161 | 164 |
    165 |

    Calvin Hawkins

    166 |

    Online

    167 |
    168 |
  • 169 |
    170 |
    171 | 172 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /examples/webrtc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PieSocket WebRTC Client 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | 16 |
    17 |
    18 |
    19 | 20 | 21 | 22 | 73 | 74 | -------------------------------------------------------------------------------- /examples/websocket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PieSocket WebSocket Client 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 |
    19 |
    20 | 21 | PieSocket Javascript Client Demo 22 | 23 |
    24 | 25 | What is a channel ID? 26 |
    27 |
    28 | 29 |
    30 | 31 |
    32 | 33 |
    34 |
    No active connections.
    35 |
    36 |
    37 | 38 | 39 |
    40 | 41 |
    
     42 |                 
    43 | 44 |
    45 |
    46 | 53 | 54 |
    55 | 56 | 57 | 58 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PieSocket JS 8 | 9 | 10 |

    Welcome to PieSocket JS

    11 |

    Use this SDK to integrate realtime features offered by PieSocket into your application.

    12 |
    13 |

    Example usage

    14 | 19 |

    Useful links

    20 | 24 | 25 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | testTimeout: 20000, 3 | maxWorkers: 1 4 | }; 5 | export default config; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "piesocket-js", 3 | "type":"module", 4 | "version": "5.1.0", 5 | "description": "PieSocket Javascript Client", 6 | "main": "src/index.js", 7 | "unpkg": "dist/piesocket.js", 8 | "scripts": { 9 | "test": "jest", 10 | "coverage": "jest --coverage", 11 | "lint": "eslint src --fix", 12 | 13 | "start": "NODE_ENV=development webpack serve --mode=development --config webpack.dev.js ", 14 | "build": "webpack --mode=production --config webpack.prod.js", 15 | "prepare": "npm run build", 16 | "watch": "webpack --mode=development --config webpack.dev.js --watch" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/piesocket/piesocket-js.git" 21 | }, 22 | "keywords": [ 23 | "piesocket", 24 | "client", 25 | "websocket", 26 | "pubsub", 27 | "http" 28 | ], 29 | "author": "PieSocket", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/piesocket/piesocket-js/issues" 33 | }, 34 | "homepage": "https://github.com/piesocket/piesocket-js#readme", 35 | "devDependencies": { 36 | "@babel/cli": "^7.16.8", 37 | "@babel/core": "^7.16.7", 38 | "@babel/polyfill": "^7.12.1", 39 | "@babel/preset-env": "^7.16.8", 40 | "@babel/register": "^7.16.9", 41 | "@webpack-cli/serve": "^1.1.0", 42 | "babel-jest": "^27.4.6", 43 | "cypress": "^9.3.1", 44 | "eslint": "^8.6.0", 45 | "eslint-config-google": "^0.14.0", 46 | "jest": "^27.4.7", 47 | "puppeteer": "^13.1.2", 48 | "webpack": "^5.9.0", 49 | "webpack-cli": "^4.2.0", 50 | "webpack-dev-server": "^4.7.3", 51 | "webpack-merge": "^5.4.0" 52 | }, 53 | "dependencies": { 54 | "uuid": "^8.3.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Blockchain.js: -------------------------------------------------------------------------------- 1 | const PieMessage = {}; 2 | const BCMEndpoint = 'https://www.piesocket.com/api/blockchain/payloadHash'; 3 | const PieMessageAddressDev = '0x2321c321828946153a845e69ee168f413e85c90d'; 4 | const PieMessageAddressProd = '0x2a840CA40E082DbF24610B62a978900BfCaB23D3'; 5 | 6 | export default class Blockchain { 7 | constructor(options) { 8 | this.options = options; 9 | 10 | this.apiKey = this.options.apiKey; 11 | this.channel = this.options.channelId; 12 | this.blockchainTestMode = this.options.blockchainTestMode; 13 | this.blockchainGasFee = this.options.blockchainGasFee; 14 | 15 | if (this.blockchainTestMode) { 16 | this.contractAddress = PieMessageAddressDev; 17 | } else { 18 | this.contractAddress = PieMessageAddressProd; 19 | } 20 | } 21 | 22 | async init() { 23 | const w3 = new Web3(window.ethereum); 24 | const accounts = await ethereum.request({method: 'eth_requestAccounts'}); 25 | this.account = accounts[0]; 26 | 27 | this.contract = new w3.eth.Contract(PieMessage.abi, this.contractAddress); 28 | } 29 | 30 | checkWeb3() { 31 | if (typeof Web3 == 'undefined') { 32 | console.log('Web3.js is not installed!'); 33 | return false; 34 | } 35 | 36 | if (typeof window.ethereum == 'undefined') { 37 | console.log('MetaMask is not installed!'); 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | async confirm(hash) { 45 | return new Promise(async (resolve, reject) => { 46 | if (this.checkWeb3()) { 47 | if (!this.contract) { 48 | await this.init(); 49 | } 50 | 51 | const receipt = this.contract.methods.confirm(hash).send({from: this.account, gas: this.blockchainGasFee}); 52 | receipt.on('transactionHash', resolve); 53 | receipt.on('error', (error) => { 54 | reject(error); 55 | }); 56 | } 57 | }); 58 | } 59 | 60 | async send(message) { 61 | return new Promise(async (resolve, reject) => { 62 | if (this.checkWeb3()) { 63 | if (!this.contract) { 64 | await this.init(); 65 | } 66 | 67 | const bacmHash = await this.getTransactionHash(message); 68 | 69 | const receipt = this.contract.methods.send(bacmHash.payload).send({from: this.account, gas: this.blockchainGasFee}); 70 | receipt.on('transactionHash', (hash) => { 71 | resolve({ 72 | hash: hash, 73 | id: bacmHash.transaction_id, 74 | }); 75 | }); 76 | receipt.on('error', (error) => { 77 | reject(error); 78 | }); 79 | } else { 80 | if (typeof Web3 == 'undefined') { 81 | reject('Please install Web3.js'); 82 | } else { 83 | reject('Please install MetaMask'); 84 | } 85 | } 86 | }); 87 | } 88 | 89 | async getTransactionHash(message) { 90 | return new Promise((resolve, reject) => { 91 | const data = new FormData(); 92 | 93 | data.append('apiKey', this.apiKey); 94 | data.append('channel', this.channel); 95 | data.append('message', JSON.stringify(message)); 96 | data.append('contract', this.contractAddress); 97 | 98 | const xhr = new XMLHttpRequest(); 99 | 100 | xhr.addEventListener('readystatechange', function() { 101 | if (this.readyState === 4) { 102 | try { 103 | const response = JSON.parse(this.responseText); 104 | if (response.errors) { 105 | console.error(`PieSocket Error: ${JSON.stringify(response.errors)}`); 106 | reject(); 107 | } 108 | 109 | if (response.success) { 110 | resolve(response.success); 111 | } else { 112 | reject('Unknown error'); 113 | } 114 | } catch (e) { 115 | console.error('Could not connect to Blockchain Messaging API, try later'); 116 | reject(); 117 | } 118 | } 119 | }); 120 | 121 | xhr.addEventListener('error', () => { 122 | console.error('Blockchain Messaging API seems unreachable at the moment, try later'); 123 | reject(); 124 | }); 125 | 126 | xhr.open('POST', BCMEndpoint); 127 | xhr.setRequestHeader('Accept', 'application/json'); 128 | xhr.send(data); 129 | }); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Channel.js: -------------------------------------------------------------------------------- 1 | import Logger from './Logger.js'; 2 | import Blockchain from './Blockchain.js'; 3 | import Socket from './misc/WebSocket.js'; 4 | 5 | export default class Channel { 6 | constructor(endpoint, identity, init=true) { 7 | this.events = {}; 8 | this.listeners = {}; 9 | this.members = []; 10 | this.portal = null; 11 | this.uuid = null; 12 | this.onSocketConnected = () => {}; 13 | this.onSocketError = () => {}; 14 | 15 | if (!init) { 16 | return; 17 | } 18 | 19 | this.init(endpoint, identity); 20 | } 21 | 22 | init(endpoint, identity) { 23 | this.endpoint = endpoint; 24 | this.identity = identity; 25 | this.connection = this.connect(); 26 | this.shouldReconnect = false; 27 | this.logger = new Logger(identity); 28 | } 29 | 30 | getMemberByUUID(uuid) { 31 | let member = null; 32 | for (let i = 0; i < this.members.length; i++) { 33 | if (this.members[i].uuid == uuid) { 34 | member = this.members[i]; 35 | break; 36 | } 37 | } 38 | return member; 39 | } 40 | 41 | getCurrentMember() { 42 | return this.getMemberByUUID(this.uuid); 43 | } 44 | 45 | connect() { 46 | const connection = new Socket(this.endpoint); 47 | connection.onmessage = this.onMessage.bind(this); 48 | connection.onopen = this.onOpen.bind(this); 49 | connection.onerror = this.onError.bind(this); 50 | connection.onclose = this.onClose.bind(this); 51 | 52 | if (this.identity.onSocketConnected) { 53 | this.onSocketConnected = this.identity.onSocketConnected; 54 | } 55 | 56 | if (this.identity.onSocketError) { 57 | this.onSocketError = this.identity.onSocketError; 58 | } 59 | 60 | return connection; 61 | } 62 | 63 | on(event, callback) { 64 | // Register lifecycle callbacks 65 | this.events[event] = callback; 66 | } 67 | 68 | listen(event, callback) { 69 | // Register user defined callbacks 70 | this.listeners[event] = callback; 71 | } 72 | 73 | 74 | send(data) { 75 | return this.connection.send(data); 76 | } 77 | 78 | async publish(event, data, meta) { 79 | if (meta && meta.blockchain) { 80 | return await this.sendOnBlockchain(event, data, meta); 81 | } 82 | return this.connection.send(JSON.stringify({ 83 | event: event, 84 | data: data, 85 | meta: meta, 86 | })); 87 | } 88 | 89 | 90 | async sendOnBlockchain(event, data, meta) { 91 | if (!this.blockchain) { 92 | this.blockchain = new Blockchain(this.identity); 93 | } 94 | 95 | try { 96 | const receipt = await this.blockchain.send(data); 97 | 98 | if (this.events['blockchain-hash']) { 99 | this.events['blockchain-hash'].bind(this)({ 100 | event: event, 101 | data: data, 102 | meta: meta, 103 | transactionHash: receipt.hash, 104 | }); 105 | } 106 | 107 | return this.connection.send(JSON.stringify({'event': event, 'data': data, 'meta': {...meta, 'transaction_id': receipt.id, 'transaction_hash': receipt.hash}})); 108 | } catch (e) { 109 | if (this.events['blockchain-error']) { 110 | this.events['blockchain-error'].bind(this)(e); 111 | } 112 | }; 113 | } 114 | 115 | async confirmOnBlockchain(event, transactionHash) { 116 | if (!this.blockchain) { 117 | this.blockchain = new Blockchain(this.identity); 118 | } 119 | 120 | try { 121 | const hash = await this.blockchain.confirm(transactionHash); 122 | 123 | if (this.events['blockchain-hash']) { 124 | this.events['blockchain-hash'].bind(this)({ 125 | event: event, 126 | confirmationHash: transactionHash, 127 | transactionHash: hash, 128 | }); 129 | } 130 | 131 | return this.connection.send(JSON.stringify({'event': event, 'data': transactionHash, 'meta': {'transaction_id': 1, 'transaction_hash': hash}})); 132 | } catch (e) { 133 | if (this.events['blockchain-error']) { 134 | this.events['blockchain-error'].bind(this)(e); 135 | } 136 | } 137 | } 138 | 139 | onMessage(e) { 140 | this.logger.log('Channel message:', e); 141 | 142 | try { 143 | const message = JSON.parse(e.data); 144 | if (message.error && message.error.length) { 145 | this.shouldReconnect = false; 146 | } 147 | 148 | // Fire event listeners 149 | if (message.event) { 150 | this.handleMemberHandshake(message); 151 | 152 | if (this.listeners[message.event]) { 153 | this.listeners[message.event].bind(this)(message.data, message.meta); 154 | } 155 | 156 | if (this.listeners['*']) { 157 | this.listeners['*'].bind(this)(message.event, message.data, message.meta); 158 | } 159 | } 160 | } catch (jsonException) { 161 | console.error(jsonException); 162 | } 163 | 164 | // Fire lifecycle callback 165 | if (this.events['message']) { 166 | this.events['message'].bind(this)(e); 167 | } 168 | } 169 | 170 | handleMemberHandshake(message) { 171 | if (message.event == 'system:member_list') { 172 | this.members = message.data.members; 173 | } else if (message.event == 'system:member_joined') { 174 | this.members = message.data.members; 175 | } else if (message.event == 'system:member_left') { 176 | this.members = message.data.members; 177 | if (this.portal) { 178 | this.portal.removeParticipant(message.data.member.uuid); 179 | } 180 | } else if (message.event == 'system:portal_broadcaster' && message.data.from != this.uuid) { 181 | this.portal.requestOfferFromPeer(message.data); 182 | } else if (message.event == 'system:stopped_screen' && message.data.from != this.uuid) { 183 | this.portal.onRemoteScreenStopped(message.data.from, message.data.streamId); 184 | } else if (message.event == 'system:portal_watcher' && message.data.from != this.uuid) { 185 | this.portal.shareVideo(message.data); 186 | } else if (message.event == 'system:video_request' && message.data.from != this.uuid) { 187 | this.portal.shareVideo(message.data); 188 | } else if (message.event == 'system:portal_candidate' && message.data.to == this.uuid) { 189 | this.portal.addIceCandidate(message.data); 190 | } else if (message.event == 'system:video_offer' && message.data.to == this.uuid) { 191 | this.portal.createAnswer(message.data); 192 | } else if (message.event == 'system:video_answer' && message.data.to == this.uuid) { 193 | this.portal.handleAnswer(message.data); 194 | } 195 | } 196 | 197 | onOpen(e) { 198 | this.logger.log('Channel connected:', e); 199 | this.shouldReconnect = true; 200 | 201 | // System init callback 202 | this.onSocketConnected(e); 203 | } 204 | 205 | onError(e) { 206 | this.logger.error('Channel error:', e); 207 | this.connection.close(); 208 | 209 | // System init error callback 210 | this.onSocketError(e); 211 | 212 | // User defined callback 213 | if (this.events['error']) { 214 | this.events['error'].bind(this)(e); 215 | } 216 | } 217 | 218 | onClose(e) { 219 | this.logger.warn('Channel closed:', e); 220 | this.reconnect(); 221 | 222 | // User defined callback 223 | if (this.events['close']) { 224 | this.events['close'].bind(this)(e); 225 | } 226 | } 227 | 228 | reconnect() { 229 | if (!this.shouldReconnect) { 230 | return; 231 | } 232 | this.logger.log('Reconnecting'); 233 | this.connection = this.connect(); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/InvalidAuthException.js: -------------------------------------------------------------------------------- 1 | export default class InvalidAuthException { 2 | constructor(message=null, name='InvalidAuthException') { 3 | this.message = message || 'Auth endpoint did not return a valid JWT Token, please see: https://www.piesocket.com/docs/3.0/authentication'; 4 | this.name = name; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Logger.js: -------------------------------------------------------------------------------- 1 | export default class Logger { 2 | constructor(options) { 3 | this.options = options; 4 | } 5 | 6 | log(...data) { 7 | if (this.options.consoleLogs) { 8 | console.log(...data); 9 | } 10 | } 11 | 12 | warn(...data) { 13 | if (this.options.consoleLogs) { 14 | console.warn(...data); 15 | } 16 | } 17 | 18 | error(...data) { 19 | if (this.options.consoleLogs) { 20 | console.error(...data); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/PieSocket.js: -------------------------------------------------------------------------------- 1 | import Channel from './Channel.js'; 2 | import Logger from './Logger.js'; 3 | import Portal from './Portal.js'; 4 | import InvalidAuthException from './InvalidAuthException.js'; 5 | import defaultOptions from './misc/DefaultOptions.js'; 6 | import {v4 as uuidv4} from 'uuid'; 7 | 8 | export default class PieSocket { 9 | constructor(options) { 10 | options = options || {}; 11 | 12 | this.options = {...defaultOptions, ...options}; 13 | this.connections = {}; 14 | this.logger = new Logger(this.options); 15 | } 16 | 17 | async subscribe(channelId, roomOptions={}) { 18 | return new Promise(async (resolve, reject) => { 19 | if (roomOptions.video || roomOptions.audio || roomOptions.portal) { 20 | // Force config when video is required 21 | this.options.notifySelf = true; 22 | } 23 | 24 | const uuid = uuidv4(); 25 | const endpoint = await this.getEndpoint(channelId, uuid); 26 | 27 | if (this.connections[channelId]) { 28 | this.logger.log('Returning existing channel', channelId); 29 | resolve(this.connections[channelId]); 30 | } else { 31 | this.logger.log('Creating new channel', channelId); 32 | const channel = new Channel(endpoint, { 33 | channelId: channelId, 34 | onSocketConnected: () => { 35 | channel.uuid = uuid; 36 | if (roomOptions.video || roomOptions.audio || roomOptions.portal) { 37 | channel.portal = new Portal(channel, { 38 | ...this.options, 39 | ...roomOptions, 40 | }); ``; 41 | } 42 | this.connections[channelId] = channel; 43 | resolve(channel); 44 | }, 45 | onSocketError: () => { 46 | reject('Failed to make websocket connection'); 47 | }, 48 | ...this.options, 49 | }); 50 | 51 | if (typeof WebSocket == 'undefined') { 52 | // Resolves the promise in case WebSocket is not defined 53 | channel.uuid = uuid; 54 | this.connections[channelId] = channel; 55 | resolve(channel); 56 | } 57 | } 58 | }); 59 | } 60 | 61 | unsubscribe(channelId) { 62 | if (this.connections[channelId]) { 63 | this.connections[channelId].shouldReconnect = false; 64 | this.connections[channelId].connection.close(); 65 | delete this.connections[channelId]; 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | getConnections() { 73 | return this.connections; 74 | } 75 | 76 | async getAuthToken(channel) { 77 | return new Promise((resolve, reject)=>{ 78 | const data = new FormData(); 79 | data.append('channel_name', channel); 80 | 81 | const xhr = new XMLHttpRequest(); 82 | xhr.withCredentials = true; 83 | 84 | xhr.addEventListener('readystatechange', function() { 85 | if (this.readyState === 4) { 86 | try { 87 | const response = JSON.parse(this.responseText); 88 | resolve(response); 89 | } catch (e) { 90 | reject(new InvalidAuthException('Could not fetch auth token', 'AuthEndpointResponseError')); 91 | } 92 | } 93 | }); 94 | xhr.addEventListener('error', ()=>{ 95 | reject(new InvalidAuthException('Could not fetch auth token', 'AuthEndpointError')); 96 | }); 97 | 98 | xhr.open('POST', this.options.authEndpoint); 99 | 100 | const headers = Object.keys(this.options.authHeaders); 101 | headers.forEach((header) => { 102 | xhr.setRequestHeader(header, this.options.authHeaders[header]); 103 | }); 104 | 105 | xhr.send(data); 106 | }); 107 | } 108 | 109 | isGuarded(channel) { 110 | if (this.options.forceAuth) { 111 | return true; 112 | } 113 | 114 | return (''+channel).startsWith('private-'); 115 | } 116 | 117 | async getEndpoint(channelId, uuid) { 118 | const clusterDomain = this.options.clusterDomain == null ? `${this.options.clusterId}.piesocket.com`:this.options.clusterDomain; 119 | let endpoint = `wss://${clusterDomain}/v${this.options.version}/${channelId}?api_key=${this.options.apiKey}¬ify_self=${this.options.notifySelf}&source=jssdk&v=5.0.8&presence=${this.options.presence}`; 120 | 121 | // Set auth 122 | if (this.options.jwt) { 123 | endpoint = endpoint+'&jwt='+this.options.jwt; 124 | } else if (this.isGuarded(channelId)) { 125 | const auth = await this.getAuthToken(channelId); 126 | if (auth.auth) { 127 | endpoint = endpoint + '&jwt='+auth.auth; 128 | } 129 | } 130 | 131 | // Set user identity 132 | if (this.options.userId) { 133 | endpoint = endpoint + '&user='+this.options.userId; 134 | } 135 | 136 | // Add uuid 137 | endpoint = endpoint+'&uuid='+uuid; 138 | 139 | return endpoint; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Portal.js: -------------------------------------------------------------------------------- 1 | import Logger from './Logger.js'; 2 | import IceCandidate from './misc/RTCIceCandidate.js'; 3 | import PeerConnection from './misc/RTCPeerConnection.js'; 4 | import SessionDescription from './misc/RTCSessionDescription.js'; 5 | const defaultPortalOptions = { 6 | shouldBroadcast: true, 7 | portal: true, 8 | video: false, 9 | audio: true, 10 | }; 11 | 12 | export default class Portal { 13 | /** 14 | * Creates a video room instance 15 | * @param {*} channel 16 | * @param {*} identity 17 | * 18 | * @return {void} Returns an instance of the Portal 19 | */ 20 | constructor(channel, identity) { 21 | this.channel = channel; 22 | this.logger = new Logger(identity); 23 | this.identity = {...defaultPortalOptions, ...identity}; 24 | this.localStream = null; 25 | this.displayStream = null; 26 | this.peerConnectionConfig = { 27 | iceServers: [ 28 | {urls: 'stun:stun.stunprotocol.org:3478'}, 29 | {urls: 'stun:stun.l.google.com:19302'}, 30 | ], 31 | }; 32 | this.constraints = { 33 | video: identity.video, 34 | audio: identity.audio, 35 | }; 36 | 37 | this.participants = []; 38 | this.isNegotiating = []; 39 | 40 | this.logger.log('Initializing video room'); 41 | this.init(); 42 | } 43 | 44 | /** 45 | * Initialize local video 46 | * @return {void} 47 | */ 48 | init() { 49 | if (!this.constraints.video && !this.constraints.audio) { 50 | this.requestPeerVideo(); 51 | return; 52 | } 53 | 54 | if ( 55 | typeof navigator != 'undefined' && 56 | navigator.mediaDevices.getUserMedia 57 | ) { 58 | navigator.mediaDevices 59 | .getUserMedia(this.constraints) 60 | .then(this.getUserMediaSuccess.bind(this)) 61 | .catch(this.errorHandler.bind(this)); 62 | return true; 63 | } else { 64 | this.logger.error('Your browser does not support getUserMedia API'); 65 | return false; 66 | } 67 | } 68 | 69 | shareVideo(signal, isCaller = true) { 70 | if (!this.identity.shouldBroadcast && isCaller && !signal.isBroadcasting) { 71 | console.log('Refusing to call, denied broadcast request'); 72 | return; 73 | } 74 | 75 | const rtcConnection = new PeerConnection(this.peerConnectionConfig); 76 | 77 | rtcConnection.onicecandidate = (event) => { 78 | if (event.candidate != null) { 79 | this.channel.publish('system:portal_candidate', { 80 | from: this.channel.uuid, 81 | to: signal.from, 82 | ice: event.candidate, 83 | }); 84 | } 85 | }; 86 | 87 | rtcConnection.ontrack = (event) => { 88 | if (event.track.kind != 'video') { 89 | return; 90 | } 91 | 92 | this.participants[signal.from].streams = event.streams; 93 | if (typeof this.identity.onParticipantJoined == 'function') { 94 | this.identity.onParticipantJoined(signal.from, event.streams[0]); 95 | } 96 | }; 97 | 98 | rtcConnection.onsignalingstatechange = (e) => { 99 | // Workaround for Chrome: skip nested negotiations 100 | this.isNegotiating[signal.from] = 101 | rtcConnection.signalingState != 'stable'; 102 | }; 103 | 104 | if (this.localStream) { 105 | this.localStream.getTracks().forEach((track) => { 106 | rtcConnection.addTrack(track, this.localStream); 107 | }); 108 | } 109 | 110 | if (this.displayStream) { 111 | this.displayStream.getTracks().forEach((track) => { 112 | rtcConnection.addTrack(track, this.displayStream); 113 | }); 114 | } 115 | 116 | this.isNegotiating[signal.from] = false; 117 | 118 | rtcConnection.onnegotiationneeded = async () => { 119 | await this.sendVideoOffer(signal, rtcConnection, isCaller); 120 | }; 121 | 122 | this.participants[signal.from] = { 123 | rtc: rtcConnection, 124 | }; 125 | } 126 | 127 | async onRemoteScreenStopped(uuid, streamId) { 128 | if (typeof this.identity.onScreenSharingStopped == 'function') { 129 | this.identity.onScreenSharingStopped(uuid, streamId); 130 | } 131 | } 132 | 133 | async onLocalScreen(screenStream) { 134 | // Register stop handler 135 | screenStream.getVideoTracks()[0].addEventListener('ended', () => { 136 | this.channel.publish('system:stopped_screen', { 137 | from: this.channel.uuid, 138 | streamId: screenStream.id, 139 | }); 140 | }); 141 | 142 | // Send it to other peers 143 | this.displayStream = screenStream; 144 | const participants = this.participants; 145 | 146 | const participantsIds = Object.keys(participants); 147 | participantsIds.forEach((id) => { 148 | const rtc = participants[id].rtc; 149 | screenStream.getTracks().forEach((track) => { 150 | rtc.addTrack(track, screenStream); 151 | }); 152 | }); 153 | } 154 | 155 | async shareScreen() { 156 | navigator.mediaDevices 157 | .getDisplayMedia() 158 | .then(this.onLocalScreen.bind(this)) 159 | .catch(this.errorHandler.bind(this)); 160 | } 161 | 162 | async sendVideoOffer(signal, rtcConnection, isCaller) { 163 | // if (!isCaller) { 164 | // console.log("Skipped, not the caller"); 165 | // return; 166 | // } 167 | 168 | if (this.isNegotiating[signal.from]) { 169 | console.log('SKIP nested negotiations'); 170 | return; 171 | } 172 | 173 | this.isNegotiating[signal.from] = true; 174 | 175 | const description = await rtcConnection.createOffer(); 176 | await rtcConnection.setLocalDescription(description); 177 | 178 | console.log('Making offer'); 179 | // Send a call offer 180 | this.channel.publish('system:video_offer', { 181 | from: this.channel.uuid, 182 | to: signal.from, 183 | sdp: rtcConnection.localDescription, 184 | }); 185 | } 186 | 187 | removeParticipant(uuid) { 188 | delete this.participants[uuid]; 189 | 190 | if (typeof this.identity.onParticipantLeft == 'function') { 191 | this.identity.onParticipantLeft(uuid); 192 | } 193 | } 194 | 195 | addIceCandidate(signal) { 196 | const rtcConnection = this.participants[signal.from].rtc; 197 | rtcConnection.addIceCandidate(new IceCandidate(signal.ice)); 198 | } 199 | 200 | createAnswer(signal) { 201 | return new Promise(async (resolve, reject) => { 202 | if ( 203 | !this.participants[signal.from] || 204 | !this.participants[signal.from].rtc 205 | ) { 206 | console.log('Starting call in createAnswer'); 207 | this.shareVideo(signal, false); 208 | } 209 | 210 | await this.participants[signal.from].rtc.setRemoteDescription( 211 | new SessionDescription(signal.sdp), 212 | ); 213 | // Only create answers in response to offers 214 | if (signal.sdp.type == 'offer') { 215 | this.logger.log('Got an offer from ' + signal.from, signal); 216 | const description = await this.participants[ 217 | signal.from 218 | ].rtc.createAnswer(); 219 | 220 | await this.participants[signal.from].rtc.setLocalDescription( 221 | description, 222 | ); 223 | this.channel.publish('system:video_answer', { 224 | from: this.channel.uuid, 225 | to: signal.from, 226 | sdp: this.participants[signal.from].rtc.localDescription, 227 | }); 228 | resolve(); 229 | } else { 230 | this.logger.log('Got an asnwer from ' + signal.from); 231 | 232 | resolve(); 233 | } 234 | }); 235 | } 236 | 237 | handleAnswer(signal) { 238 | this.participants[signal.from].rtc.setRemoteDescription( 239 | new SessionDescription(signal.sdp), 240 | ); 241 | } 242 | 243 | /** 244 | * Callback to handle local stream 245 | * @param {*} stream 246 | */ 247 | getUserMediaSuccess(stream) { 248 | this.localStream = stream; 249 | 250 | if (typeof this.identity.onLocalVideo == 'function') { 251 | this.identity.onLocalVideo(stream, this); 252 | } 253 | 254 | this.requestPeerVideo(); 255 | } 256 | 257 | requestPeerVideo() { 258 | let eventName = 'system:portal_broadcaster'; 259 | 260 | if (!this.identity.shouldBroadcast) { 261 | eventName = 'system:portal_watcher'; 262 | } 263 | 264 | this.channel.publish(eventName, { 265 | from: this.channel.uuid, 266 | isBroadcasting: this.identity.shouldBroadcast, 267 | }); 268 | } 269 | 270 | requestOfferFromPeer() { 271 | this.channel.publish('system:video_request', { 272 | from: this.channel.uuid, 273 | isBroadcasting: this.identity.shouldBroadcast, 274 | }); 275 | } 276 | 277 | errorHandler(e) { 278 | this.logger.error('Portal error', e); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import PieSocket from './PieSocket.js'; 2 | export default PieSocket; 3 | -------------------------------------------------------------------------------- /src/misc/DefaultOptions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | version: 3, 3 | clusterId: 'demo', 4 | clusterDomain: null, 5 | apiKey: 'oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm', 6 | consoleLogs: false, 7 | notifySelf: 0, 8 | jwt: null, 9 | presence: 0, 10 | authEndpoint: '/broadcasting/auth', 11 | authHeaders: {}, 12 | forceAuth: false, 13 | userId: null, 14 | blockchainTestMode: false, 15 | blockchainGasFee: 41000, 16 | }; 17 | -------------------------------------------------------------------------------- /src/misc/RTCIceCandidate.js: -------------------------------------------------------------------------------- 1 | let iceCandidate = {}; 2 | try { 3 | iceCandidate = RTCIceCandidate; 4 | } catch (e) {} 5 | export default iceCandidate; 6 | -------------------------------------------------------------------------------- /src/misc/RTCPeerConnection.js: -------------------------------------------------------------------------------- 1 | let peerConnection = {}; 2 | try { 3 | peerConnection = RTCPeerConnection; 4 | } catch (e) {} 5 | export default peerConnection; 6 | -------------------------------------------------------------------------------- /src/misc/RTCSessionDescription.js: -------------------------------------------------------------------------------- 1 | let sessionDescription = {}; 2 | try { 3 | sessionDescription = RTCSessionDescription; 4 | } catch (e) {} 5 | export default sessionDescription; 6 | -------------------------------------------------------------------------------- /src/misc/WebSocket.js: -------------------------------------------------------------------------------- 1 | let socket = {}; 2 | try { 3 | socket = WebSocket; 4 | } catch (e) {} 5 | export default socket; 6 | -------------------------------------------------------------------------------- /test/Blockchain.test.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import Blockchain from '../src/Blockchain'; 3 | 4 | const blockchainProperties = ["options", "apiKey","channel", "blockchainTestMode", "blockchainGasFee", "contractAddress"]; 5 | const PieMessageAddressDev = '0x2321c321828946153a845e69ee168f413e85c90d'; 6 | const PieMessageAddressProd = '0x2a840CA40E082DbF24610B62a978900BfCaB23D3'; 7 | 8 | describe('Blockchain', function () { 9 | 10 | let blockchain; 11 | 12 | beforeAll(()=>{ 13 | blockchain = new Blockchain({}); 14 | }); 15 | 16 | it("#constructor() - Creates a valid Blockchain instance", ()=>{ 17 | expect(Object.keys(blockchain)).toEqual(blockchainProperties); 18 | }); 19 | 20 | it("#constructor() - Test mode usage test contract address", ()=>{ 21 | let testModeBlockchain = new Blockchain({ 22 | blockchainTestMode: true 23 | }); 24 | 25 | expect(testModeBlockchain.contractAddress).toEqual(PieMessageAddressDev); 26 | }); 27 | 28 | it("#checkWeb3() - Returns error when Web3.js is not available", ()=>{ 29 | const result = blockchain.checkWeb3(); 30 | expect(result).toEqual(false); 31 | }); 32 | 33 | it("#confirm() - Returns error when Web3.js is not available", ()=>{ 34 | const result = blockchain.confirm("0x00000000000"); 35 | }); 36 | 37 | }); -------------------------------------------------------------------------------- /test/Channel.test.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import Channel from '../src/Channel'; 3 | import PieSocket from '../src/PieSocket'; 4 | import Logger from '../src/Logger'; 5 | import Blockchain from '../src/Blockchain'; 6 | 7 | const assert = require('assert'); 8 | 9 | //Mock 10 | const mockAddIceCandidate = jest.fn(); 11 | const mockCreateAnswer = jest.fn().mockImplementation(()=> { 12 | return Promise.resolve(); 13 | }); 14 | const mockSetLocalDescription = jest.fn().mockImplementation(()=> { 15 | return Promise.resolve(); 16 | }); 17 | const mockSetRemoteDescription = jest.fn().mockImplementation(()=> { 18 | return Promise.resolve(); 19 | }); 20 | jest.mock('../src/misc/RTCPeerConnection.js', () => { 21 | return jest.fn().mockImplementation(() => { 22 | return { 23 | addIceCandidate: mockAddIceCandidate, 24 | setRemoteDescription: mockSetRemoteDescription, 25 | createAnswer: mockCreateAnswer, 26 | setLocalDescription: mockSetLocalDescription 27 | }; 28 | }); 29 | }); 30 | 31 | jest.mock('../src/misc/RTCSessionDescription.js', () => { 32 | return jest.fn().mockImplementation(() => { 33 | return { 34 | }; 35 | }); 36 | }); 37 | 38 | jest.mock('../src/misc/RTCIceCandidate.js', () => { 39 | return jest.fn().mockImplementation(() => { 40 | return { 41 | }; 42 | }); 43 | }); 44 | 45 | const mockBlockchainSend = jest.fn().mockImplementation((data) => { 46 | if(data.name == "Testfail"){ 47 | return Promise.reject(); 48 | } 49 | 50 | return Promise.resolve({ 51 | hash: "0x000000000000000000" 52 | }) 53 | }); 54 | 55 | const mockBlockchainConfirm = jest.fn().mockImplementation((data) => { 56 | if(data.name == "Testfail"){ 57 | return Promise.reject(); 58 | } 59 | 60 | return Promise.resolve({ 61 | hash: "0x000000000000000000" 62 | }) 63 | }); 64 | 65 | jest.mock('../src/Blockchain.js', () => { 66 | return jest.fn().mockImplementation(() => { 67 | return { 68 | send: mockBlockchainSend, 69 | confirm: mockBlockchainConfirm 70 | }; 71 | }); 72 | }); 73 | 74 | 75 | const mockWebSocketClose = jest.fn(); 76 | const mockWebSocketSend = jest.fn(); 77 | jest.mock('../src/misc/WebSocket.js', () => { 78 | return jest.fn().mockImplementation(() => { 79 | return { 80 | close: mockWebSocketClose, 81 | send: mockWebSocketSend 82 | }; 83 | }); 84 | }); 85 | 86 | const channelProperties = ['events', 'listeners', 'members', 'portal', 'uuid', 'onSocketConnected', 'onSocketError', 'endpoint', 'identity', 'connection', 'shouldReconnect', 'logger']; 87 | const uninitializedChannelProperties = ['events', 'listeners', 'members', 'portal', 'uuid', 'onSocketConnected', 'onSocketError']; 88 | 89 | describe('Channel', function () { 90 | 91 | let piesocket; 92 | let channel; 93 | 94 | beforeAll(()=>{ 95 | piesocket = new PieSocket(); 96 | channel = new Channel(piesocket.getEndpoint("test"), piesocket.options); 97 | }); 98 | 99 | it('#constructor() - Returns Channel Instance', function () { 100 | expect(Object.keys(channel)).toEqual(channelProperties); 101 | 102 | assert.deepEqual(channel.identity, { ...piesocket.options }); 103 | assert.deepEqual(channel.events, {}); 104 | assert.deepEqual(channel.listeners, {}); 105 | assert.equal(channel.shouldReconnect, false); 106 | assert.deepEqual(channel.logger, new Logger(channel.identity)); 107 | }); 108 | 109 | it('#init() - Initializes Uninitialized Channel Instance', function () { 110 | const uninitializedChannel = new Channel(null, null, false); 111 | 112 | expect(Object.keys(uninitializedChannel)).toEqual(uninitializedChannelProperties); 113 | const connectSpy = jest.spyOn(uninitializedChannel, 'connect'); 114 | uninitializedChannel.init(piesocket.getEndpoint("test"), piesocket.options); 115 | 116 | const propsAfterInit = Object.keys(uninitializedChannel); 117 | propsAfterInit.splice(7,1); //remove connect property added by spy 118 | expect(propsAfterInit).toEqual(channelProperties); 119 | 120 | expect(connectSpy).toHaveBeenCalled() 121 | 122 | assert.deepEqual(uninitializedChannel.identity, { ...piesocket.options }); 123 | assert.deepEqual(uninitializedChannel.events, {}); 124 | assert.deepEqual(uninitializedChannel.listeners, {}); 125 | assert.equal(uninitializedChannel.shouldReconnect, false); 126 | assert.deepEqual(uninitializedChannel.logger, new Logger(channel.identity)); 127 | }); 128 | 129 | it('#on() - Registers event callbacks', function () { 130 | const callback = jest.fn(); 131 | channel.on('open', callback); 132 | expect(channel.events['open']).toEqual(callback); 133 | }) 134 | 135 | it('#listen() - Registers websocket onmessage event listeners', function () { 136 | const callback = jest.fn(); 137 | channel.listen('testevent', callback); 138 | expect(channel.listeners['testevent']).toEqual(callback); 139 | }) 140 | 141 | it('#send() - Sends message on websocket connection', function () { 142 | channel.send('testdata'); 143 | expect(mockWebSocketSend).toHaveBeenCalledWith("testdata"); 144 | }) 145 | 146 | it('#publish() - Sends formatted event on websocket connection', function () { 147 | channel.publish('test-event', { 148 | "name": "John Doe" 149 | }, { "foo": "bar" }); 150 | 151 | expect(mockWebSocketSend).toHaveBeenCalledWith(JSON.stringify({ 152 | event: "test-event", 153 | data: { 154 | "name": "John Doe" 155 | }, 156 | meta: { "foo": "bar" } 157 | })); 158 | }); 159 | 160 | it('#sendOnBlockchain() - Registers a event on blockchain and then broadcasts the payload to peers', done =>{ 161 | const blockChainHashCallback = jest.fn(); 162 | const blockChainErrorCallback = jest.fn(); 163 | 164 | channel.on('blockchain-hash', blockChainHashCallback); 165 | channel.on('blockchain-error', blockChainErrorCallback); 166 | 167 | channel.sendOnBlockchain('blockchain-event', { 168 | "name": "Harry Doe" 169 | }, { "foo": "bar" }).then(()=>{ 170 | 171 | expect(mockBlockchainSend).toHaveBeenCalledWith({ 172 | "name": "Harry Doe" 173 | }); 174 | expect(blockChainErrorCallback).not.toBeCalled(); 175 | expect(blockChainHashCallback).toBeCalled(); 176 | expect(mockWebSocketSend).toHaveBeenCalledWith(JSON.stringify({ 177 | event: "blockchain-event", 178 | data: { 179 | "name": "Harry Doe" 180 | }, 181 | meta: { "foo": "bar", "transaction_hash":"0x000000000000000000" } 182 | })); 183 | done(); 184 | }); 185 | 186 | 187 | }); 188 | 189 | it('#sendOnBlockchain() - Triggers blockchain-error when error occurs', done =>{ 190 | const blockChainHashCallback = jest.fn(); 191 | const blockChainErrorCallback = jest.fn(); 192 | 193 | channel.on('blockchain-hash', blockChainHashCallback); 194 | channel.on('blockchain-error', blockChainErrorCallback); 195 | 196 | channel.sendOnBlockchain('blockchain-event', { 197 | "name": "Testfail" 198 | }, { "foo": "bar" }).then(()=>{ 199 | expect(blockChainErrorCallback).toBeCalled(); 200 | expect(blockChainHashCallback).not.toBeCalled(); 201 | done(); 202 | }); 203 | }); 204 | 205 | it('#confirmOnBlockchain() - Confirms as blockchain message witness', done =>{ 206 | const blockChainHashCallback = jest.fn(); 207 | const blockChainErrorCallback = jest.fn(); 208 | 209 | channel.on('blockchain-hash', blockChainHashCallback); 210 | channel.on('blockchain-error', blockChainErrorCallback); 211 | 212 | channel.confirmOnBlockchain('blockchain-event2', { 213 | "name": "Tom Doe" 214 | }, { "foo": "bar" }).then(()=>{ 215 | expect(blockChainErrorCallback).not.toBeCalled(); 216 | expect(mockWebSocketSend).toHaveBeenCalledTimes(4); 217 | expect(blockChainHashCallback).toBeCalled(); 218 | done(); 219 | }); 220 | }); 221 | 222 | it('#confirmOnBlockchain() - Triggers blockchain-error when something goes wrong', done =>{ 223 | const blockChainHashCallback = jest.fn(); 224 | const blockChainErrorCallback = jest.fn(); 225 | 226 | channel.on('blockchain-hash', blockChainHashCallback); 227 | channel.on('blockchain-error', blockChainErrorCallback); 228 | 229 | channel.confirmOnBlockchain('blockchain-event2', { 230 | "name": "Testfail" 231 | }, { "foo": "bar" }).then(()=>{ 232 | expect(blockChainErrorCallback).toBeCalled(); 233 | expect(blockChainHashCallback).not.toBeCalled(); 234 | done(); 235 | }); 236 | }); 237 | 238 | it("#onMessage() - Fires event listeners", () => { 239 | const testEventListener = jest.fn(); 240 | const testEventListenerAll = jest.fn(); 241 | const testEventListenerMessage = jest.fn(); 242 | channel.listen("test-onmessage-event", testEventListener); 243 | channel.listen("*", testEventListenerAll); 244 | channel.on("message", testEventListenerMessage); 245 | const evt = { 246 | data: JSON.stringify({ 247 | event: "test-onmessage-event", 248 | data: { 249 | "name": "Harry Doe" 250 | }, 251 | meta: { "foo": "bar" } 252 | }) 253 | }; 254 | channel.onMessage(evt); 255 | 256 | expect(testEventListener).toHaveBeenCalledWith({ 257 | "name": "Harry Doe" 258 | }, { "foo": "bar" }); 259 | expect(testEventListenerAll).toHaveBeenCalledWith("test-onmessage-event",{ 260 | "name": "Harry Doe" 261 | }, { "foo": "bar" }); 262 | expect(testEventListenerMessage).toHaveBeenCalledWith(evt); 263 | 264 | }); 265 | 266 | it("#onOpen() - Fires event listeners", () => { 267 | const testEventListener = jest.fn(); 268 | channel.onSocketConnected = testEventListener; 269 | channel.onOpen({"foo":"bar"}); 270 | 271 | expect(testEventListener).toHaveBeenCalledWith({ "foo": "bar" }); 272 | }); 273 | 274 | it("#onError() - Fires event listeners", () => { 275 | const testEventListener = jest.fn(); 276 | channel.on("error", testEventListener); 277 | channel.onError({"foo":"error"}); 278 | 279 | expect(testEventListener).toHaveBeenCalledWith({ "foo": "error" }); 280 | }); 281 | 282 | it("#onError() - Fires event listeners", () => { 283 | const testEventListener = jest.fn(); 284 | channel.on("close", testEventListener); 285 | channel.onClose({"foo":"close"}); 286 | 287 | expect(testEventListener).toHaveBeenCalledWith({ "foo": "close" }); 288 | }); 289 | 290 | it("#reConnect() - Reconnects websocket connection", () => { 291 | const testEventListener = jest.fn(); 292 | 293 | channel.shouldReconnect = true; 294 | const connectSpy = jest.spyOn(channel, 'connect'); 295 | channel.reconnect(); 296 | expect(connectSpy).toHaveBeenCalled() 297 | }); 298 | 299 | }); -------------------------------------------------------------------------------- /test/InvalidAuthException.test.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import InvalidAuthException from '../src/InvalidAuthException'; 3 | 4 | describe('Invalid Auth Exception', function () { 5 | 6 | it("#constructor() - Returns valid exception instance", ()=>{ 7 | const e = new InvalidAuthException(); 8 | expect(e.message).toEqual('Auth endpoint did not return a valid JWT Token, please see: https://www.piesocket.com/docs/3.0/authentication'); 9 | expect(e.name).toEqual('InvalidAuthException'); 10 | }) 11 | 12 | it("#constructor() - Returns valid exception instance with a custom message", ()=>{ 13 | const message = "Something went wrong!"; 14 | const e = new InvalidAuthException(message); 15 | expect(e.message).toEqual(message); 16 | expect(e.name).toEqual('InvalidAuthException'); 17 | }) 18 | }); -------------------------------------------------------------------------------- /test/PieSocket.test.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import PieSocket from '../src/PieSocket'; 3 | import Logger from '../src/Logger'; 4 | import DefaultOptions from '../src/misc/DefaultOptions'; 5 | import Socket from '../src/misc/WebSocket'; 6 | 7 | const currentVersion = "5.0.8"; 8 | const pieSocketProperties = ['options', 'connections', 'logger']; 9 | const channelProperties = ['events', 'listeners', "members", "portal", "uuid", "onSocketConnected", "onSocketError", 'endpoint', 'identity', 'connection', 'shouldReconnect', 'logger']; 10 | 11 | //Mocks 12 | const mockAddIceCandidate = jest.fn(); 13 | const mockCreateAnswer = jest.fn().mockImplementation(()=> { 14 | return Promise.resolve(); 15 | }); 16 | const mockSetLocalDescription = jest.fn().mockImplementation(()=> { 17 | return Promise.resolve(); 18 | }); 19 | const mockSetRemoteDescription = jest.fn().mockImplementation(()=> { 20 | return Promise.resolve(); 21 | }); 22 | jest.mock('../src/misc/RTCPeerConnection.js', () => { 23 | return jest.fn().mockImplementation(() => { 24 | return { 25 | addIceCandidate: mockAddIceCandidate, 26 | setRemoteDescription: mockSetRemoteDescription, 27 | createAnswer: mockCreateAnswer, 28 | setLocalDescription: mockSetLocalDescription 29 | }; 30 | }); 31 | }); 32 | 33 | jest.mock('../src/misc/RTCSessionDescription.js', () => { 34 | return jest.fn().mockImplementation(() => { 35 | return { 36 | }; 37 | }); 38 | }); 39 | 40 | jest.mock('../src/misc/RTCIceCandidate.js', () => { 41 | return jest.fn().mockImplementation(() => { 42 | return { 43 | }; 44 | }); 45 | }); 46 | 47 | const mockWebSocketClose = jest.fn(); 48 | jest.mock('../src/misc/WebSocket.js', () => { 49 | return jest.fn().mockImplementation(() => { 50 | return { 51 | close: mockWebSocketClose 52 | }; 53 | });; 54 | }); 55 | 56 | var assert = require('assert'); 57 | 58 | //Tests 59 | describe('PieSocket', function () { 60 | 61 | describe('#constructor()', function () { 62 | 63 | it('PieSocket constructor returns ok', function () { 64 | const piesocket = new PieSocket(); 65 | assert.deepEqual(piesocket.options, DefaultOptions); 66 | assert.deepEqual(piesocket.connections, {}); 67 | assert.deepEqual(piesocket.logger, new Logger(piesocket.options)); 68 | 69 | expect(Object.keys(piesocket)).toEqual(pieSocketProperties); 70 | }); 71 | 72 | it('Option overrides work', function () { 73 | const piesocket = new PieSocket({ 74 | clusterId: 'nyc1', 75 | apiKey: 'xxx' 76 | }); 77 | assert.deepEqual(piesocket.options, { ...DefaultOptions, clusterId: 'nyc1', apiKey: 'xxx' }); 78 | }); 79 | 80 | }); 81 | 82 | describe('Channel functions', function () { 83 | 84 | let piesocket; 85 | let channel; 86 | 87 | beforeAll(done => { 88 | piesocket = new PieSocket({ 89 | clusterId: 'nyc1', 90 | apiKey: 'xxx' 91 | }); 92 | 93 | function subscribeCallback(_channel) { 94 | channel = _channel; 95 | delete channel.identity['onSocketConnected']; 96 | delete channel.identity['onSocketError']; 97 | 98 | expect(Socket).toHaveBeenCalledWith(`wss://nyc1.piesocket.com/v3/test-channel?api_key=xxx¬ify_self=0&source=jssdk&v=${currentVersion}&presence=0&uuid=${channel.uuid}`); 99 | assert.deepEqual(channel.identity, { 100 | ...piesocket.options, 101 | channelId: 'test-channel' 102 | }); 103 | assert.deepEqual(channel.events, {}); 104 | assert.deepEqual(channel.listeners, {}); 105 | assert.equal(channel.shouldReconnect, false); 106 | assert.deepEqual(channel.logger, new Logger(channel.identity)); 107 | expect(Object.keys(channel)).toEqual(channelProperties); 108 | done(); 109 | } 110 | 111 | piesocket.subscribe('test-channel').then(subscribeCallback); 112 | }); 113 | 114 | it('#subscribe - Creates channel connection', () => { 115 | expect(Object.keys(channel)).toEqual(channelProperties); 116 | }); 117 | 118 | it('#subscribe - Creates another channel subscription', done => { 119 | piesocket.subscribe('test-channel-2').then(()=>{ 120 | expect(Object.keys(piesocket.connections).length).toEqual(2); 121 | done(); 122 | }); 123 | }); 124 | 125 | it('#subscribe - Prevents duplicate channel connection', done => { 126 | piesocket.subscribe('test-channel').then(()=>{ 127 | expect(Object.keys(piesocket.connections).length).toEqual(2); 128 | done(); 129 | }); 130 | }); 131 | 132 | it('#unsubscribe - Removes channel subscription', () => { 133 | const result = piesocket.unsubscribe("test-channel"); 134 | expect(result).toEqual(true); 135 | expect(Object.keys(piesocket.connections).length).toEqual(1); 136 | }); 137 | 138 | it('#unsubscribe - Removes non-existing channel subscription', () => { 139 | const result = piesocket.unsubscribe("test-channel-99"); 140 | expect(result).toEqual(false); 141 | expect(Object.keys(piesocket.connections).length).toEqual(1); 142 | }); 143 | 144 | it('#getConnections - Returns all connections', () => { 145 | const result = piesocket.getConnections(); 146 | assert.deepEqual(result, piesocket.connections); 147 | }); 148 | 149 | it('#isGuarded - Returns true for private- prefixed channels', () => { 150 | const result = piesocket.isGuarded('private-channel'); 151 | expect(result).toEqual(true); 152 | }); 153 | 154 | it('#isGuarded - Returns false for channels not prefixed with private-', () => { 155 | const result = piesocket.isGuarded('dummy-channel'); 156 | expect(result).toEqual(false); 157 | }); 158 | 159 | it('#isGuarded - Returns true for any channel when forceAuth is set', () => { 160 | piesocket.options.forceAuth = true; 161 | const result = piesocket.isGuarded('dummy-channel'); 162 | expect(result).toEqual(true); 163 | }); 164 | 165 | it('#getEndpoint - Returns websocket endpoint', done => { 166 | piesocket.options.forceAuth = false; 167 | piesocket.getEndpoint('test-channel', 'xxxx').then((result) => { 168 | expect(result).toEqual(`wss://nyc1.piesocket.com/v3/test-channel?api_key=xxx¬ify_self=0&source=jssdk&v=${currentVersion}&presence=0&uuid=xxxx`); 169 | done(); 170 | }); 171 | }); 172 | 173 | it('#getEndpoint - Returns websocket endpoint with JWT', done => { 174 | piesocket.options.jwt = 'testcode'; 175 | piesocket.getEndpoint('test-channel', 'xxxx').then((result) => { 176 | expect(result).toEqual(`wss://nyc1.piesocket.com/v3/test-channel?api_key=xxx¬ify_self=0&source=jssdk&v=${currentVersion}&presence=0&jwt=testcode&uuid=xxxx`); 177 | done(); 178 | }); 179 | }); 180 | 181 | it('#getEndpoint - Returns websocket endpoint with auto-generated JWT when forceAuth is set', done => { 182 | piesocket.options.jwt = null; 183 | piesocket.options.forceAuth = true; 184 | piesocket.getAuthToken = jest.fn().mockImplementation(()=>{ 185 | return Promise.resolve({ 186 | auth: 'test-token' 187 | }); 188 | }); 189 | 190 | piesocket.getEndpoint('test-channel', 'xxxx').then((result) => { 191 | expect(result).toEqual(`wss://nyc1.piesocket.com/v3/test-channel?api_key=xxx¬ify_self=0&source=jssdk&v=${currentVersion}&presence=0&jwt=test-token&uuid=xxxx`); 192 | done(); 193 | }); 194 | }); 195 | 196 | it('#getEndpoint - Returns websocket endpoint without auto-generated JWT when auth endpoint call returns invalid data', done => { 197 | piesocket.options.jwt = null; 198 | piesocket.options.forceAuth = true; 199 | piesocket.getAuthToken = jest.fn().mockImplementation(()=>{ 200 | return Promise.resolve("blabla"); 201 | }); 202 | 203 | piesocket.getEndpoint('test-channel','xxxx').then((result) => { 204 | expect(result).toEqual(`wss://nyc1.piesocket.com/v3/test-channel?api_key=xxx¬ify_self=0&source=jssdk&v=${currentVersion}&presence=0&uuid=xxxx`); 205 | done(); 206 | }); 207 | }); 208 | 209 | it('#getEndpoint - Returns websocket endpoint with auto-generated JWT for private channels', done => { 210 | piesocket.options.jwt = null; 211 | piesocket.options.forceAuth = false; 212 | piesocket.getAuthToken = jest.fn().mockImplementation(()=>{ 213 | return Promise.resolve({ 214 | auth: 'test-token' 215 | }); 216 | }); 217 | 218 | piesocket.getEndpoint('private-channel','xxxx').then((result) => { 219 | expect(result).toEqual(`wss://nyc1.piesocket.com/v3/private-channel?api_key=xxx¬ify_self=0&source=jssdk&v=${currentVersion}&presence=0&jwt=test-token&uuid=xxxx`); 220 | done(); 221 | }); 222 | }); 223 | 224 | 225 | it('#getEndpoint - Returns websocket endpoint with user identity', done => { 226 | piesocket.options.forceAuth = false; 227 | piesocket.options.userId = 1; 228 | piesocket.getEndpoint('test-channel', 'xxxx').then((result) => { 229 | expect(result).toEqual(`wss://nyc1.piesocket.com/v3/test-channel?api_key=xxx¬ify_self=0&source=jssdk&v=${currentVersion}&presence=0&user=1&uuid=xxxx`); 230 | done(); 231 | }); 232 | }); 233 | 234 | }); 235 | }); -------------------------------------------------------------------------------- /test/Portal.test.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import PieSocket from '../src/PieSocket'; 3 | import Portal from '../src/Portal'; 4 | import Channel from '../src/Channel'; 5 | 6 | //Mocks 7 | const mockAddIceCandidate = jest.fn(); 8 | const mockCreateAnswer = jest.fn().mockImplementation(() => { 9 | return Promise.resolve(); 10 | }); 11 | const mockSetLocalDescription = jest.fn().mockImplementation(() => { 12 | return Promise.resolve(); 13 | }); 14 | const mockSetRemoteDescription = jest.fn().mockImplementation(() => { 15 | return Promise.resolve(); 16 | }); 17 | jest.mock('../src/misc/RTCPeerConnection.js', () => { 18 | return jest.fn().mockImplementation(() => { 19 | return { 20 | addIceCandidate: mockAddIceCandidate, 21 | setRemoteDescription: mockSetRemoteDescription, 22 | createAnswer: mockCreateAnswer, 23 | setLocalDescription: mockSetLocalDescription 24 | }; 25 | }); 26 | }); 27 | 28 | jest.mock('../src/misc/RTCSessionDescription.js', () => { 29 | return jest.fn().mockImplementation(() => { 30 | return { 31 | }; 32 | }); 33 | }); 34 | 35 | jest.mock('../src/misc/RTCIceCandidate.js', () => { 36 | return jest.fn().mockImplementation(() => { 37 | return { 38 | }; 39 | }); 40 | }); 41 | 42 | const mockWebSocketClose = jest.fn(); 43 | const mockWebSocketSend = jest.fn(); 44 | jest.mock('../src/misc/WebSocket.js', () => { 45 | return jest.fn().mockImplementation(() => { 46 | return { 47 | close: mockWebSocketClose, 48 | send: mockWebSocketSend 49 | }; 50 | }); 51 | }); 52 | 53 | const mockOnLocalVideo = jest.fn(); 54 | const mockOnParticipantLeft = jest.fn(); 55 | 56 | describe('Portal', function () { 57 | 58 | let piesocket; 59 | let channel; 60 | let portal; 61 | let uuidLocal; 62 | let uuidRemote; 63 | 64 | beforeAll(() => { 65 | const roomOptions = { 66 | video: true 67 | }; 68 | 69 | uuidLocal = "local-tester-uuid"; 70 | uuidRemote = "remote-tester-uuid"; 71 | 72 | piesocket = new PieSocket({ 73 | consoleLogs: false 74 | }); 75 | channel = new Channel(piesocket.getEndpoint("test"), piesocket.options); 76 | channel.uuid = uuidLocal; 77 | portal = new Portal(channel, { 78 | ...piesocket.options, 79 | ...roomOptions, 80 | onLocalVideo: mockOnLocalVideo, 81 | onParticipantLeft: mockOnParticipantLeft 82 | }); 83 | }); 84 | 85 | it('#getUserMediaSuccess - Starts video call as caller, on system:video_request', () => { 86 | const videoStream = { 87 | getTracks: jest.fn().mockImplementation(() => { 88 | return []; 89 | }) 90 | }; 91 | portal.getUserMediaSuccess(videoStream); 92 | expect(mockWebSocketSend).toHaveBeenCalledWith(JSON.stringify({ 93 | "event": "system:portal_broadcaster", 94 | "data": { 95 | "from": uuidLocal, 96 | "isBroadcasting": true 97 | }, 98 | })); 99 | expect(mockOnLocalVideo).toHaveBeenCalledWith(videoStream, portal); 100 | }); 101 | 102 | it('#shareVideo - Initialize video call as caller, on system:video_request or system:video_offer', () => { 103 | const shareVideo = portal.shareVideo({ 104 | from: uuidRemote 105 | }, true); 106 | const remoteConnection = portal.participants[uuidRemote]; 107 | expect(typeof remoteConnection.rtc).toEqual("object"); 108 | expect(typeof remoteConnection.rtc.onicecandidate).toEqual("function"); 109 | expect(typeof remoteConnection.rtc.ontrack).toEqual("function"); 110 | expect(typeof remoteConnection.rtc.onsignalingstatechange).toEqual("function"); 111 | expect(typeof remoteConnection.rtc.onnegotiationneeded).toEqual("function"); 112 | }); 113 | 114 | it('#addIceCandidate - Adds Ice Candidate', () => { 115 | portal.addIceCandidate({ 116 | from: uuidRemote 117 | }); 118 | expect(mockAddIceCandidate).toHaveBeenCalled(); 119 | }); 120 | 121 | it('#createAnswer - Answers an incoming video offer', done => { 122 | const remoteConnection = portal.participants[uuidRemote]; 123 | portal.createAnswer({ 124 | from: uuidRemote, 125 | sdp: { 126 | type: "offer" 127 | } 128 | }).then(() => { 129 | expect(mockSetRemoteDescription).toHaveBeenCalled(); 130 | expect(mockCreateAnswer).toHaveBeenCalled(); 131 | expect(mockSetLocalDescription).toHaveBeenCalled(); 132 | 133 | expect(mockWebSocketSend).toHaveBeenCalledWith(JSON.stringify({ 134 | "event": "system:video_answer", 135 | "data": { 136 | "from": uuidLocal, 137 | "to": uuidRemote 138 | } 139 | })); 140 | 141 | done(); 142 | }); 143 | }); 144 | 145 | it('#removeParticipant - Removes portal participant', () => { 146 | portal.removeParticipant(uuidRemote); 147 | expect(portal.participants).not.toHaveProperty(uuidRemote); 148 | expect(portal.participants).toHaveLength(0); 149 | expect(mockOnParticipantLeft).toHaveBeenCalledWith(uuidRemote); 150 | }); 151 | 152 | }); -------------------------------------------------------------------------------- /test/e2e/Chatroom.html.test.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import puppeteer from "puppeteer"; 3 | jest.setTimeout(10000); 4 | 5 | describe("Chatroom.html", () => { 6 | let browser; 7 | let page; 8 | 9 | beforeAll(async () => { 10 | browser = await puppeteer.launch({ 11 | defaultViewport: null, 12 | headless: true 13 | }); 14 | page = await browser.newPage(); 15 | }); 16 | 17 | it("Loads the chatroom", async () => { 18 | await page.goto("http://localhost:8080/examples/chatroom.html#anand", { waitUntil: 'domcontentloaded' }); 19 | await page.waitForXPath("//*[@id='chat-room' and contains(., 'anand')]"); 20 | const chatroom = await page.$("#chat-room"); 21 | const messages = await chatroom.$$("#chat-sender"); 22 | 23 | expect(messages.length).toEqual(1); 24 | }); 25 | 26 | it("Send and receives messages", async () => { 27 | const page2 = await browser.newPage(); 28 | await page.goto("http://localhost:8080/examples/chatroom.html#anand", { waitUntil: 'domcontentloaded' }); 29 | await page.waitForXPath("//*[@id='chat-room' and contains(., 'anand')]"); 30 | 31 | await page2.goto("http://localhost:8080/examples/chatroom.html#john", { waitUntil: 'domcontentloaded' }); 32 | await page2.waitForXPath("//*[@id='chat-room' and contains(., 'john')]"); 33 | 34 | await page.bringToFront(); 35 | const inputArea = await page.$("#chat-message-input"); 36 | const submitButton = await page.$("[type='submit']"); 37 | await inputArea.type("Hello world!"); 38 | await submitButton.click(); 39 | await page.waitForTimeout(1000); 40 | 41 | const chatroom = await page.$("#chat-room"); 42 | const messages = await chatroom.$$("#chat-sender"); 43 | 44 | 45 | await page2.bringToFront(); 46 | const chatroomOnPageTwo = await page2.$("#chat-room"); 47 | const messagesOnPageTwo = await chatroomOnPageTwo.$$("#chat-sender"); 48 | 49 | expect(messages.length).toEqual(3); 50 | expect(messagesOnPageTwo.length).toEqual(2); 51 | await page2.close(); 52 | }) 53 | 54 | it("Updates members list", async () => { 55 | const page2 = await browser.newPage(); 56 | 57 | //Open page 1, should have 1 member 58 | await page.goto("http://localhost:8080/examples/chatroom.html#anand", { waitUntil: 'domcontentloaded' }); 59 | await page.waitForXPath("//*[@id='chat-room' and contains(., 'anand')]"); 60 | 61 | await page.bringToFront(); 62 | let memberList = await page.$("#members-list"); 63 | let members = await memberList.$$("#member-name"); 64 | 65 | expect(members.length).toEqual(1); 66 | 67 | //Open another page, it should show 2 members 68 | await page2.goto("http://localhost:8080/examples/chatroom.html#john", { waitUntil: 'domcontentloaded' }); 69 | await page2.waitForXPath("//*[@id='chat-room' and contains(., 'john')]"); 70 | 71 | await page2.bringToFront(); 72 | const memberListOnPageTwo = await page.$("#members-list"); 73 | const membersOnPageTwo = await memberListOnPageTwo.$$("#member-name"); 74 | 75 | expect(membersOnPageTwo.length).toEqual(2); 76 | 77 | //Page 1 should have been updated to show 2 members 78 | await page.bringToFront(); 79 | memberList = await page.$("#members-list"); 80 | members = await memberList.$$("#member-name"); 81 | 82 | expect(members.length).toEqual(2); 83 | 84 | //Close page 2 and, page 1 should show single member once again 85 | await page2.close(); 86 | await page.waitForTimeout(1000); 87 | memberList = await page.$("#members-list"); 88 | members = await memberList.$$("#member-name"); 89 | 90 | expect(members.length).toEqual(1); 91 | }) 92 | 93 | afterAll(() => browser.close()); 94 | }); -------------------------------------------------------------------------------- /test/e2e/Index.html.test.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import puppeteer from "puppeteer"; 3 | 4 | describe("Index.html", () => { 5 | let browser; 6 | let page; 7 | 8 | beforeAll(async () => { 9 | browser = await puppeteer.launch(); 10 | page = await browser.newPage(); 11 | }); 12 | 13 | it("Contains the welcome text", async () => { 14 | await page.goto("http://localhost:8080"); 15 | await page.waitForSelector("h1"); 16 | const text = await page.$eval("h1", (e) => e.textContent); 17 | expect(text).toContain("Welcome to PieSocket JS"); 18 | }); 19 | 20 | afterAll(() => browser.close()); 21 | }); -------------------------------------------------------------------------------- /test/e2e/Videoroom.html.test.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import puppeteer from "puppeteer"; 3 | jest.setTimeout(10000); 4 | 5 | describe("Chatroom.html", () => { 6 | let browser; 7 | let page; 8 | 9 | beforeAll(async () => { 10 | browser = await puppeteer.launch({ 11 | args: [ '--use-fake-ui-for-media-stream', '--use-fake-device-for-media-stream' ], 12 | defaultViewport: null, 13 | headless: true 14 | }); 15 | page = await browser.newPage(); 16 | }); 17 | 18 | it("Loads the room and self video", async () => { 19 | await page.goto("http://localhost:8080/examples/videoroom.html#anand", { waitUntil: 'domcontentloaded' }); 20 | await page.waitForXPath("//*[@id='members-list' and contains(., 'anand')]"); 21 | 22 | const membersList = await page.$("#members-list"); 23 | const members = await membersList.$$("#member-name"); 24 | expect(members.length).toBeGreaterThanOrEqual(1); 25 | 26 | const videos = await page.$$("video"); 27 | expect(videos.length).toEqual(1); 28 | }); 29 | 30 | 31 | it("Adds and removes video participants", async () => { 32 | await page.goto("http://localhost:8080/examples/videoroom.html#anand", { waitUntil: 'domcontentloaded' }); 33 | await page.waitForXPath("//*[@id='members-list' and contains(., 'anand')]"); 34 | 35 | const membersList = await page.$("#members-list"); 36 | const members = await membersList.$$("#member-name"); 37 | expect(members.length).toBeGreaterThanOrEqual(1); 38 | 39 | let videos = await page.$$("video"); 40 | expect(videos.length).toEqual(1); 41 | 42 | //Open another page 43 | const page2 = await browser.newPage(); 44 | await page2.goto("http://localhost:8080/examples/videoroom.html#john", { waitUntil: 'domcontentloaded' }); 45 | await page2.waitForXPath("//*[@id='members-list' and contains(., 'john')]"); 46 | await page2.waitForTimeout(2000); 47 | 48 | const videosOnPageTwo = await page2.$$("video"); 49 | expect(videosOnPageTwo.length).toEqual(2); 50 | 51 | 52 | //Check back page 1 53 | page.bringToFront(); 54 | videos = await page.$$("video"); 55 | expect(videos.length).toEqual(2); 56 | 57 | //Check after removing page 2 58 | await page2.close(); 59 | await page.waitForTimeout(1000); 60 | videos = await page.$$("video"); 61 | expect(videos.length).toEqual(1); 62 | }); 63 | 64 | it("Updates members list", async () => { 65 | await page.goto("http://localhost:8080/examples/videoroom.html#anand", { waitUntil: 'domcontentloaded' }); 66 | await page.waitForXPath("//*[@id='members-list' and contains(., 'anand')]"); 67 | 68 | let membersList = await page.$("#members-list"); 69 | let members = await membersList.$$("#member-name"); 70 | expect(members.length).toBeGreaterThanOrEqual(1); 71 | 72 | let videos = await page.$$("video"); 73 | expect(videos.length).toEqual(1); 74 | 75 | //Open another page 76 | const page2 = await browser.newPage(); 77 | await page2.goto("http://localhost:8080/examples/videoroom.html#john", { waitUntil: 'domcontentloaded' }); 78 | await page2.waitForXPath("//*[@id='members-list' and contains(., 'john')]"); 79 | await page2.waitForTimeout(2000); 80 | 81 | let membersListOnPageTwo = await page.$("#members-list"); 82 | let membersOnPageTwo = await membersListOnPageTwo.$$("#member-name"); 83 | expect(membersOnPageTwo.length).toEqual(2); 84 | 85 | 86 | //Check back page 1 87 | page.bringToFront(); 88 | membersList = await page.$("#members-list"); 89 | members = await membersList.$$("#member-name"); 90 | expect(members.length).toBeGreaterThanOrEqual(2); 91 | 92 | //Check after removing page 2 93 | await page2.close(); 94 | await page.waitForTimeout(700); 95 | membersList = await page.$("#members-list"); 96 | members = await membersList.$$("#member-name"); 97 | expect(members.length).toBeGreaterThanOrEqual(1); 98 | 99 | }); 100 | 101 | afterAll(() => browser.close()); 102 | }); -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { dirname } from "path"; 3 | import { fileURLToPath } from "url"; 4 | const __dirname = dirname(fileURLToPath(import.meta.url)); 5 | 6 | var common = { 7 | entry: './src/index.js', 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | filename: 'piesocket.js', 11 | library: 'PieSocket' 12 | } 13 | }; 14 | 15 | export default common; -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | import {merge} from "webpack-merge"; 2 | import common from "./webpack.common.js"; 3 | import path from "path"; 4 | import { dirname } from "path"; 5 | import { fileURLToPath } from "url"; 6 | const __dirname = dirname(fileURLToPath(import.meta.url)); 7 | 8 | var config = merge(common, { 9 | mode: "development", 10 | devtool: "inline-source-map", 11 | devServer: { 12 | static: path.resolve(__dirname, "./"), 13 | }, 14 | }); 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | import {merge} from 'webpack-merge'; 2 | import common from "./webpack.common.js"; 3 | 4 | var config = merge(common, { 5 | mode: 'production' 6 | }); 7 | 8 | export default config; --------------------------------------------------------------------------------