├── .codeclimate.yml
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .scrutinizer.yml
├── LICENSE
├── README.md
├── build
└── minify-resources.php
├── composer.json
├── composer.lock
├── config-sample.php
├── dependency-checker.json
├── gitamp.sample.service
├── phpcs.xml.dist
├── phpunit.xml.dist
├── public
├── css
│ ├── main.css
│ ├── main.min.css
│ └── themes
│ │ └── default.css
├── images
│ ├── GitAmp-logo-black.png
│ ├── GitAmp-logo-white.png
│ ├── GitAmp-neon-logo.png
│ ├── electric-guitar.png
│ ├── favicon.png
│ ├── neon-g-favicon_256.ico
│ ├── speaker-muted.svg
│ └── speaker.svg
├── index.html
├── js
│ ├── main.js
│ └── main.min.js
├── robots.txt
└── sounds
│ ├── celesta
│ ├── c001.mp3
│ ├── c001.ogg
│ ├── c002.mp3
│ ├── c002.ogg
│ ├── c003.mp3
│ ├── c003.ogg
│ ├── c004.mp3
│ ├── c004.ogg
│ ├── c005.mp3
│ ├── c005.ogg
│ ├── c006.mp3
│ ├── c006.ogg
│ ├── c007.mp3
│ ├── c007.ogg
│ ├── c008.mp3
│ ├── c008.ogg
│ ├── c009.mp3
│ ├── c009.ogg
│ ├── c010.mp3
│ ├── c010.ogg
│ ├── c011.mp3
│ ├── c011.ogg
│ ├── c012.mp3
│ ├── c012.ogg
│ ├── c013.mp3
│ ├── c013.ogg
│ ├── c014.mp3
│ ├── c014.ogg
│ ├── c015.mp3
│ ├── c015.ogg
│ ├── c016.mp3
│ ├── c016.ogg
│ ├── c017.mp3
│ ├── c017.ogg
│ ├── c018.mp3
│ ├── c018.ogg
│ ├── c019.mp3
│ ├── c019.ogg
│ ├── c020.mp3
│ ├── c020.ogg
│ ├── c021.mp3
│ ├── c021.ogg
│ ├── c022.mp3
│ ├── c022.ogg
│ ├── c023.mp3
│ ├── c023.ogg
│ ├── c024.mp3
│ ├── c024.ogg
│ ├── c025.mp3
│ ├── c025.ogg
│ ├── c026.mp3
│ ├── c026.ogg
│ ├── c027.mp3
│ └── c027.ogg
│ ├── clav
│ ├── c001.mp3
│ ├── c001.ogg
│ ├── c002.mp3
│ ├── c002.ogg
│ ├── c003.mp3
│ ├── c003.ogg
│ ├── c004.mp3
│ ├── c004.ogg
│ ├── c005.mp3
│ ├── c005.ogg
│ ├── c006.mp3
│ ├── c006.ogg
│ ├── c007.mp3
│ ├── c007.ogg
│ ├── c008.mp3
│ ├── c008.ogg
│ ├── c009.mp3
│ ├── c009.ogg
│ ├── c010.mp3
│ ├── c010.ogg
│ ├── c011.mp3
│ ├── c011.ogg
│ ├── c012.mp3
│ ├── c012.ogg
│ ├── c013.mp3
│ ├── c013.ogg
│ ├── c014.mp3
│ ├── c014.ogg
│ ├── c015.mp3
│ ├── c015.ogg
│ ├── c016.mp3
│ ├── c016.ogg
│ ├── c017.mp3
│ ├── c017.ogg
│ ├── c018.mp3
│ ├── c018.ogg
│ ├── c019.mp3
│ ├── c019.ogg
│ ├── c020.mp3
│ ├── c020.ogg
│ ├── c021.mp3
│ ├── c021.ogg
│ ├── c022.mp3
│ ├── c022.ogg
│ ├── c023.mp3
│ ├── c023.ogg
│ ├── c024.mp3
│ ├── c024.ogg
│ ├── c025.mp3
│ ├── c025.ogg
│ ├── c026.mp3
│ ├── c026.ogg
│ ├── c027.mp3
│ └── c027.ogg
│ ├── egg
│ ├── celesta.mp3
│ ├── celesta.ogg
│ ├── clav.mp3
│ ├── clav.ogg
│ ├── swell.mp3
│ └── swell.ogg
│ └── swells
│ ├── swell1.mp3
│ ├── swell1.ogg
│ ├── swell2.mp3
│ ├── swell2.ogg
│ ├── swell3.mp3
│ └── swell3.ogg
├── server.php
├── src
├── Configuration.php
├── Event
│ ├── BaseEvent.php
│ ├── Event.php
│ ├── Factory.php
│ └── GitHub
│ │ ├── CreateEvent.php
│ │ ├── ForkEvent.php
│ │ ├── IssueCommentEvent.php
│ │ ├── IssuesEvent.php
│ │ ├── PullRequestEvent.php
│ │ ├── PushEvent.php
│ │ └── WatchEvent.php
├── Exception
│ ├── DecodingFailed.php
│ ├── Exception.php
│ ├── RequestFailed.php
│ └── UnknownEvent.php
├── Github
│ ├── Credentials.php
│ └── Token.php
├── Log
│ └── LoggerFactory.php
├── Presentation
│ ├── Information.php
│ ├── Ring.php
│ ├── Sound
│ │ ├── BaseSound.php
│ │ ├── Celesta.php
│ │ ├── CelestaEgg.php
│ │ ├── Clav.php
│ │ ├── ClavEgg.php
│ │ ├── Swell.php
│ │ └── SwellEgg.php
│ └── Type.php
├── Provider
│ ├── GitHub.php
│ └── Listener.php
├── Response
│ ├── Factory.php
│ └── Results.php
├── Server.php
├── ServerAddress.php
├── SslServerAddress.php
└── Websocket
│ └── Handler.php
└── tests
├── ConfigurationTest.php
├── Data
├── invalid.json
└── valid.json
├── Event
├── BaseEventTest.php
├── FactoryTest.php
└── GitHub
│ ├── CreateEventTest.php
│ ├── ForkEventTest.php
│ ├── IssueCommentEventTest.php
│ ├── IssuesEventTest.php
│ ├── PullRequestEventTest.php
│ ├── PushEventTest.php
│ └── WatchEventTest.php
├── Exception
└── UnknownEventTest.php
├── Fakes
└── HttpClient
│ ├── MockFailedResponseInterceptor.php
│ ├── MockSuccessfulResponseInterceptor.php
│ └── MockThrowingResponseInterceptor.php
├── Github
└── TokenTest.php
├── Log
└── LoggerFactoryTest.php
├── Provider
└── GitHubTest.php
├── Response
├── FactoryTest.php
└── ResultsTest.php
├── ServerAddressTest.php
├── ServerTest.php
├── SslServerAddressTest.php
├── Websocket
└── HandlerTest.php
└── bootstrap.php
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | duplication:
4 | enabled: true
5 | config:
6 | languages:
7 | - php
8 | fixme:
9 | enabled: true
10 | phpmd:
11 | enabled: true
12 | ratings:
13 | paths:
14 | - "**.php"
15 | exclude_paths:
16 | - tests/
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 |
9 | [*.{diff,md}]
10 | trim_trailing_whitespace = false
11 |
12 | [*.php]
13 | indent_style = space
14 | indent_size = 4
15 |
16 | [*.{js,json,html,twig}]
17 | indent_style = space
18 | indent_size = 4
19 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.github export-ignore
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | /vendor/
3 | config.php
4 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | filter:
2 | excluded_paths:
3 | - 'tests/*'
4 | checks:
5 | php:
6 | use_self_instead_of_fqcn: true
7 | uppercase_constants: true
8 | simplify_boolean_return: true
9 | remove_extra_empty_lines: true
10 | properties_in_camelcaps: true
11 | phpunit_assertions: true
12 | prefer_while_loop_over_for_loop: true
13 | parameters_in_camelcaps: true
14 | param_doc_comment_if_not_inferrable: true
15 | overriding_parameter: true
16 | optional_parameters_at_the_end: true
17 | newline_at_end_of_file: true
18 | line_length:
19 | max_length: '120'
20 | encourage_single_quotes: true
21 | encourage_postdec_operator: true
22 | classes_in_camel_caps: true
23 | check_method_contracts:
24 | verify_interface_like_constraints: true
25 | verify_documented_constraints: true
26 | verify_parent_constraints: true
27 | avoid_unnecessary_concatenation: true
28 | avoid_todo_comments: true
29 | avoid_perl_style_comments: true
30 | avoid_multiple_statements_on_same_line: true
31 | avoid_fixme_comments: true
32 | align_assignments: true
33 | coding_style:
34 | php:
35 | spaces:
36 | around_operators:
37 | concatenation: true
38 | tools:
39 | external_code_coverage: true
40 | build:
41 | environment:
42 | php:
43 | version: "7.4"
44 | nodes:
45 | analysis:
46 | tests:
47 | override:
48 | - php-scrutinizer-run
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Ekin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ---
4 | [](https://github.com/ekinhbayar/gitamp/actions?query=workflow%3ACI)
5 | [](https://scrutinizer-ci.com/g/ekinhbayar/gitamp/?branch=master)
6 | [](https://scrutinizer-ci.com/g/ekinhbayar/gitamp/?branch=master)
7 | [](https://codeclimate.com/github/ekinhbayar/gitamp)
8 |
9 | Listen to music generated by events across github.
10 |
11 | Made with [amphp](http://amphp.org/) magic `<3`
12 |
13 | Clone of [github.audio](https://github.audio).
14 |
15 | Requires:
16 |
17 | - PHP 7.4+
18 |
19 | ## Usage
20 |
21 | - Run `composer update`
22 | - Copy the config.sample.php file to config.php and change the settings
23 | - Run the server using `php server.php`
24 | - Open your browser and go to http://localhost:1337 (for default settings)
25 | - Profit!
26 |
27 | ## GitAmp as a Service
28 |
29 | To run GitAmp as a systemd unit:
30 |
31 | - Copy the [gitamp.sample.service](https://github.com/ekinhbayar/gitamp/blob/master/gitamp.sample.service) to `/etc/systemd/system/gitamp.service`.
32 | - Replace the paths with your installation location.
33 | - Enable it by running `systemctl enable gitamp` && start with `systemctl start gitamp`
34 |
35 | If you want to run it after reboots as well, symlink the service file under `multi-user.target.wants` via
36 |
37 | `ln -sf /etc/systemd/system/gitamp.service /etc/systemd/system/multi-user.target.wants/gitamp.service`
38 |
39 | ## Optional Dependencies
40 |
41 | For true non-blocking execution, install one of the following:
42 |
43 | - [`libevent`](https://pecl.php.net/package/libevent) PECL extension.
44 | - [`ev`](https://pecl.php.net/package/ev) PECL extension
45 | - [`php-uv`](https://github.com/bwoebi/php-uv) PHP extension.
46 |
47 | ## Issues
48 |
49 | All features requests, bug reports or questions can be posted in [GitHub issues](https://github.com/ekinhbayar/gitamp/issues).
50 |
51 | For security related reports please send a mail to gitamp-security@ekins.space instead of using GitHub's issues.
52 |
--------------------------------------------------------------------------------
/build/minify-resources.php:
--------------------------------------------------------------------------------
1 | add(__DIR__ . '/../public/css/themes\default.css');
12 |
13 | $cssMinifier->minify(__DIR__ . '/../public/css/main.min.css');
14 |
15 | (new JS(__DIR__ . '/../public/js/main.js'))->minify(__DIR__ . '/../public/js/main.min.js');
16 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ekinhbayar/gitamp",
3 | "description": "Listen ambient music generated by events across github.",
4 | "keywords": [
5 | "audio",
6 | "events",
7 | "github",
8 | "async",
9 | "non-blocking"
10 | ],
11 | "license": "MIT",
12 | "authors": [
13 | {
14 | "name": "Ekin H. Bayar",
15 | "email": "me@ekins.space",
16 | "role": "Developer"
17 | },
18 | {
19 | "name": "Pieter Hordijk",
20 | "email": "info@pieterhordijk.com",
21 | "homepage": "https://pieterhordijk.com",
22 | "role": "Developer"
23 | }
24 | ],
25 | "minimum-stability": "dev",
26 | "prefer-stable": true,
27 | "require": {
28 | "php": ">=7.4",
29 | "ext-json": "*",
30 | "amphp/amp": "^2",
31 | "amphp/byte-stream": "^1.8",
32 | "amphp/http": "^1.6",
33 | "amphp/http-client": "^4.5",
34 | "amphp/http-server": "^2.1",
35 | "amphp/http-server-router": "^1.0",
36 | "amphp/http-server-static-content": "^1.0.6",
37 | "amphp/log": "^1.1",
38 | "amphp/socket": "^1.1",
39 | "amphp/websocket": "^1.0",
40 | "amphp/websocket-server": "^2.0",
41 | "daverandom/exceptional-json": "^1.0.4",
42 | "league/uri": "^6.3",
43 | "league/uri-interfaces": "^2.1",
44 | "monolog/monolog": "^2",
45 | "nikic/fast-route": "^1",
46 | "psr/log": "^1"
47 | },
48 | "require-dev": {
49 | "matthiasmullie/minify": "^1.3",
50 | "object-calisthenics/phpcs-calisthenics-rules": "^3.7",
51 | "phpunit/phpunit": "^9.3",
52 | "slevomat/coding-standard": "^6.0@dev",
53 | "squizlabs/php_codesniffer": "^3.5"
54 | },
55 | "autoload": {
56 | "psr-4": {
57 | "ekinhbayar\\GitAmp\\": "src/"
58 | }
59 | },
60 | "autoload-dev": {
61 | "psr-4": {
62 | "ekinhbayar\\GitAmpTests\\": "tests/"
63 | }
64 | },
65 | "config": {
66 | "sort-packages": true
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/config-sample.php:
--------------------------------------------------------------------------------
1 | addWebsocketAddress(Uri::createFromString('https://gitamp.audio'))
16 | ->bind(new ServerAddress('127.0.0.1', 1337))
17 | ->addSpecialRepository('ekinhbayar/gitamp')
18 | ->addSpecialRepository('amphp/amp')
19 | ;
20 |
--------------------------------------------------------------------------------
/dependency-checker.json:
--------------------------------------------------------------------------------
1 | {
2 | "symbol-whitelist" : [
3 | "null", "true", "false",
4 | "static", "self", "parent",
5 | "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object"
6 | ],
7 | "php-core-extensions" : [
8 | "Core",
9 | "date",
10 | "pcre",
11 | "Phar",
12 | "Reflection",
13 | "SPL",
14 | "standard"
15 | ],
16 | "scan-files" : ["bin/*.php"]
17 | }
18 |
--------------------------------------------------------------------------------
/gitamp.sample.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=GitAmp as a service (GAAS)
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | ExecStart=/path/to/php /path/to/gitamp/vendor/bin/aerys -c /path/to/gitamp/server.php -d
8 | Restart=always
9 |
10 | [Install]
11 | WantedBy=multi-user.target
12 |
13 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ./src
11 | ./tests
12 | ./config-sample.php
13 | ./server.php
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | error
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
19 |
20 |
21 | ./src
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/public/css/main.css:
--------------------------------------------------------------------------------
1 | @import 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:400|Josefin+Sans';
2 |
3 | html, body, div, span, applet, object, iframe,
4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
5 | a, abbr, acronym, address, big, cite, code,
6 | del, dfn, em, img, ins, kbd, q, s, samp,
7 | small, strike, strong, sub, sup, tt, var,
8 | b, u, i, center,
9 | dl, dt, dd, ol, ul, li,
10 | fieldset, form, label, legend,
11 | table, caption, tbody, tfoot, thead, tr, th, td,
12 | article, aside, canvas, details, embed,
13 | figure, figcaption, footer, header,
14 | menu, nav, output, ruby, section, summary,
15 | time, mark, audio, video {
16 | margin: 0;
17 | padding: 0;
18 | border: 0;
19 | font: inherit;
20 | font-size: 100%;
21 | vertical-align: baseline;
22 | }
23 |
24 | /* HTML5 display-role reset for older browsers */
25 | article, aside, details, figcaption, figure,
26 | footer, header, menu, nav, section {
27 | display: block;
28 | }
29 |
30 | body {
31 | line-height: 1;
32 | }
33 |
34 | ol, ul {
35 | list-style: none;
36 | }
37 |
38 | blockquote, q {
39 | quotes: none;
40 | }
41 |
42 | blockquote:before, blockquote:after,
43 | q:before, q:after {
44 | content: '';
45 | }
46 |
47 | table {
48 | border-collapse: collapse;
49 | border-spacing: 0;
50 | }
51 |
52 | a {
53 | text-decoration: none;
54 | }
55 |
56 | * {
57 | box-sizing: border-box;
58 | margin: 0;
59 | padding: 0;
60 | }
61 |
62 | html,body {
63 | font-family: 'Source Sans Pro', sans-serif;
64 | font-weight: 400;
65 | font-size: 16px;
66 | height: 100%;
67 | width: 100%;
68 | }
69 |
70 | header {
71 | position: absolute;
72 | width: 100%;
73 | /*height: 35px;*/
74 | color: #fff;
75 | font-family: 'Source Sans Pro', sans-serif;
76 | padding-left: 20px;
77 | padding-top: 20px;
78 | padding-bottom: 20px;
79 | box-sizing: border-box;
80 | height: 80px;
81 | z-index: 100;
82 | }
83 |
84 | .repo-link {
85 | color: white;
86 | }
87 |
88 | .header-text {
89 | float: left;
90 | font-size: 2em;
91 | line-height: 1em;
92 | }
93 |
94 | .offline-text {
95 | font-size: 0.4em;
96 | visibility: hidden;
97 | }
98 |
99 | .events-remaining {
100 | float:right;
101 | margin-right: 5%;
102 | margin-top: 30px;
103 | }
104 |
105 | .events-remaining-text, .events-remaining-value {
106 | font-size: 0.8em;
107 | visibility: hidden;
108 | }
109 |
110 | #volumeSlider {
111 | cursor:pointer;
112 | position: absolute;
113 | top: 30px;
114 | right: 40px;
115 | width: 100px;
116 | opacity: 0.3;
117 | border-radius: 5px;
118 | }
119 |
120 | #volumeSlider:hover {
121 | cursor:pointer;
122 | opacity: 0.9;
123 | }
124 |
125 | #area {
126 | width: 100%;
127 | position: relative;
128 | height: 100%;
129 | overflow: hidden;
130 | }
131 |
132 | svg {
133 | height: 100%;
134 | display: block;
135 | }
136 |
137 | svg text {
138 | color: #FFFFFF;
139 | }
140 |
141 | circle {
142 | fill-opacity: 0.8;
143 | }
144 |
145 | .online-users-div {
146 | text-align: center;
147 | position: absolute;
148 | bottom: 60px;
149 | width: 100%;
150 | margin: 0 auto;
151 | font-size: 0.9em;
152 | z-index: 1;
153 | opacity: 0.5;
154 | visibility: hidden;
155 | }
156 |
157 | .online-users-text {
158 | font-family: 'Source Sans Pro', sans-serif;
159 | font-size: 1em;
160 | }
161 |
162 | .site-description {
163 | font-size: 1em;
164 | line-height: 1.6em;
165 | width: 50%;
166 | margin: 50px auto 0;
167 | }
168 |
169 | @media only screen and (max-device-width: 480px) {
170 | .online-users-div {
171 | margin-left: 20px;
172 | text-align: left;
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/public/css/main.min.css:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Source+Sans+Pro:400|Josefin+Sans";html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:''}table{border-collapse:collapse;border-spacing:0}a{text-decoration:none}*{box-sizing:border-box;margin:0;padding:0}html,body{font-family:'Source Sans Pro',sans-serif;font-weight:400;font-size:16px;height:100%;width:100%}header{position:absolute;width:100%;color:#fff;font-family:'Source Sans Pro',sans-serif;padding-left:20px;padding-top:20px;padding-bottom:20px;box-sizing:border-box;height:80px;z-index:100}.repo-link{color:#fff}.header-text{float:left;font-size:2em;line-height:1em}.offline-text{font-size:.4em;visibility:hidden}.events-remaining{float:right;margin-right:5%;margin-top:30px}.events-remaining-text,.events-remaining-value{font-size:.8em;visibility:hidden}#volumeSlider{cursor:pointer;position:absolute;top:30px;right:40px;width:100px;opacity:.3;border-radius:5px}#volumeSlider:hover{cursor:pointer;opacity:.9}#area{width:100%;position:relative;height:100%;overflow:hidden}svg{height:100%;display:block}svg text{color:#FFF}circle{fill-opacity:.8}.online-users-div{text-align:center;position:absolute;bottom:60px;width:100%;margin:0 auto;font-size:.9em;z-index:1;opacity:.5;visibility:hidden}.online-users-text{font-family:'Source Sans Pro',sans-serif;font-size:1em}.site-description{font-size:1em;line-height:1.6em;width:50%;margin:50px auto 0}@media only screen and (max-device-width:480px){.online-users-div{margin-left:20px;text-align:left}}html,body{background-color:#232323}a{color:#0091EA}svg{background-color:#232323}.label{font:1em 'Source Sans Pro',sans-serif;text-shadow:1px 1px 0 rgb(28,39,51),-1px -1px 0 rgb(28,39,51),1px -1px 0 rgb(28,39,51),-1px 1px 0 rgb(28,39,51),0 1px 0 rgb(28,39,51),1px 0 0 rgb(28,39,51),0 -1px 0 rgb(28,39,51),-1px 0 0 rgb(28,39,51)}.online-users-text{color:#E0E0E0}.event-1{fill:#22B65D}.event-2{fill:#8F19BB}.event-3{fill:#ADD913}.event-4{fill:#FF4901}.event-5{fill:#0184FF}.event-6{fill:#00C0C0}.event-7{fill:#E60062}
--------------------------------------------------------------------------------
/public/css/themes/default.css:
--------------------------------------------------------------------------------
1 | html,body {
2 | background-color: #232323;
3 | }
4 |
5 | a {
6 | color: #0091EA;
7 | }
8 |
9 | svg {
10 | background-color: #232323;
11 | }
12 |
13 | .label {
14 | font: 1.0em 'Source Sans Pro', sans-serif;
15 | text-shadow:1px 1px 0 rgb(28, 39, 51),
16 | -1px -1px 0 rgb(28, 39, 51),
17 | 1px -1px 0 rgb(28, 39, 51),
18 | -1px 1px 0 rgb(28, 39, 51),
19 | 0 1px 0 rgb(28, 39, 51),
20 | 1px 0 0 rgb(28, 39, 51),
21 | 0 -1px 0 rgb(28, 39, 51),
22 | -1px 0 0 rgb(28, 39, 51);
23 | }
24 |
25 | .online-users-text {
26 | color: #E0E0E0;
27 | }
28 |
29 | .event-1 {
30 | fill: #22B65D;
31 | }
32 |
33 | .event-2 {
34 | fill: #8F19BB;
35 | }
36 |
37 | .event-3 {
38 | fill: #ADD913;
39 | }
40 |
41 | .event-4 {
42 | fill: #FF4901;
43 | }
44 |
45 | .event-5 {
46 | fill: #0184FF;
47 | }
48 |
49 | .event-6 {
50 | fill: #00C0C0;
51 | }
52 |
53 | .event-7 {
54 | fill: #E60062;
55 | }
56 |
--------------------------------------------------------------------------------
/public/images/GitAmp-logo-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/GitAmp-logo-black.png
--------------------------------------------------------------------------------
/public/images/GitAmp-logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/GitAmp-logo-white.png
--------------------------------------------------------------------------------
/public/images/GitAmp-neon-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/GitAmp-neon-logo.png
--------------------------------------------------------------------------------
/public/images/electric-guitar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/electric-guitar.png
--------------------------------------------------------------------------------
/public/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/favicon.png
--------------------------------------------------------------------------------
/public/images/neon-g-favicon_256.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/neon-g-favicon_256.ico
--------------------------------------------------------------------------------
/public/images/speaker-muted.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
36 |
--------------------------------------------------------------------------------
/public/images/speaker.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
36 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | GitAmp
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
43 |
44 |
events remaining in queue
45 |
46 |
47 |
48 |
49 |
50 |
51 |
people listening
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/public/js/main.js:
--------------------------------------------------------------------------------
1 | const GitAmp = (function(exports, $) {
2 | 'use strict';
3 |
4 | /**
5 | * AudioPlayer
6 | */
7 | const AudioPlayer = (function() {
8 | // something somewhere needs a global volume variable
9 | // not sure what thing it is, but adding this line works
10 | exports.volume = localStorage.getItem('volume');
11 |
12 | const maxPitch = 100.0;
13 | const logUsed = 1.0715307808111486871978099;
14 |
15 | const maximumSimultaneousNotes = 2;
16 | const soundLength = 300;
17 |
18 | function AudioPlayer() {
19 | this.currentlyPlayingSounds = 0;
20 |
21 | this.sounds = {
22 | celesta: this.initializeCelesta(),
23 | clav: this.initializeClav(),
24 | swells: this.initializeSwells(),
25 | easterEggs: this.initializeEasterEggs()
26 | };
27 |
28 | //noinspection JSUnresolvedVariable
29 | exports.Howler.volume(localStorage.getItem('volume'));
30 | }
31 |
32 | AudioPlayer.prototype.initializeCelesta = function() {
33 | const sounds = [];
34 |
35 | for (let i = 1; i <= 24; i++) {
36 | let filename = (i > 9) ? 'c0' + i : 'c00' + i;
37 |
38 | //noinspection JSUnresolvedFunction
39 | sounds.push(new Howl({
40 | src : [
41 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + filename + '.ogg',
42 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + filename + '.mp3'
43 | ],
44 | volume : 0.7,
45 | buffer: true
46 | }));
47 | }
48 |
49 | return sounds;
50 | };
51 |
52 | AudioPlayer.prototype.initializeClav = function() {
53 | const sounds = [];
54 |
55 | for (let i = 1; i <= 24; i++) {
56 | let filename = (i > 9) ? 'c0' + i : 'c00' + i;
57 |
58 | //noinspection JSUnresolvedFunction
59 | sounds.push(new Howl({
60 | src : [
61 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + filename + '.ogg',
62 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + filename + '.mp3'
63 | ],
64 | volume : 0.7,
65 | buffer: true
66 | }));
67 | }
68 |
69 | return sounds;
70 | };
71 |
72 | AudioPlayer.prototype.initializeSwells = function() {
73 | const sounds = [];
74 |
75 | for (let i = 1; i <= 3; i++) {
76 | //noinspection JSUnresolvedFunction
77 | sounds.push(new Howl({
78 | src : [
79 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.ogg',
80 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.mp3'
81 | ],
82 | volume : 0.7,
83 | buffer: true
84 | }));
85 | }
86 |
87 | return sounds;
88 | };
89 |
90 | AudioPlayer.prototype.initializeEasterEggs = function() {
91 | const sounds = {};
92 |
93 | const eggs = ['celesta', 'clav', 'swell'];
94 |
95 | for (let i = 0; i < eggs.length; i++) {
96 | //noinspection JSUnresolvedFunction
97 | sounds[eggs[i]] = new Howl({
98 | src : [
99 | '/sounds/egg/' + eggs[i] + '.ogg',
100 | '/sounds/egg/' + eggs[i] + '.mp3'
101 | ],
102 | volume : 0.7,
103 | buffer: true
104 | });
105 | }
106 |
107 | return sounds;
108 | };
109 |
110 | AudioPlayer.prototype.getSoundIndex = function(size, type) {
111 | const pitch = 100 - Math.min(maxPitch, Math.log(size + logUsed) / Math.log(logUsed));
112 | let index = Math.floor(pitch / 100.0 * this.sounds[type].length);
113 |
114 | index += Math.floor(Math.random() * 4) - 2;
115 | index = Math.min(this.sounds[type].length - 1, index);
116 | index = Math.max(1, index);
117 |
118 | return index;
119 | };
120 |
121 | AudioPlayer.prototype.playSound = function(sound) {
122 | if (this.currentlyPlayingSounds >= maximumSimultaneousNotes) {
123 | return;
124 | }
125 |
126 | sound.play();
127 |
128 | this.currentlyPlayingSounds++;
129 |
130 | setTimeout(function() {
131 | this.currentlyPlayingSounds--;
132 | }.bind(this), soundLength);
133 | };
134 |
135 | AudioPlayer.prototype.playCelesta = function(size) {
136 | this.playSound(this.sounds.celesta[this.getSoundIndex(size, 'celesta')]);
137 | };
138 |
139 | AudioPlayer.prototype.playClav = function(size) {
140 | this.playSound(this.sounds.clav[this.getSoundIndex(size, 'clav')]);
141 | };
142 |
143 | AudioPlayer.prototype.playSwell = function() {
144 | this.playSound(this.sounds.swells[Math.round(Math.random() * (this.sounds.swells.length - 1))]);
145 | };
146 |
147 | AudioPlayer.prototype.playCelestaEgg = function() {
148 | //noinspection JSUnresolvedVariable
149 | this.playSound(this.sounds.easterEggs.celesta);
150 | };
151 |
152 | AudioPlayer.prototype.playClavEgg = function() {
153 | //noinspection JSUnresolvedVariable
154 | this.playSound(this.sounds.easterEggs.clav);
155 | };
156 |
157 | AudioPlayer.prototype.playSwellEgg = function() {
158 | //noinspection JSUnresolvedVariable
159 | this.playSound(this.sounds.easterEggs.swell);
160 | };
161 |
162 | return AudioPlayer;
163 | }());
164 |
165 | /**
166 | * Gui
167 | */
168 | const Gui = (function() {
169 | const scaleFactor = 6;
170 | const textColor = '#ffffff';
171 | const maxLife = 20000;
172 |
173 | function Event(event, svg) {
174 | this.event = event;
175 | this.svg = svg;
176 | }
177 |
178 | Event.prototype.getSize = function() {
179 | return Math.max(Math.sqrt(Math.abs(this.event.getPayload().length)) * scaleFactor, 3);
180 | };
181 |
182 | Event.prototype.getText = function() {
183 | return this.event.getMessage();
184 | };
185 |
186 | Event.prototype.getClassName = function() {
187 | return 'event-' + this.event.getType();
188 | };
189 |
190 | Event.prototype.getRingAnimationDuration = function() {
191 | return this.event.getRingAnimationDuration();
192 | };
193 |
194 | Event.prototype.getRingRadius = function() {
195 | return this.event.getRingRadius();
196 | };
197 |
198 | Event.prototype.draw = function(width, height) {
199 | let no_label = false;
200 | let size = this.getSize();
201 |
202 | const self = this;
203 |
204 | //noinspection JSUnresolvedFunction
205 | Math.seedrandom(this.event.getPayload());
206 | let x = Math.random() * (width - size) + size;
207 | let y = Math.random() * (height - size) + size;
208 |
209 | let circle_group = this.svg.append('g')
210 | .classed(this.getClassName(), true)
211 | .attr('transform', 'translate(' + x + ', ' + y + ')')
212 | .style('opacity', 1);
213 |
214 | let ring = circle_group.append('circle');
215 | ring.attr({r: size, stroke: 'none'});
216 | ring.transition()
217 | .attr('r', size + this.getRingRadius())
218 | .style('opacity', 0)
219 | .ease(Math.sqrt)
220 | .duration(this.getRingAnimationDuration())
221 | .remove();
222 |
223 | let circle_container = circle_group.append('a');
224 | circle_container.attr('xlink:href', this.event.getUrl());
225 | circle_container.attr('target', '_blank');
226 | circle_container.attr('fill', textColor);
227 |
228 | let circle = circle_container.append('circle');
229 | circle.classed(this.getClassName(), true);
230 | circle.attr('r', size)
231 | .transition()
232 | .duration(maxLife)
233 | .style('opacity', 0)
234 | .remove();
235 |
236 | circle_container.on('mouseover', function() {
237 | circle_container.append('text')
238 | .text(self.getText())
239 | .classed('label', true)
240 | .attr('text-anchor', 'middle')
241 | .attr('font-size', '0.8em')
242 | .transition()
243 | .delay(1000)
244 | .style('opacity', 0)
245 | .duration(2000)
246 | .each(function() { no_label = true; })
247 | .remove();
248 | });
249 |
250 | circle_container.append('text')
251 | .text(this.getText())
252 | .classed('article-label', true)
253 | .attr('text-anchor', 'middle')
254 | .attr('font-size', '0.8em')
255 | .transition()
256 | .delay(2000)
257 | .style('opacity', 0)
258 | .duration(5000)
259 | .each(function() { no_label = true; })
260 | .remove();
261 | };
262 |
263 | function Gui() {
264 | //noinspection JSUnresolvedVariable
265 | this.svg = exports.d3.select('#area').append('svg');
266 |
267 | exports.addEventListener('resize', this.resize.bind(this));
268 |
269 | this.setupVolumeSlider();
270 | this.resize();
271 | }
272 |
273 | Gui.prototype.setupVolumeSlider = function() {
274 | //noinspection JSUnresolvedFunction
275 | $('#volumeSlider').slider({
276 | max: 100,
277 | min: 0,
278 | value: localStorage.getItem('volume') * 100,
279 | slide: function (event, ui) {
280 | //noinspection JSUnresolvedVariable
281 | exports.Howler.volume(ui.value/100.0);
282 |
283 | localStorage.setItem('volume', ui.value/100.0);
284 | },
285 | change: function (event, ui) {
286 | //noinspection JSUnresolvedVariable
287 | exports.Howler.volume(ui.value/100.0);
288 |
289 | localStorage.setItem('volume', ui.value/100.0);
290 | }
291 | });
292 | };
293 |
294 | Gui.prototype.getWidth = function() {
295 | return exports.innerWidth;
296 | };
297 |
298 | Gui.prototype.getHeight = function() {
299 | return exports.innerHeight;
300 | };
301 |
302 | Gui.prototype.resize = function() {
303 | this.svg.attr('width', this.getWidth());
304 | this.svg.attr('height', this.getHeight());
305 | };
306 |
307 | Gui.prototype.drawEvent = function(event) {
308 | if (document.hidden) {
309 | return;
310 | }
311 |
312 | new Event(event, this.svg).draw(this.getWidth(), this.getHeight());
313 |
314 | // Remove HTML of decayed events
315 | // Keep it less than 50
316 | let $area = $('#area');
317 | if($area.find('svg g').length > 50){
318 | $area.find('svg g:lt(10)').remove();
319 | }
320 | };
321 |
322 | return Gui;
323 | }());
324 |
325 | /**
326 | * ConnectedUsersMessage
327 | */
328 | function ConnectedUsersMessage(response) {
329 | //noinspection JSUnresolvedVariable
330 | this.count = response.connectedUsers;
331 | }
332 |
333 | ConnectedUsersMessage.prototype.getCount = function() {
334 | return this.count;
335 | };
336 |
337 | /**
338 | * EventMessage
339 | */
340 | function EventMessage(event) {
341 | this.event = event;
342 | }
343 |
344 | EventMessage.prototype.getId = function() {
345 | //noinspection JSUnresolvedVariable
346 | return this.event.id;
347 | };
348 |
349 | EventMessage.prototype.getType = function() {
350 | //noinspection JSUnresolvedVariable
351 | return this.event.type;
352 | };
353 |
354 | EventMessage.prototype.getUrl = function() {
355 | //noinspection JSUnresolvedVariable
356 | return this.event.information.url;
357 | };
358 |
359 | EventMessage.prototype.getPayload = function() {
360 | //noinspection JSUnresolvedVariable
361 | return this.event.information.payload;
362 | };
363 |
364 | EventMessage.prototype.getMessage = function() {
365 | //noinspection JSUnresolvedVariable
366 | return this.event.information.message;
367 | };
368 |
369 | EventMessage.prototype.getRingAnimationDuration = function() {
370 | //noinspection JSUnresolvedVariable
371 | return this.event.ring.animationDuration;
372 | };
373 |
374 | EventMessage.prototype.getRingRadius = function() {
375 | //noinspection JSUnresolvedVariable
376 | return this.event.ring.radius;
377 | };
378 |
379 | EventMessage.prototype.getSoundType = function() {
380 | //noinspection JSUnresolvedVariable
381 | return this.event.sound.type;
382 | };
383 |
384 | EventMessage.prototype.getSoundSize = function() {
385 | //noinspection JSUnresolvedVariable
386 | return this.event.sound.size;
387 | };
388 |
389 | /**
390 | * EventMessageCollection
391 | */
392 | function EventMessageCollection(response) {
393 | this.events = [];
394 |
395 | for (let i = 0; i < response.length; i++) {
396 | this.events.push(new EventMessage(response[i]));
397 | }
398 | }
399 |
400 | EventMessageCollection.prototype.forEach = function(callback) {
401 | for (let i = 0; i < this.events.length; i++) {
402 | callback(this.events[i]);
403 | }
404 | };
405 |
406 | /**
407 | * EventMessagesFactory
408 | */
409 | function EventMessagesFactory () {
410 | }
411 |
412 | EventMessagesFactory.prototype.build = function(response) {
413 | const parsedResponse = JSON.parse(response.data);
414 |
415 | if (parsedResponse.hasOwnProperty('connectedUsers')) {
416 | return new ConnectedUsersMessage(parsedResponse);
417 | }
418 |
419 | return new EventMessageCollection(parsedResponse);
420 | };
421 |
422 | /**
423 | * EventQueue
424 | */
425 | function EventQueue() {
426 | this.queue = [];
427 | }
428 |
429 | EventQueue.prototype.append = function(eventMessages) {
430 | eventMessages.forEach(function(event) {
431 | if (this.exists(event)) {
432 | return;
433 | }
434 |
435 | this.queue.push(event);
436 | }.bind(this));
437 |
438 | if (this.queue.length > 1000) {
439 | this.queue = this.queue.slice(0, 1000);
440 | }
441 | };
442 |
443 | EventQueue.prototype.exists = function(event) {
444 | for (let i = 0; i < this.queue.length; i++) {
445 | if (event.getId() === this.queue[i].getId()) {
446 | return true;
447 | }
448 | }
449 |
450 | return false;
451 | };
452 |
453 | EventQueue.prototype.get = function() {
454 | return this.queue.shift();
455 | };
456 |
457 | EventQueue.prototype.count = function() {
458 | return this.queue.length;
459 | };
460 |
461 | /**
462 | * Connection
463 | */
464 | function Connection(eventMessageFactory) {
465 | this.eventMessageFactory = eventMessageFactory;
466 |
467 | this.connection = null;
468 | this.handlers = [];
469 | }
470 |
471 | Connection.prototype.start = function() {
472 | let protocol = 'ws://';
473 |
474 | if (exports.location.protocol === "https:") {
475 | protocol = 'wss://';
476 | }
477 |
478 | try {
479 | this.connection = new WebSocket(protocol + exports.location.host + '/ws');
480 |
481 | this.connection.addEventListener('message', this.handleMessage.bind(this));
482 | this.connection.addEventListener('open', this.handleOpen.bind(this));
483 | this.connection.addEventListener('close', this.reconnect.bind(this));
484 | this.connection.addEventListener('error', this.reconnect.bind(this));
485 | } catch(e) {
486 | this.connection = null;
487 | }
488 | };
489 |
490 | Connection.prototype.registerHandler = function(handler) {
491 | this.handlers.push(handler);
492 | };
493 |
494 | Connection.prototype.handleMessage = function(response) {
495 | const message = this.eventMessageFactory.build(response);
496 |
497 | for (let i = 0; i < this.handlers.length; i++) {
498 | this.handlers[i](message);
499 | }
500 | };
501 |
502 | Connection.prototype.handleOpen = function() {
503 | const elements = document.querySelectorAll('.online-users-div');
504 |
505 | for (let i = 0; i < elements.length; i++) {
506 | elements[i].style.visibility = 'visible';
507 | }
508 | };
509 |
510 | Connection.prototype.reconnect = function() {
511 | // prevent piling up reconnect
512 | if (this.connection.readyState === 0 || this.connection.readyState === 1) {
513 | return;
514 | }
515 |
516 | setTimeout(function() {
517 | this.start();
518 | }.bind(this), 5000);
519 | };
520 |
521 | /**
522 | * Application
523 | */
524 | function Application() {
525 | if (localStorage.getItem('volume') === null) {
526 | localStorage.setItem('volume', 0.6);
527 | }
528 |
529 | this.queue = new EventQueue();
530 | this.audio = new AudioPlayer();
531 | this.gui = new Gui();
532 | }
533 |
534 | Application.prototype.run = function() {
535 | const connection = new Connection(new EventMessagesFactory());
536 |
537 | connection.registerHandler(this.process.bind(this));
538 |
539 | connection.start();
540 |
541 | this.loop();
542 | };
543 |
544 | Application.prototype.process = function(message) {
545 | if (message instanceof ConnectedUsersMessage) {
546 | document.getElementsByClassName('online-users-count')[0].textContent = message.getCount();
547 |
548 | return;
549 | }
550 |
551 | this.queue.append(message);
552 | };
553 |
554 | Application.prototype.loop = function() {
555 | setTimeout(function() {
556 | this.loop();
557 |
558 | if (!this.queue.count()) {
559 | return;
560 | }
561 |
562 | this.processEvent(this.queue.get());
563 |
564 | document.getElementsByClassName('events-remaining-value')[0].textContent = this.queue.count();
565 | }.bind(this), Math.floor(Math.random() * 1000) + 500);
566 | };
567 |
568 | Application.prototype.processEvent = function(event) {
569 | if (!event.getPayload()) {
570 | return;
571 | }
572 |
573 | this.audio['play' + event.getSoundType()](event.getSoundSize());
574 |
575 | this.gui.drawEvent(event);
576 | };
577 |
578 | return Application;
579 | }(window, jQuery));
580 |
581 | $(function() {
582 | new GitAmp().run();
583 | });
584 |
--------------------------------------------------------------------------------
/public/js/main.min.js:
--------------------------------------------------------------------------------
1 | const GitAmp=(function(exports,$){'use strict';const AudioPlayer=(function(){exports.volume=localStorage.getItem('volume');const maxPitch=100.0;const logUsed=1.0715307808111486871978099;const maximumSimultaneousNotes=2;const soundLength=300;function AudioPlayer(){this.currentlyPlayingSounds=0;this.sounds={celesta:this.initializeCelesta(),clav:this.initializeClav(),swells:this.initializeSwells(),easterEggs:this.initializeEasterEggs()};exports.Howler.volume(localStorage.getItem('volume'))}
2 | AudioPlayer.prototype.initializeCelesta=function(){const sounds=[];for(let i=1;i<=24;i++){let filename=(i>9)?'c0'+i:'c00'+i;sounds.push(new Howl({src:['https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/'+filename+'.ogg','https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/'+filename+'.mp3'],volume:0.7,buffer:!0}))}
3 | return sounds};AudioPlayer.prototype.initializeClav=function(){const sounds=[];for(let i=1;i<=24;i++){let filename=(i>9)?'c0'+i:'c00'+i;sounds.push(new Howl({src:['https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/'+filename+'.ogg','https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/'+filename+'.mp3'],volume:0.7,buffer:!0}))}
4 | return sounds};AudioPlayer.prototype.initializeSwells=function(){const sounds=[];for(let i=1;i<=3;i++){sounds.push(new Howl({src:['https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell'+i+'.ogg','https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell'+i+'.mp3'],volume:0.7,buffer:!0}))}
5 | return sounds};AudioPlayer.prototype.initializeEasterEggs=function(){const sounds={};const eggs=['celesta','clav','swell'];for(let i=0;i=maximumSimultaneousNotes){return}
7 | sound.play();this.currentlyPlayingSounds++;setTimeout(function(){this.currentlyPlayingSounds--}.bind(this),soundLength)};AudioPlayer.prototype.playCelesta=function(size){this.playSound(this.sounds.celesta[this.getSoundIndex(size,'celesta')])};AudioPlayer.prototype.playClav=function(size){this.playSound(this.sounds.clav[this.getSoundIndex(size,'clav')])};AudioPlayer.prototype.playSwell=function(){this.playSound(this.sounds.swells[Math.round(Math.random()*(this.sounds.swells.length-1))])};AudioPlayer.prototype.playCelestaEgg=function(){this.playSound(this.sounds.easterEggs.celesta)};AudioPlayer.prototype.playClavEgg=function(){this.playSound(this.sounds.easterEggs.clav)};AudioPlayer.prototype.playSwellEgg=function(){this.playSound(this.sounds.easterEggs.swell)};return AudioPlayer}());const Gui=(function(){const scaleFactor=6;const textColor='#ffffff';const maxLife=20000;function Event(event,svg){this.event=event;this.svg=svg}
8 | Event.prototype.getSize=function(){return Math.max(Math.sqrt(Math.abs(this.event.getPayload().length))*scaleFactor,3)};Event.prototype.getText=function(){return this.event.getMessage()};Event.prototype.getClassName=function(){return'event-'+this.event.getType()};Event.prototype.getRingAnimationDuration=function(){return this.event.getRingAnimationDuration()};Event.prototype.getRingRadius=function(){return this.event.getRingRadius()};Event.prototype.draw=function(width,height){let no_label=!1;let size=this.getSize();const self=this;Math.seedrandom(this.event.getPayload());let x=Math.random()*(width-size)+size;let y=Math.random()*(height-size)+size;let circle_group=this.svg.append('g').classed(this.getClassName(),!0).attr('transform','translate('+x+', '+y+')').style('opacity',1);let ring=circle_group.append('circle');ring.attr({r:size,stroke:'none'});ring.transition().attr('r',size+this.getRingRadius()).style('opacity',0).ease(Math.sqrt).duration(this.getRingAnimationDuration()).remove();let circle_container=circle_group.append('a');circle_container.attr('xlink:href',this.event.getUrl());circle_container.attr('target','_blank');circle_container.attr('fill',textColor);let circle=circle_container.append('circle');circle.classed(this.getClassName(),!0);circle.attr('r',size).transition().duration(maxLife).style('opacity',0).remove();circle_container.on('mouseover',function(){circle_container.append('text').text(self.getText()).classed('label',!0).attr('text-anchor','middle').attr('font-size','0.8em').transition().delay(1000).style('opacity',0).duration(2000).each(function(){no_label=!0}).remove()});circle_container.append('text').text(this.getText()).classed('article-label',!0).attr('text-anchor','middle').attr('font-size','0.8em').transition().delay(2000).style('opacity',0).duration(5000).each(function(){no_label=!0}).remove()};function Gui(){this.svg=exports.d3.select('#area').append('svg');exports.addEventListener('resize',this.resize.bind(this));this.setupVolumeSlider();this.resize()}
9 | Gui.prototype.setupVolumeSlider=function(){$('#volumeSlider').slider({max:100,min:0,value:localStorage.getItem('volume')*100,slide:function(event,ui){exports.Howler.volume(ui.value/100.0);localStorage.setItem('volume',ui.value/100.0)},change:function(event,ui){exports.Howler.volume(ui.value/100.0);localStorage.setItem('volume',ui.value/100.0)}})};Gui.prototype.getWidth=function(){return exports.innerWidth};Gui.prototype.getHeight=function(){return exports.innerHeight};Gui.prototype.resize=function(){this.svg.attr('width',this.getWidth());this.svg.attr('height',this.getHeight())};Gui.prototype.drawEvent=function(event){if(document.hidden){return}
10 | new Event(event,this.svg).draw(this.getWidth(),this.getHeight());let $area=$('#area');if($area.find('svg g').length>50){$area.find('svg g:lt(10)').remove()}};return Gui}());function ConnectedUsersMessage(response){this.count=response.connectedUsers}
11 | ConnectedUsersMessage.prototype.getCount=function(){return this.count};function EventMessage(event){this.event=event}
12 | EventMessage.prototype.getId=function(){return this.event.id};EventMessage.prototype.getType=function(){return this.event.type};EventMessage.prototype.getUrl=function(){return this.event.information.url};EventMessage.prototype.getPayload=function(){return this.event.information.payload};EventMessage.prototype.getMessage=function(){return this.event.information.message};EventMessage.prototype.getRingAnimationDuration=function(){return this.event.ring.animationDuration};EventMessage.prototype.getRingRadius=function(){return this.event.ring.radius};EventMessage.prototype.getSoundType=function(){return this.event.sound.type};EventMessage.prototype.getSoundSize=function(){return this.event.sound.size};function EventMessageCollection(response){this.events=[];for(let i=0;i1000){this.queue=this.queue.slice(0,1000)}};EventQueue.prototype.exists=function(event){for(let i=0;ibuild($configuration->getLogLevel());
14 |
15 | Loop::setErrorHandler(function (\Throwable $e) use ($logger): void {
16 | $logger->emergency('GitAmp blew up', ['exception' => $e]);
17 | });
18 |
19 | $server = new Server($logger, $configuration);
20 |
21 | Loop::run(function () use ($server) {
22 | yield $server->start();
23 | });
24 |
--------------------------------------------------------------------------------
/src/Configuration.php:
--------------------------------------------------------------------------------
1 | */
14 | private array $websocketAddresses = [];
15 |
16 | /** @var array */
17 | private array $bind = [];
18 |
19 | /** @var array */
20 | private array $specialRepositories = [];
21 |
22 | /** @var array */
23 | private array $bindSsl = [];
24 |
25 | private Token $githubToken;
26 |
27 | public function __construct(Token $githubToken)
28 | {
29 | $this->githubToken = $githubToken;
30 | }
31 |
32 | public function setLogLevel(int $logLevel): self
33 | {
34 | $this->logLevel = $logLevel;
35 |
36 | return $this;
37 | }
38 |
39 | public function getLogLevel(): int
40 | {
41 | return $this->logLevel;
42 | }
43 |
44 | public function addWebsocketAddress(UriInterface $address): self
45 | {
46 | $this->websocketAddresses[] = $address;
47 |
48 | return $this;
49 | }
50 |
51 | public function websocketAddressExists(string $origin): bool
52 | {
53 | foreach ($this->websocketAddresses as $websocketAddress) {
54 | if ((string) $websocketAddress === $origin) {
55 | return true;
56 | }
57 | }
58 |
59 | return false;
60 | }
61 |
62 | public function bind(ServerAddress $address): self
63 | {
64 | $this->bind[] = $address;
65 |
66 | return $this;
67 | }
68 |
69 | public function bindSsl(SslServerAddress $sslServerAddress): self
70 | {
71 | $this->bindSsl[] = $sslServerAddress;
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * @return array
78 | */
79 | public function getServerAddresses(): array
80 | {
81 | return $this->bind;
82 | }
83 |
84 | /**
85 | * @return array
86 | */
87 | public function getSslServerAddresses(): array
88 | {
89 | return $this->bindSsl;
90 | }
91 |
92 | public function addSpecialRepository(string $repository): self
93 | {
94 | $this->specialRepositories[] = $repository;
95 |
96 | return $this;
97 | }
98 |
99 | /**
100 | * @return array
101 | */
102 | public function getSpecialRepositories(): array
103 | {
104 | return $this->specialRepositories;
105 | }
106 |
107 | public function getGithubToken(): Token
108 | {
109 | return $this->githubToken;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Event/BaseEvent.php:
--------------------------------------------------------------------------------
1 | id = $id;
25 | $this->type = $type;
26 | $this->information = $information;
27 | $this->ring = $ring;
28 | $this->sound = $sound;
29 | }
30 |
31 | /**
32 | * @return array
33 | */
34 | public function getAsArray(): array
35 | {
36 | return [
37 | 'id' => $this->id,
38 | 'type' => $this->type->getValue(),
39 | 'information' => $this->information->getAsArray(),
40 | 'ring' => $this->ring->getAsArray(),
41 | 'sound' => $this->sound->getAsArray(),
42 | ];
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Event/Event.php:
--------------------------------------------------------------------------------
1 | */
10 | private array $specialRepositories = [];
11 |
12 | /**
13 | * @param array $specialRepositories
14 | */
15 | public function __construct(array $specialRepositories)
16 | {
17 | $this->specialRepositories = $specialRepositories;
18 | }
19 |
20 | public function build(string $namespace, array $event): Event
21 | {
22 | $eventType = $namespace . '\\' . $event['type'];
23 |
24 | if (!$this->isValidType($eventType)) {
25 | throw new UnknownEvent($event['type']);
26 | }
27 |
28 | return new $eventType($event, $this->specialRepositories);
29 | }
30 |
31 | private function isValidType(string $type): bool
32 | {
33 | return \class_exists($type);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Event/GitHub/CreateEvent.php:
--------------------------------------------------------------------------------
1 | $event
17 | * @param array $specialRepositories
18 | */
19 | public function __construct(array $event, array $specialRepositories)
20 | {
21 | parent::__construct(
22 | (int) $event['id'],
23 | new Type(Type::REPOSITORY_CREATED),
24 | new Information($this->buildUrl($event), $this->buildPayload($event), $this->buildMessage($event)),
25 | new Ring(3000, 80),
26 | $this->buildSound($event, $specialRepositories),
27 | );
28 | }
29 |
30 | /**
31 | * @param array $event
32 | */
33 | private function buildUrl(array $event): string
34 | {
35 | return 'https://github.com/' . $event['repo']['name'];
36 | }
37 |
38 | /**
39 | * @param array $event
40 | */
41 | private function buildPayload(array $event): string
42 | {
43 | if (isset($event['payload']['description'])) {
44 | return $event['payload']['description'];
45 | }
46 |
47 | return 'https://github.com/' . $event['repo']['name'];
48 | }
49 |
50 | /**
51 | * @param array $event
52 | */
53 | private function buildMessage(array $event): string
54 | {
55 | return \sprintf('%s created %s', $event['actor']['login'], $event['repo']['name']);
56 | }
57 |
58 | /**
59 | * @param array $event
60 | * @param array $specialRepositories
61 | */
62 | private function buildSound(array $event, array $specialRepositories): BaseSound
63 | {
64 | if (in_array($event['repo']['name'], $specialRepositories, true)) {
65 | return new SwellEgg();
66 | }
67 |
68 | return new Swell();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Event/GitHub/ForkEvent.php:
--------------------------------------------------------------------------------
1 | $event
17 | * @param array $specialRepositories
18 | */
19 | public function __construct(array $event, array $specialRepositories)
20 | {
21 | parent::__construct(
22 | (int) $event['id'],
23 | new Type(Type::REPOSITORY_FORKED),
24 | new Information($this->buildUrl($event), $this->buildPayload(), $this->buildMessage($event)),
25 | new Ring(3000, 80),
26 | $this->buildSound($event, $specialRepositories),
27 | );
28 | }
29 |
30 | /**
31 | * @param array $event
32 | */
33 | private function buildUrl(array $event): string
34 | {
35 | return 'https://github.com/' . $event['repo']['name'];
36 | }
37 |
38 | /**
39 | * @param array $event
40 | */
41 | private function buildPayload(): string
42 | {
43 | return 'not sure if stupid but works anyway';
44 | }
45 |
46 | /**
47 | * @param array $event
48 | */
49 | private function buildMessage(array $event): string
50 | {
51 | return \sprintf('%s forked %s', $event['actor']['login'], $event['repo']['name']);
52 | }
53 |
54 | /**
55 | * @param array $event
56 | * @param array $specialRepositories
57 | */
58 | private function buildSound(array $event, array $specialRepositories): BaseSound
59 | {
60 | if (in_array($event['repo']['name'], $specialRepositories, true)) {
61 | return new SwellEgg();
62 | }
63 |
64 | return new Swell();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Event/GitHub/IssueCommentEvent.php:
--------------------------------------------------------------------------------
1 | $event
17 | * @param array $specialRepositories
18 | */
19 | public function __construct(array $event, array $specialRepositories)
20 | {
21 | parent::__construct(
22 | (int) $event['id'],
23 | new Type(Type::COMMENTED_ON_ISSUE),
24 | new Information(
25 | $event['payload']['issue']['html_url'],
26 | $this->buildPayload($event),
27 | $this->buildMessage($event),
28 | ),
29 | new Ring(3000, 80),
30 | $this->buildSound($event, $specialRepositories),
31 | );
32 | }
33 |
34 | /**
35 | * @param array $event
36 | */
37 | private function buildPayload(array $event): string
38 | {
39 | if (isset($event['comment']['body'])) {
40 | return $event['comment']['body'];
41 | }
42 |
43 | return $event['payload']['issue']['title'];
44 | }
45 |
46 | /**
47 | * @param array $event
48 | */
49 | private function buildMessage(array $event): string
50 | {
51 | return \sprintf('%s commented in %s', $event['actor']['login'], $event['repo']['name']);
52 | }
53 |
54 | /**
55 | * @param array $event
56 | * @param array $specialRepositories
57 | */
58 | private function buildSound(array $event, array $specialRepositories): BaseSound
59 | {
60 | if (in_array($event['repo']['name'], $specialRepositories, true)) {
61 | return new ClavEgg();
62 | }
63 |
64 | return new Clav(\strlen($this->buildPayload($event)) * 1.1);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Event/GitHub/IssuesEvent.php:
--------------------------------------------------------------------------------
1 | $event
17 | * @param array $specialRepositories
18 | */
19 | public function __construct(array $event, array $specialRepositories)
20 | {
21 | parent::__construct(
22 | (int) $event['id'],
23 | new Type(Type::ISSUE_ACTION),
24 | new Information(
25 | $event['payload']['issue']['html_url'],
26 | $event['payload']['issue']['title'],
27 | $this->buildMessage($event),
28 | ),
29 | new Ring(3000, 80),
30 | $this->buildSound($event, $specialRepositories),
31 | );
32 | }
33 |
34 | /**
35 | * @param array $event
36 | */
37 | private function buildMessage(array $event): string
38 | {
39 | return \sprintf(
40 | '%s %s an issue in %s',
41 | $event['actor']['login'],
42 | $event['payload']['action'],
43 | $event['repo']['name'],
44 | );
45 | }
46 |
47 | /**
48 | * @param array $event
49 | * @param array $specialRepositories
50 | */
51 | private function buildSound(array $event, array $specialRepositories): BaseSound
52 | {
53 | if (in_array($event['repo']['name'], $specialRepositories, true)) {
54 | return new ClavEgg();
55 | }
56 |
57 | return new Clav(\strlen($event['payload']['issue']['title']) * 1.1);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Event/GitHub/PullRequestEvent.php:
--------------------------------------------------------------------------------
1 | $event
17 | * @param array $specialRepositories
18 | */
19 | public function __construct(array $event, array $specialRepositories)
20 | {
21 | parent::__construct(
22 | (int) $event['id'],
23 | new Type(Type::PR_ACTION),
24 | new Information(
25 | $event['payload']['pull_request']['html_url'],
26 | $event['payload']['pull_request']['title'],
27 | $this->buildMessage($event),
28 | ),
29 | new Ring(10000, 600),
30 | $this->buildSound($event, $specialRepositories),
31 | );
32 | }
33 |
34 | /**
35 | * @param array $event
36 | */
37 | private function buildMessage(array $event): string
38 | {
39 | return \sprintf(
40 | '%s %s a PR for %s',
41 | $event['actor']['login'],
42 | $event['payload']['action'],
43 | $event['repo']['name'],
44 | );
45 | }
46 |
47 | /**
48 | * @param array $event
49 | * @param array $specialRepositories
50 | */
51 | private function buildSound(array $event, array $specialRepositories): BaseSound
52 | {
53 | if (in_array($event['repo']['name'], $specialRepositories, true)) {
54 | return new SwellEgg();
55 | }
56 |
57 | return new Swell();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Event/GitHub/PushEvent.php:
--------------------------------------------------------------------------------
1 | $event
17 | * @param array $specialRepositories
18 | */
19 | public function __construct(array $event, array $specialRepositories)
20 | {
21 | parent::__construct(
22 | (int) $event['id'],
23 | new Type(Type::PUSH_TO_REPOSITORY),
24 | new Information($this->buildUrl($event), $this->buildPayload($event), $this->buildMessage($event)),
25 | new Ring(3000, 80),
26 | $this->buildSound($event, $specialRepositories),
27 | );
28 | }
29 |
30 | /**
31 | * @param array $event
32 | */
33 | private function buildUrl(array $event): string
34 | {
35 | return 'https://github.com/' . $event['repo']['name'];
36 | }
37 |
38 | /**
39 | * @param array $event
40 | */
41 | private function buildPayload(array $event): string
42 | {
43 | if (isset($event['payload']['commits'][0]['message'])) {
44 | return $event['payload']['commits'][0]['message'];
45 | }
46 |
47 | return 'https://github.com/' . $event['actor']['login'];
48 | }
49 |
50 | /**
51 | * @param array $event
52 | */
53 | private function buildMessage(array $event): string
54 | {
55 | return \sprintf('%s pushed to %s', $event['actor']['login'], $event['repo']['name']);
56 | }
57 |
58 | /**
59 | * @param array $event
60 | * @param array $specialRepositories
61 | */
62 | private function buildSound(array $event, array $specialRepositories): BaseSound
63 | {
64 | if (in_array($event['repo']['name'], $specialRepositories, true)) {
65 | return new CelestaEgg();
66 | }
67 |
68 | return new Celesta(\strlen($this->buildPayload($event)) * 1.1);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Event/GitHub/WatchEvent.php:
--------------------------------------------------------------------------------
1 | $event
17 | * @param array $specialRepositories
18 | */
19 | public function __construct(array $event, array $specialRepositories)
20 | {
21 | parent::__construct(
22 | (int) $event['id'],
23 | new Type(Type::STARTED_WATCHING),
24 | new Information($this->buildUrl($event), $this->buildPayload(), $this->buildMessage($event)),
25 | new Ring(3000, 80),
26 | $this->buildSound($event, $specialRepositories),
27 | );
28 | }
29 |
30 | /**
31 | * @param array $event
32 | */
33 | private function buildUrl(array $event): string
34 | {
35 | return 'https://github.com/' . $event['repo']['name'];
36 | }
37 |
38 | /**
39 | * @param array $event
40 | */
41 | private function buildPayload(): string
42 | {
43 | return 'not sure if stupid but works anyway';
44 | }
45 |
46 | /**
47 | * @param array $event
48 | */
49 | private function buildMessage(array $event): string
50 | {
51 | return \sprintf('%s watched %s', $event['actor']['login'], $event['repo']['name']);
52 | }
53 |
54 | /**
55 | * @param array $event
56 | * @param array $specialRepositories
57 | */
58 | private function buildSound(array $event, array $specialRepositories): BaseSound
59 | {
60 | if (in_array($event['repo']['name'], $specialRepositories, true)) {
61 | return new SwellEgg();
62 | }
63 |
64 | return new Swell();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Exception/DecodingFailed.php:
--------------------------------------------------------------------------------
1 | token = $token;
12 | }
13 |
14 | public function getAuthenticationString(): string
15 | {
16 | return $this->token;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Log/LoggerFactory.php:
--------------------------------------------------------------------------------
1 | getResource(), $logLevel);
15 | $logHandler->setFormatter(new ConsoleFormatter());
16 |
17 | $logger = new Logger('gitamp');
18 | $logger->pushHandler($logHandler);
19 |
20 | return $logger;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Presentation/Information.php:
--------------------------------------------------------------------------------
1 | url = $url;
16 | $this->payload = $payload;
17 | $this->message = \ucfirst($message);
18 | }
19 |
20 | public function getAsArray(): array
21 | {
22 | return [
23 | 'url' => $this->url,
24 | 'payload' => $this->payload,
25 | 'message' => $this->message,
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Presentation/Ring.php:
--------------------------------------------------------------------------------
1 | animationDuration = $animationDuration;
14 | $this->radius = $radius;
15 | }
16 |
17 | public function getAsArray(): array
18 | {
19 | return [
20 | 'animationDuration' => $this->animationDuration,
21 | 'radius' => $this->radius,
22 | ];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Presentation/Sound/BaseSound.php:
--------------------------------------------------------------------------------
1 | size = $size;
12 | }
13 |
14 | abstract public function getAsArray(): array;
15 | }
16 |
--------------------------------------------------------------------------------
/src/Presentation/Sound/Celesta.php:
--------------------------------------------------------------------------------
1 | $this->size,
11 | 'type' => 'Celesta',
12 | ];
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Presentation/Sound/CelestaEgg.php:
--------------------------------------------------------------------------------
1 | $this->size,
11 | 'type' => 'CelestaEgg',
12 | ];
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Presentation/Sound/Clav.php:
--------------------------------------------------------------------------------
1 | $this->size,
11 | 'type' => 'Clav',
12 | ];
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Presentation/Sound/ClavEgg.php:
--------------------------------------------------------------------------------
1 | $this->size,
11 | 'type' => 'ClavEgg',
12 | ];
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Presentation/Sound/Swell.php:
--------------------------------------------------------------------------------
1 | $this->size,
11 | 'type' => 'Swell',
12 | ];
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Presentation/Sound/SwellEgg.php:
--------------------------------------------------------------------------------
1 | $this->size,
11 | 'type' => 'SwellEgg',
12 | ];
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Presentation/Type.php:
--------------------------------------------------------------------------------
1 | type = $type;
20 | }
21 |
22 | public function getValue(): int
23 | {
24 | return $this->type;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Provider/GitHub.php:
--------------------------------------------------------------------------------
1 | client = $client;
38 | $this->credentials = $credentials;
39 | $this->resultFactory = $resultFactory;
40 | $this->logger = $logger;
41 | }
42 |
43 | private function request(): Promise
44 | {
45 | return call(function () {
46 | try {
47 | $request = new Request(self::API_ENDPOINT, 'GET');
48 |
49 | $request->setHeaders($this->getAuthHeader());
50 |
51 | $response = yield $this->client->request($request, new NullCancellationToken());
52 | } catch (\Throwable $e) {
53 | $this->logger->error('Failed to send GET request to API endpoint', ['exception' => $e]);
54 |
55 | throw new RequestFailed('Failed to send GET request to API endpoint', $e->getCode(), $e);
56 | }
57 |
58 | /** @var Response $result */
59 | if ($response->getStatus() !== 200) {
60 | $message = \sprintf(
61 | 'A non-200 response status (%s - %s) was encountered',
62 | $response->getStatus(),
63 | $response->getReason(),
64 | );
65 |
66 | $this->logger->critical($message, ['response' => $response]);
67 |
68 | throw new RequestFailed($message);
69 | }
70 |
71 | return $response;
72 | });
73 | }
74 |
75 | public function listen(): Promise
76 | {
77 | return call(function () {
78 | $response = yield $this->request();
79 |
80 | return yield $this->resultFactory->buildFromResponse(self::EVENT_NAMESPACE, $response);
81 | });
82 | }
83 |
84 | private function getAuthHeader(): array
85 | {
86 | return [
87 | 'Accept' => \sprintf('application/vnd.github.%s+json', self::API_VERSION),
88 | 'Authorization' => \sprintf('Bearer %s', $this->credentials->getAuthenticationString()),
89 | ];
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Provider/Listener.php:
--------------------------------------------------------------------------------
1 | eventFactory = $eventFactory;
20 | $this->logger = $logger;
21 | }
22 |
23 | public function build(): Results
24 | {
25 | return new Results($this->eventFactory, $this->logger);
26 | }
27 |
28 | public function buildFromResponse(string $eventNamespace, Response $response): Promise
29 | {
30 | return call(function() use ($eventNamespace, $response) {
31 | $results = $this->build();
32 |
33 | yield $results->appendResponse($eventNamespace, $response);
34 |
35 | return $results;
36 | });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Response/Results.php:
--------------------------------------------------------------------------------
1 | eventFactory = $eventFactory;
25 | $this->logger = $logger;
26 | }
27 |
28 | /**
29 | * @return Promise
30 | */
31 | public function appendResponse(string $eventNamespace, Response $response): Promise
32 | {
33 | return call(function () use ($eventNamespace, $response) {
34 | try {
35 | $bufferedResponse = yield $response->getBody()->buffer();
36 |
37 | $events = \json_try_decode($bufferedResponse, true);
38 | } catch (DecodeErrorException $e) {
39 | $this->logger->emergency('Failed to decode response body as JSON', ['exception' => $e]);
40 |
41 | throw new DecodingFailed('Failed to decode response body as JSON', $e->getCode(), $e);
42 | }
43 |
44 | foreach ($events as $event) {
45 | $this->appendEvent($eventNamespace, $event);
46 | }
47 | });
48 | }
49 |
50 | private function appendEvent(string $eventNamespace, array $event): void
51 | {
52 | try {
53 | $this->events[] = $this->eventFactory->build($eventNamespace, $event);
54 | } catch (UnknownEvent $e) {
55 | //$this->logger->debug('Unknown event encountered', ['exception' => $e]);
56 | }
57 | }
58 |
59 | public function hasEvents(): bool
60 | {
61 | return (bool) \count($this->events);
62 | }
63 |
64 | public function jsonEncode(): string
65 | {
66 | $events = [];
67 |
68 | foreach ($this->events as $event) {
69 | $events[] = $event->getAsArray();
70 | }
71 |
72 | return \json_encode($events);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Server.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
29 | $this->configuration = $configuration;
30 | }
31 |
32 | public function start(): Promise
33 | {
34 | $server = new HttpServer($this->getSockets(), $this->getRouter(), $this->logger);
35 |
36 | return $server->start();
37 | }
38 |
39 | /**
40 | * @return array
41 | */
42 | private function getSockets(): array
43 | {
44 | $sockets = array_map(
45 | fn (ServerAddress $address) => SocketServer::listen($address->getUri()),
46 | $this->configuration->getServerAddresses(),
47 | );
48 |
49 | $sockets = array_merge($sockets, array_map(
50 | fn (SslServerAddress $address) => SocketServer::listen(
51 | $address->getUri(),
52 | (new BindContext())
53 | ->withTlsContext((new ServerTlsContext())->withDefaultCertificate($address->getCertificate())),
54 | ),
55 | $this->configuration->getSslServerAddresses(),
56 | ));
57 |
58 | return $sockets;
59 | }
60 |
61 | private function getRouter(): Router
62 | {
63 | $router = new Router();
64 |
65 | $router->addRoute('GET', '/ws', $this->getWebSocket());
66 | $router->setFallback(new DocumentRoot(__DIR__ . '/../public'));
67 |
68 | return $router;
69 | }
70 |
71 | private function getWebSocket(): Websocket
72 | {
73 | $eventCollectionFactory = new EventCollectionFactory(
74 | new EventFactory($this->configuration->getSpecialRepositories()),
75 | $this->logger,
76 | );
77 |
78 | $gitHubListener = new GitHub(
79 | HttpClientBuilder::buildDefault(),
80 | $this->configuration->getGithubToken(),
81 | $eventCollectionFactory,
82 | $this->logger,
83 | );
84 |
85 | $clientHandler = new Handler(
86 | $gitHubListener,
87 | $this->configuration,
88 | $eventCollectionFactory->build(),
89 | $this->logger,
90 | );
91 |
92 | return new Websocket($clientHandler);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/ServerAddress.php:
--------------------------------------------------------------------------------
1 | ipAddress = $ipAddress;
14 | $this->port = $port;
15 | }
16 |
17 | public function getUri(): string
18 | {
19 | return sprintf('%s:%d', $this->ipAddress, $this->port);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/SslServerAddress.php:
--------------------------------------------------------------------------------
1 | ipAddress = $ipAddress;
18 | $this->port = $port;
19 | $this->certificate = $certificate;
20 | }
21 |
22 | public function getUri(): string
23 | {
24 | return sprintf('%s:%d', $this->ipAddress, $this->port);
25 | }
26 |
27 | public function getCertificate(): Certificate
28 | {
29 | return $this->certificate;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Websocket/Handler.php:
--------------------------------------------------------------------------------
1 | provider = $provider;
42 | $this->configuration = $configuration;
43 | $this->lastEvents = $results;
44 | $this->logger = $logger;
45 | }
46 |
47 | public function handleHandshake(Gateway $gateway, Request $request, Response $response): Promise
48 | {
49 | if (!$this->configuration->websocketAddressExists($request->getHeader('origin'))) {
50 | return $gateway->getErrorHandler()->handleError(Status::FORBIDDEN, 'Forbidden Origin', $request);
51 | }
52 |
53 | return new Success($response);
54 | }
55 |
56 | public function handleClient(Gateway $gateway, Client $client, Request $request, Response $response): Promise
57 | {
58 | return call(function () use ($gateway, $client, $request, $response) {
59 | $client->onClose(function (Client $client, int $code, string $reason) use ($gateway) {
60 | yield $this->processDisconnectingClient($gateway, $client, $code, $reason);
61 | });
62 |
63 | $this->logger->info(
64 | \sprintf('Client %d connected. Total clients: %d', $client->getId(), count($gateway->getClients())),
65 | );
66 |
67 | yield $this->sendConnectedUsersCount(\count($gateway->getClients()));
68 |
69 | $client->send($this->lastEvents->jsonEncode());
70 |
71 | yield $client->receive();
72 | });
73 | }
74 |
75 | private function processDisconnectingClient(Gateway $gateway, Client $client, int $code, string $reason): Promise
76 | {
77 | return call(function () use ($gateway, $client, $code, $reason) {
78 | $this->logger->info(
79 | \sprintf(
80 | 'Client %d disconnected. Code: %d Reason: %s. Total clients: %d',
81 | $client->getId(),
82 | $code,
83 | $reason,
84 | count($gateway->getClients()),
85 | )
86 | );
87 |
88 | yield $this->sendConnectedUsersCount(count($gateway->getClients()));
89 | });
90 | }
91 |
92 | private function emit(Results $events): Promise
93 | {
94 | if (!$events->hasEvents()) {
95 | return new Success();
96 | }
97 |
98 | $this->lastEvents = $events;
99 |
100 | return $this->gateway->broadcast($events->jsonEncode());
101 | }
102 |
103 | private function sendConnectedUsersCount(int $count): Promise
104 | {
105 | return $this->gateway->broadcast(\json_encode(['connectedUsers' => $count]));
106 | }
107 |
108 | public function onStart(HttpServer $server, Gateway $gateway): Promise
109 | {
110 | $this->gateway = $gateway;
111 |
112 | asyncCall(function () {
113 | while (true) {
114 | $this->emit(yield $this->provider->listen());
115 |
116 | yield new Delayed(25000);
117 | }
118 | });
119 |
120 | return new Success();
121 | }
122 |
123 | public function onStop(HttpServer $server, Gateway $gateway): Promise
124 | {
125 | return new Success();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/tests/ConfigurationTest.php:
--------------------------------------------------------------------------------
1 | configuration = new Configuration(new Token('12345'));
21 | }
22 |
23 | public function testDefaultLogLevelAccessors(): void
24 | {
25 | $this->assertSame(Logger::INFO, $this->configuration->getLogLevel());
26 | }
27 |
28 | public function testLogLevelAccessors(): void
29 | {
30 | $this->configuration->setLogLevel(Logger::ALERT);
31 |
32 | $this->assertSame(Logger::ALERT, $this->configuration->getLogLevel());
33 | }
34 |
35 | public function testWebsocketAddressesAccessors(): void
36 | {
37 | $this->configuration->addWebsocketAddress(Uri::createFromString('http://example.com'));
38 | $this->configuration->addWebsocketAddress(Uri::createFromString('http://example.com:1337'));
39 |
40 | $this->assertTrue($this->configuration->websocketAddressExists('http://example.com'));
41 | $this->assertTrue($this->configuration->websocketAddressExists('http://example.com:1337'));
42 |
43 | $this->assertFalse($this->configuration->websocketAddressExists('http://xample.com'));
44 | }
45 |
46 | public function testBindAccessors(): void
47 | {
48 | $this->configuration->bind(new ServerAddress('127.0.0.1', 80));
49 | $this->configuration->bind(new ServerAddress('127.0.0.1', 8080));
50 |
51 | $this->assertCount(2, $this->configuration->getServerAddresses());
52 | }
53 |
54 | public function testBindSslAccessors(): void
55 | {
56 | $this->configuration->bindSsl(
57 | new SslServerAddress('127.0.0.1', 1338, new Certificate('/test/cert.pem')),
58 | );
59 |
60 | $this->configuration->bindSsl(
61 | new SslServerAddress('127.0.0.1', 443, new Certificate('/test/cert.pem')),
62 | );
63 |
64 | $this->assertCount(2, $this->configuration->getSslServerAddresses());
65 | }
66 |
67 | public function testGetGithubToken(): void
68 | {
69 | $this->assertSame('12345', $this->configuration->getGithubToken()->getAuthenticationString());
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Data/invalid.json:
--------------------------------------------------------------------------------
1 | fooo
2 |
--------------------------------------------------------------------------------
/tests/Event/BaseEventTest.php:
--------------------------------------------------------------------------------
1 | 1,
18 | 'type' => 1,
19 | 'information' => [
20 | 'url' => 'url',
21 | 'payload' => 'payload',
22 | 'message' => 'Message',
23 | ],
24 | 'ring' => [
25 | 'animationDuration' => 3000,
26 | 'radius' => 80,
27 | ],
28 | 'sound' => [
29 | 'size' => 1.0,
30 | 'type' => 'Swell',
31 | ],
32 | ];
33 |
34 | $event = new class(
35 | $data['id'],
36 | new Type($data['type']),
37 | new Information(
38 | $data['information']['url'],
39 | $data['information']['payload'],
40 | $data['information']['message']
41 | ),
42 | new Ring(3000, 80),
43 | new Swell()
44 | ) extends BaseEvent {};
45 |
46 | $this->assertSame($data, $event->getAsArray());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Event/FactoryTest.php:
--------------------------------------------------------------------------------
1 | expectException(UnknownEvent::class);
15 |
16 | (new Factory([]))->build('Foo', ['type' => 'UnknownEvent']);
17 | }
18 |
19 | public function testBuildReturnsEvent(): void
20 | {
21 | $event = [
22 | 'id' => '5103197839',
23 | 'type' => 'CreateEvent',
24 | 'repo' => ['name' => 'ekinhbayar/gitamp'],
25 | 'actor' => ['login' => 'PeeHaa'],
26 | ];
27 |
28 | $this->assertInstanceOf(CreateEvent::class, (new Factory([]))->build('ekinhbayar\GitAmp\Event\GitHub', $event));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Event/GitHub/CreateEventTest.php:
--------------------------------------------------------------------------------
1 | event = [
17 | 'id' => 1,
18 | 'repo' => ['name' => 'test/repo'],
19 | 'actor' => ['login' => 'PeeHaa'],
20 | 'payload' => ['description' => 'The description'],
21 | ];
22 |
23 | $this->assertEvent = [
24 | 'id' => 1,
25 | 'type' => 6,
26 | 'information' => [
27 | 'url' => 'https://github.com/test/repo',
28 | 'payload' => 'The description',
29 | 'message' => 'PeeHaa created test/repo',
30 | ],
31 | 'ring' => [
32 | 'animationDuration' => 3000,
33 | 'radius' => 80,
34 | ],
35 | 'sound' => [
36 | 'size' => 1.0,
37 | 'type' => 'Swell',
38 | ],
39 | ];
40 | }
41 |
42 | public function testGetAsArrayWithPayloadDescription(): void
43 | {
44 | $event = new CreateEvent($this->event, []);
45 |
46 | $this->assertSame($this->assertEvent, $event->getAsArray());
47 | }
48 |
49 | public function testGetAsArrayWithoutPayloadDescription(): void
50 | {
51 | unset($this->event['payload']['description']);
52 |
53 | $this->assertEvent['information']['payload'] = $this->assertEvent['information']['url'];
54 |
55 | $event = new CreateEvent($this->event, ['ekinhbayar/gitamp']);
56 |
57 | $this->assertSame($this->assertEvent, $event->getAsArray());
58 | }
59 |
60 | public function testGetAsArrayWithPayloadDescriptionEgg(): void
61 | {
62 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
63 |
64 | $this->assertEvent['information']['url'] = 'https://github.com/ekinhbayar/gitamp';
65 | $this->assertEvent['information']['message'] = 'PeeHaa created ekinhbayar/gitamp';
66 | $this->assertEvent['sound']['type'] = 'SwellEgg';
67 |
68 | $event = new CreateEvent($this->event, ['ekinhbayar/gitamp']);
69 |
70 | $this->assertSame($this->assertEvent, $event->getAsArray());
71 | }
72 |
73 | public function testGetAsArrayWithoutPayloadDescriptionEgg(): void
74 | {
75 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
76 |
77 | $this->assertEvent['information']['url'] = 'https://github.com/ekinhbayar/gitamp';
78 | $this->assertEvent['information']['message'] = 'PeeHaa created ekinhbayar/gitamp';
79 | $this->assertEvent['sound']['type'] = 'SwellEgg';
80 |
81 | unset($this->event['payload']['description']);
82 |
83 | $this->assertEvent['information']['payload'] = $this->assertEvent['information']['url'];
84 |
85 | $event = new CreateEvent($this->event, ['ekinhbayar/gitamp']);
86 |
87 | $this->assertSame($this->assertEvent, $event->getAsArray());
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Event/GitHub/ForkEventTest.php:
--------------------------------------------------------------------------------
1 | event = [
17 | 'id' => 1,
18 | 'repo' => ['name' => 'test/repo'],
19 | 'actor' => ['login' => 'PeeHaa'],
20 | ];
21 |
22 | $this->assertEvent = [
23 | 'id' => 1,
24 | 'type' => 5,
25 | 'information' => [
26 | 'url' => 'https://github.com/test/repo',
27 | 'payload' => 'not sure if stupid but works anyway',
28 | 'message' => 'PeeHaa forked test/repo',
29 | ],
30 | 'ring' => [
31 | 'animationDuration' => 3000,
32 | 'radius' => 80,
33 | ],
34 | 'sound' => [
35 | 'size' => 1.0,
36 | 'type' => 'Swell',
37 | ],
38 | ];
39 | }
40 |
41 | public function testGetAsArray(): void
42 | {
43 | $event = new ForkEvent($this->event, []);
44 |
45 | $this->assertSame($this->assertEvent, $event->getAsArray());
46 | }
47 |
48 | public function testGetAsArrayEgg(): void
49 | {
50 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
51 |
52 | $this->assertEvent['information']['url'] = 'https://github.com/ekinhbayar/gitamp';
53 | $this->assertEvent['information']['message'] = 'PeeHaa forked ekinhbayar/gitamp';
54 | $this->assertEvent['sound']['type'] = 'SwellEgg';
55 |
56 | $event = new ForkEvent($this->event, ['ekinhbayar/gitamp']);
57 |
58 | $this->assertSame($this->assertEvent, $event->getAsArray());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/Event/GitHub/IssueCommentEventTest.php:
--------------------------------------------------------------------------------
1 | event = [
17 | 'id' => 1,
18 | 'repo' => ['name' => 'test/repo'],
19 | 'actor' => ['login' => 'PeeHaa'],
20 | 'payload' => [
21 | 'action' => 'The action',
22 | 'issue' => ['html_url' => 'http://example.com', 'title' => 'Issue title'],
23 | ],
24 | 'comment' => ['body' => 'Comment body'],
25 | ];
26 |
27 | $this->assertEvent = [
28 | 'id' => 1,
29 | 'type' => 4,
30 | 'information' => [
31 | 'url' => 'http://example.com',
32 | 'payload' => 'Comment body',
33 | 'message' => 'PeeHaa commented in test/repo',
34 | ],
35 | 'ring' => [
36 | 'animationDuration' => 3000,
37 | 'radius' => 80,
38 | ],
39 | 'sound' => [
40 | 'size' => strlen('Comment body') * 1.1,
41 | 'type' => 'Clav',
42 | ],
43 | ];
44 | }
45 |
46 | public function testGetAsArrayWithCommentBody(): void
47 | {
48 | $event = new IssueCommentEvent($this->event, []);
49 |
50 | $this->assertSame($this->assertEvent, $event->getAsArray());
51 | }
52 |
53 | public function testGetAsArrayWithoutCommentBody(): void
54 | {
55 | unset($this->event['comment']['body']);
56 |
57 | $this->assertEvent['information']['payload'] = 'Issue title';
58 | $this->assertEvent['sound']['size'] = strlen('Issue title') * 1.1;
59 |
60 | $event = new IssueCommentEvent($this->event, ['ekinhbayar/gitamp']);
61 |
62 | $this->assertSame($this->assertEvent, $event->getAsArray());
63 | }
64 |
65 | public function testGetAsArrayWithCommentBodyEgg(): void
66 | {
67 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
68 |
69 | $this->assertEvent['information']['url'] = 'http://example.com';
70 | $this->assertEvent['information']['message'] = 'PeeHaa commented in ekinhbayar/gitamp';
71 | $this->assertEvent['sound']['type'] = 'ClavEgg';
72 | $this->assertEvent['sound']['size'] = 1.0;
73 |
74 | $event = new IssueCommentEvent($this->event, ['ekinhbayar/gitamp']);
75 |
76 | $this->assertSame($this->assertEvent, $event->getAsArray());
77 | }
78 |
79 | public function testGetAsArrayWithoutCommentBodyEgg(): void
80 | {
81 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
82 |
83 | $this->assertEvent['information']['url'] = 'http://example.com';
84 | $this->assertEvent['information']['message'] = 'PeeHaa commented in ekinhbayar/gitamp';
85 | $this->assertEvent['sound']['type'] = 'ClavEgg';
86 | $this->assertEvent['sound']['size'] = 1.0;
87 |
88 | unset($this->event['comment']['body']);
89 |
90 | $this->assertEvent['information']['payload'] = 'Issue title';
91 |
92 | $event = new IssueCommentEvent($this->event, ['ekinhbayar/gitamp']);
93 |
94 | $this->assertSame($this->assertEvent, $event->getAsArray());
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/Event/GitHub/IssuesEventTest.php:
--------------------------------------------------------------------------------
1 | event = [
17 | 'id' => 1,
18 | 'repo' => ['name' => 'test/repo'],
19 | 'actor' => ['login' => 'PeeHaa'],
20 | 'payload' => [
21 | 'action' => 'The action',
22 | 'issue' => ['html_url' => 'http://example.com', 'title' => 'Issue title'],
23 | ],
24 | ];
25 |
26 | $this->assertEvent = [
27 | 'id' => 1,
28 | 'type' => 3,
29 | 'information' => [
30 | 'url' => 'http://example.com',
31 | 'payload' => 'Issue title',
32 | 'message' => 'PeeHaa The action an issue in test/repo',
33 | ],
34 | 'ring' => [
35 | 'animationDuration' => 3000,
36 | 'radius' => 80,
37 | ],
38 | 'sound' => [
39 | 'size' => strlen('Issue title') * 1.1,
40 | 'type' => 'Clav',
41 | ],
42 | ];
43 | }
44 |
45 | public function testGetAsArray(): void
46 | {
47 | $event = new IssuesEvent($this->event, []);
48 |
49 | $this->assertSame($this->assertEvent, $event->getAsArray());
50 | }
51 |
52 | public function testGetAsArrayEgg(): void
53 | {
54 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
55 |
56 | $this->assertEvent['information']['url'] = 'http://example.com';
57 | $this->assertEvent['information']['message'] = 'PeeHaa The action an issue in ekinhbayar/gitamp';
58 | $this->assertEvent['sound']['type'] = 'ClavEgg';
59 | $this->assertEvent['sound']['size'] = 1.0;
60 |
61 | $event = new IssuesEvent($this->event, ['ekinhbayar/gitamp']);
62 |
63 | $this->assertSame($this->assertEvent, $event->getAsArray());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Event/GitHub/PullRequestEventTest.php:
--------------------------------------------------------------------------------
1 | event = [
17 | 'id' => 1,
18 | 'repo' => ['name' => 'test/repo'],
19 | 'actor' => ['login' => 'PeeHaa'],
20 | 'payload' => [
21 | 'action' => 'The action',
22 | 'pull_request' => ['html_url' => 'http://example.com', 'title' => 'PR title'],
23 | ],
24 | ];
25 |
26 | $this->assertEvent = [
27 | 'id' => 1,
28 | 'type' => 2,
29 | 'information' => [
30 | 'url' => 'http://example.com',
31 | 'payload' => 'PR title',
32 | 'message' => 'PeeHaa The action a PR for test/repo',
33 | ],
34 | 'ring' => [
35 | 'animationDuration' => 10000,
36 | 'radius' => 600,
37 | ],
38 | 'sound' => [
39 | 'size' => 1.0,
40 | 'type' => 'Swell',
41 | ],
42 | ];
43 | }
44 |
45 | public function testGetAsArray(): void
46 | {
47 | $event = new PullRequestEvent($this->event, []);
48 |
49 | $this->assertSame($this->assertEvent, $event->getAsArray());
50 | }
51 |
52 | public function testGetAsArrayEgg(): void
53 | {
54 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
55 |
56 | $this->assertEvent['information']['url'] = 'http://example.com';
57 | $this->assertEvent['information']['message'] = 'PeeHaa The action a PR for ekinhbayar/gitamp';
58 | $this->assertEvent['sound']['type'] = 'SwellEgg';
59 |
60 | $event = new PullRequestEvent($this->event, ['ekinhbayar/gitamp']);
61 |
62 | $this->assertSame($this->assertEvent, $event->getAsArray());
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/Event/GitHub/PushEventTest.php:
--------------------------------------------------------------------------------
1 | event = [
17 | 'id' => 1,
18 | 'repo' => ['name' => 'test/repo'],
19 | 'actor' => ['login' => 'PeeHaa'],
20 | 'payload' => [
21 | 'commits' => [['message' => 'Commit message']],
22 | ],
23 | ];
24 |
25 | $this->assertEvent = [
26 | 'id' => 1,
27 | 'type' => 1,
28 | 'information' => [
29 | 'url' => 'https://github.com/test/repo',
30 | 'payload' => 'Commit message',
31 | 'message' => 'PeeHaa pushed to test/repo',
32 | ],
33 | 'ring' => [
34 | 'animationDuration' => 3000,
35 | 'radius' => 80,
36 | ],
37 | 'sound' => [
38 | 'size' => strlen('Commit message') * 1.1,
39 | 'type' => 'Celesta',
40 | ],
41 | ];
42 | }
43 |
44 | public function testGetAsArrayWithCommitMessage(): void
45 | {
46 | $event = new PushEvent($this->event, []);
47 |
48 | $this->assertSame($this->assertEvent, $event->getAsArray());
49 | }
50 |
51 | public function testGetAsArrayWithoutCommitMessage(): void
52 | {
53 | unset($this->event['payload']['commits'][0]['message']);
54 |
55 | $this->assertEvent['information']['payload'] = 'https://github.com/PeeHaa';
56 | $this->assertEvent['sound']['size'] = strlen('https://github.com/PeeHaa') * 1.1;
57 |
58 | $event = new PushEvent($this->event, ['ekinhbayar/gitamp']);
59 |
60 | $this->assertSame($this->assertEvent, $event->getAsArray());
61 | }
62 |
63 | public function testGetAsArrayWithCommitMessageEgg(): void
64 | {
65 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
66 |
67 | $this->assertEvent['information']['url'] = 'https://github.com/ekinhbayar/gitamp';
68 | $this->assertEvent['information']['message'] = 'PeeHaa pushed to ekinhbayar/gitamp';
69 | $this->assertEvent['sound']['type'] = 'CelestaEgg';
70 | $this->assertEvent['sound']['size'] = 1.0;
71 |
72 | $event = new PushEvent($this->event, ['ekinhbayar/gitamp']);
73 |
74 | $this->assertSame($this->assertEvent, $event->getAsArray());
75 | }
76 |
77 | public function testGetAsArrayWithoutCommitMessageEgg(): void
78 | {
79 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
80 |
81 | $this->assertEvent['information']['url'] = 'https://github.com/ekinhbayar/gitamp';
82 | $this->assertEvent['information']['message'] = 'PeeHaa pushed to ekinhbayar/gitamp';
83 | $this->assertEvent['sound']['type'] = 'CelestaEgg';
84 |
85 | unset($this->event['payload']['commits'][0]['message']);
86 |
87 | $this->assertEvent['information']['payload'] = 'https://github.com/PeeHaa';
88 | $this->assertEvent['sound']['size'] = 1.0;
89 |
90 | $event = new PushEvent($this->event, ['ekinhbayar/gitamp']);
91 |
92 | $this->assertSame($this->assertEvent, $event->getAsArray());
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Event/GitHub/WatchEventTest.php:
--------------------------------------------------------------------------------
1 | event = [
17 | 'id' => 1,
18 | 'repo' => ['name' => 'test/repo'],
19 | 'actor' => ['login' => 'PeeHaa'],
20 | 'payload' => ['action' => 'The action'],
21 | ];
22 |
23 | $this->assertEvent = [
24 | 'id' => 1,
25 | 'type' => 7,
26 | 'information' => [
27 | 'url' => 'https://github.com/test/repo',
28 | 'payload' => 'not sure if stupid but works anyway',
29 | 'message' => 'PeeHaa watched test/repo',
30 | ],
31 | 'ring' => [
32 | 'animationDuration' => 3000,
33 | 'radius' => 80,
34 | ],
35 | 'sound' => [
36 | 'size' => 1.0,
37 | 'type' => 'Swell',
38 | ],
39 | ];
40 | }
41 |
42 | public function testGetAsArray(): void
43 | {
44 | $event = new WatchEvent($this->event, []);
45 |
46 | $this->assertSame($this->assertEvent, $event->getAsArray());
47 | }
48 |
49 | public function testGetAsArrayEgg(): void
50 | {
51 | $this->event['repo']['name'] = 'ekinhbayar/gitamp';
52 |
53 | $this->assertEvent['information']['url'] = 'https://github.com/ekinhbayar/gitamp';
54 | $this->assertEvent['information']['message'] = 'PeeHaa watched ekinhbayar/gitamp';
55 | $this->assertEvent['sound']['type'] = 'SwellEgg';
56 |
57 | $event = new WatchEvent($this->event, ['ekinhbayar/gitamp']);
58 |
59 | $this->assertSame($this->assertEvent, $event->getAsArray());
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Exception/UnknownEventTest.php:
--------------------------------------------------------------------------------
1 | assertSame(
13 | 'Unknown event (EventName) encountered',
14 | (new UnknownEvent('EventName'))->getMessage(),
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/Fakes/HttpClient/MockFailedResponseInterceptor.php:
--------------------------------------------------------------------------------
1 | body = $body;
21 | }
22 |
23 | /**
24 | * phpcs:disable SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
25 | *
26 | * @return Promise
27 | */
28 | public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $client): Promise
29 | {
30 | // phpcs:enable SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
31 | $body = new InMemoryStream($this->body);
32 |
33 | return new Success(new Response('2', 403, 'Forbidden Origin', [], $body, $request));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Fakes/HttpClient/MockSuccessfulResponseInterceptor.php:
--------------------------------------------------------------------------------
1 | body = $body;
21 | }
22 |
23 | /**
24 | * phpcs:disable SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
25 | *
26 | * @return Promise
27 | */
28 | public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $client): Promise
29 | {
30 | // phpcs:enable SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
31 | $body = new InMemoryStream($this->body);
32 |
33 | return new Success(new Response('2', 200, 'OK', [], $body, $request));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Fakes/HttpClient/MockThrowingResponseInterceptor.php:
--------------------------------------------------------------------------------
1 | body = $body;
21 | }
22 |
23 | /**
24 | * phpcs:disable SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
25 | *
26 | * @return Promise
27 | */
28 | public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $client): Promise
29 | {
30 | // phpcs:enable SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
31 | $body = new InMemoryStream($this->body);
32 |
33 | return new Success(new \Exception('this is fine'));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Github/TokenTest.php:
--------------------------------------------------------------------------------
1 | assertSame('gitamptoken', (new Token('gitamptoken'))->getAuthenticationString());
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/Log/LoggerFactoryTest.php:
--------------------------------------------------------------------------------
1 | logger = (new LoggerFactory())->build(Logger::INFO);
16 | }
17 |
18 | public function testBuildSetsName(): void
19 | {
20 | $this->assertSame('gitamp', $this->logger->getName());
21 | }
22 |
23 | public function testBuildSetsLogLevel(): void
24 | {
25 | $this->assertTrue($this->logger->isHandling(Logger::INFO));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Provider/GitHubTest.php:
--------------------------------------------------------------------------------
1 | credentials = new Token('token');
31 | $this->factory = $this->createMock(Factory::class);
32 | $this->logger = $this->createMock(LoggerInterface::class);
33 | }
34 |
35 | public function testListenThrowsOnFailedRequest(): void
36 | {
37 | $httpClient = (new HttpClientBuilder())
38 | ->intercept(
39 | new MockThrowingResponseInterceptor(file_get_contents(TEST_DATA_DIR . '/invalid.json')),
40 | )->build()
41 | ;
42 |
43 | $gitamp = new GitHub($httpClient, $this->credentials, $this->factory, $this->logger);
44 |
45 | $this->expectException(RequestFailed::class);
46 | $this->expectExceptionMessage('Failed to send GET request to API endpoint');
47 |
48 | wait($gitamp->listen());
49 | }
50 |
51 | public function testListenThrowsOnNonOkResponse(): void
52 | {
53 | $httpClient = (new HttpClientBuilder())
54 | ->intercept(
55 | new MockFailedResponseInterceptor(file_get_contents(TEST_DATA_DIR . '/invalid.json')),
56 | )->build()
57 | ;
58 |
59 | $gitamp = new GitHub($httpClient, $this->credentials, $this->factory, $this->logger);
60 |
61 | $this->expectException(RequestFailed::class);
62 | $this->expectExceptionMessage('A non-200 response status (403 - Forbidden Origin) was encountered');
63 |
64 | wait($gitamp->listen());
65 | }
66 |
67 | public function testListenReturnsPromise(): void
68 | {
69 | $httpClient = (new HttpClientBuilder())
70 | ->intercept(
71 | new MockFailedResponseInterceptor(file_get_contents(TEST_DATA_DIR . '/valid.json')),
72 | )->build()
73 | ;
74 |
75 | $this->assertInstanceOf(
76 | Promise::class,
77 | (new GitHub($httpClient, $this->credentials, $this->factory, $this->logger))->listen()
78 | );
79 | }
80 |
81 | public function testListenReturnsResults(): void
82 | {
83 | $httpClient = (new HttpClientBuilder())
84 | ->intercept(
85 | new MockSuccessfulResponseInterceptor(file_get_contents(TEST_DATA_DIR . '/valid.json')),
86 | )->build()
87 | ;
88 |
89 | $this->factory
90 | ->expects($this->once())
91 | ->method('buildFromResponse')
92 | ->will($this->returnValue(new Success($this->createMock(Results::class))))
93 | ;
94 |
95 | $this->assertInstanceOf(
96 | Results::class,
97 | wait((new GitHub($httpClient, $this->credentials, $this->factory, $this->logger))->listen())
98 | );
99 | }
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/tests/Response/FactoryTest.php:
--------------------------------------------------------------------------------
1 | 1,
24 | 'type' => 'InvalidEvent',
25 | 'action' => 'created',
26 | 'repoName' => 'test/repo',
27 | 'actorName' => 'PeeHaa',
28 | 'eventUrl' => 'https://github.com/test/repo',
29 | 'message' => 'The description',
30 | ],
31 | ]);
32 |
33 | $inputStream = $this->createMock(InputStream::class);
34 | $inputStream
35 | ->expects($this->exactly(2))
36 | ->method('read')
37 | ->willReturnOnConsecutiveCalls(new Success($events), new Success(null))
38 | ;
39 |
40 | $message = new Payload($inputStream);
41 |
42 | $response = new Response('2', 200, 'OK', [], $message, new Request('foo'));
43 |
44 | $logger = $this->createMock(LoggerInterface::class);
45 |
46 | $factory = new Factory(new EventFactory([]), $logger);
47 |
48 | $results = wait($factory->buildFromResponse('ekinhbayar\GitAmp\Event\GitHub', $response));
49 |
50 | $this->assertInstanceOf(Results::class, $results);
51 | }
52 |
53 | public function testBuildReturnsResult(): void
54 | {
55 | $events = json_encode([
56 | [
57 | 'id' => 1,
58 | 'type' => 'CreateEvent',
59 | 'action' => 'created',
60 | 'repo' => ['name' => 'test/repo'],
61 | 'actor' => ['login' => 'PeeHaa'],
62 | 'eventUrl' => 'https://github.com/test/repo',
63 | 'message' => 'The description',
64 | ],
65 | ]);
66 |
67 | $inputStream = $this->createMock(InputStream::class);
68 | $inputStream
69 | ->expects($this->exactly(2))
70 | ->method('read')
71 | ->willReturnOnConsecutiveCalls(new Success($events), new Success(null))
72 | ;
73 |
74 | $message = new Payload($inputStream);
75 |
76 | $response = new Response('2', 200, 'OK', [], $message, new Request('foo'));
77 |
78 | $logger = $this->createMock(LoggerInterface::class);
79 |
80 | $results = (new Factory(new EventFactory([]), $logger))
81 | ->buildFromResponse('ekinhbayar\GitAmp\Event\GitHub', $response);
82 |
83 | $this->assertInstanceOf(Results::class, wait($results));
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/tests/Response/ResultsTest.php:
--------------------------------------------------------------------------------
1 | eventData = '[
28 | {
29 | "type": "Event",
30 | "public": true,
31 | "payload": {
32 | },
33 | "repo": {
34 | "id": 3,
35 | "name": "octocat/Hello-World",
36 | "url": "https://api.github.com/repos/octocat/Hello-World"
37 | },
38 | "actor": {
39 | "id": 1,
40 | "login": "octocat",
41 | "gravatar_id": "",
42 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
43 | "url": "https://api.github.com/users/octocat"
44 | },
45 | "org": {
46 | "id": 1,
47 | "login": "github",
48 | "gravatar_id": "",
49 | "url": "https://api.github.com/orgs/github",
50 | "avatar_url": "https://github.com/images/error/octocat_happy.gif"
51 | },
52 | "created_at": "2011-09-06T17:26:27Z",
53 | "id": "12345"
54 | }
55 | ]';
56 |
57 | $this->logger = $this->createMock(LoggerInterface::class);
58 | }
59 |
60 | public function testAppendResponseThrowsOnInvalidJSON(): void
61 | {
62 | $inputStream = $this->createMock(InputStream::class);
63 |
64 | $inputStream
65 | ->expects($this->exactly(2))
66 | ->method('read')
67 | ->willReturnOnConsecutiveCalls(new Success('[{"message"}]'), new Success(null))
68 | ;
69 |
70 | $message = new Payload($inputStream);
71 |
72 | $response = new Response('2', 200, 'OK', [], $message, new Request('foo'));
73 |
74 | $this->expectException(DecodingFailed::class);
75 | $this->expectExceptionMessage('Failed to decode response body as JSON');
76 |
77 | Loop::run(function () use ($response) {
78 | yield (new Results(new Factory([]), $this->logger))->appendResponse('Foo', $response);
79 | });
80 | }
81 |
82 | public function testAppendResponseUnknownEventExceptionDoesNotBubbleUp(): void
83 | {
84 | $inputStream = $this->createMock(InputStream::class);
85 |
86 | $inputStream
87 | ->expects($this->exactly(2))
88 | ->method('read')
89 | ->willReturnOnConsecutiveCalls(new Success($this->eventData), new Success(null))
90 | ;
91 |
92 | $message = new Payload($inputStream);
93 |
94 | $response = new Response('2', 200, 'OK', [], $message, new Request('foo'));
95 |
96 | $eventFactory = $this->createMock(Factory::class);
97 |
98 | $eventFactory
99 | ->expects($this->once())
100 | ->method('build')
101 | ->willThrowException(new UnknownEvent('EventName'))
102 | ;
103 |
104 | Loop::run(function () use ($eventFactory, $response) {
105 | yield (new Results($eventFactory, $this->logger))
106 | ->appendResponse('EventName', $response);
107 | });
108 | }
109 |
110 | public function testHasEventsFalse(): void
111 | {
112 | $results = new Results(new Factory([]), $this->logger);
113 |
114 | $this->assertFalse($results->hasEvents());
115 | }
116 |
117 | public function testHasEventsTrue(): void
118 | {
119 | $inputStream = $this->createMock(InputStream::class);
120 |
121 | $inputStream
122 | ->expects($this->exactly(2))
123 | ->method('read')
124 | ->willReturnOnConsecutiveCalls(new Success($this->eventData), new Success(null))
125 | ;
126 |
127 | $message = new Payload($inputStream);
128 |
129 | $response = new Response('2', 200, 'OK', [], $message, new Request('foo'));
130 |
131 | $eventFactory = $this->createMock(Factory::class);
132 |
133 | $eventFactory
134 | ->expects($this->once())
135 | ->method('build')
136 | ->will($this->returnValue($this->createMock(PullRequestEvent::class)))
137 | ;
138 |
139 | $results = new Results($eventFactory, $this->logger);
140 |
141 | Loop::run(function () use ($results, $response) {
142 | yield $results->appendResponse('ekinhbayar\GitAmp\Event\GitHub', $response);
143 | });
144 |
145 | $this->assertTrue($results->hasEvents());
146 | }
147 |
148 | public function testJsonEncodeWithoutEvents(): void
149 | {
150 | $results = new Results(new Factory([]), $this->logger);
151 |
152 | $this->assertSame('[]', $results->jsonEncode());
153 | }
154 |
155 | public function testJsonEncodeWithEvents(): void
156 | {
157 | $inputStream = $this->createMock(InputStream::class);
158 |
159 | $inputStream
160 | ->expects($this->exactly(2))
161 | ->method('read')
162 | ->willReturnOnConsecutiveCalls(new Success($this->eventData), new Success(null))
163 | ;
164 |
165 | $message = new Payload($inputStream);
166 |
167 | $response = new Response('2', 200, 'OK', [], $message, new Request('foo'));
168 |
169 | $event = $this->createMock(PullRequestEvent::class);
170 |
171 | $event
172 | ->expects($this->once())
173 | ->method('getAsArray')
174 | ->will($this->returnValue([
175 | 'foo' => 'bar',
176 | ]))
177 | ;
178 |
179 | $eventFactory = $this->createMock(Factory::class);
180 |
181 | $eventFactory
182 | ->expects($this->once())
183 | ->method('build')
184 | ->will($this->returnValue($event))
185 | ;
186 |
187 | $results = new Results($eventFactory, $this->logger);
188 |
189 | Loop::run(function () use ($results, $response) {
190 | yield $results->appendResponse('ekinhbayar\GitAmp\Event\GitHub', $response);
191 | });
192 |
193 | $this->assertSame('[{"foo":"bar"}]', $results->jsonEncode());
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/tests/ServerAddressTest.php:
--------------------------------------------------------------------------------
1 | assertSame('127.0.0.1:8080', (new ServerAddress('127.0.0.1', 8080))->getUri());
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/ServerTest.php:
--------------------------------------------------------------------------------
1 | expectException(RequestFailed::class);
20 | $this->expectExceptionMessage('A non-200 response status (401 - Unauthorized) was encountered');
21 |
22 | $configuration = (new Configuration(new Token('12345')))
23 | ->addWebsocketAddress(Uri::createFromString('https://gitamp.audio'))
24 | ->bind(new ServerAddress('127.0.0.1', 1337))
25 | ;
26 |
27 | Loop::run(function () use ($configuration) {
28 | $server = new Server($this->createMock(LoggerInterface::class), $configuration);
29 |
30 | yield $server->start();
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/SslServerAddressTest.php:
--------------------------------------------------------------------------------
1 | assertSame(
14 | '127.0.0.1:8080',
15 | (new SslServerAddress('127.0.0.1', 8080, new Certificate('/test/cert.pem')))->getUri(),
16 | );
17 | }
18 |
19 | public function testGetCertificate(): void
20 | {
21 | $this->assertInstanceOf(
22 | Certificate::class,
23 | (new SslServerAddress('127.0.0.1', 8080, new Certificate('/test/cert.pem')))->getCertificate(),
24 | );
25 |
26 | $this->assertSame(
27 | '/test/cert.pem',
28 | (new SslServerAddress('127.0.0.1', 8080, new Certificate('/test/cert.pem')))
29 | ->getCertificate()
30 | ->getCertFile(),
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Websocket/HandlerTest.php:
--------------------------------------------------------------------------------
1 | listener = $this->createMock(Listener::class);
45 |
46 | $configuration = (new Configuration(new Token('12345')))
47 | ->addWebsocketAddress(Uri::createFromString('https://gitamp.audio'))
48 | ->bind(new ServerAddress('127.0.0.1', 1337))
49 | ;
50 | $results = $this->createMock(Results::class);
51 |
52 | $this->logger = $this->createMock(Logger::class);
53 | $this->gateway = $this->createMock(Gateway::class);
54 |
55 | $this->httpServer = new HttpServer(
56 | [Server::listen("tcp://127.0.0.1:0")],
57 | new CallableRequestHandler(function () {
58 | yield new Delayed(1500);
59 |
60 | return new Response(Status::NO_CONTENT);
61 | }
62 | ), $this->logger);
63 |
64 | $this->handler = new Handler($this->listener, $configuration, $results, $this->logger);
65 | }
66 |
67 | public function testHandleHandshakeReturnsForbiddenOnInvalidOrigin(): void
68 | {
69 | $request = new Request(
70 | $this->createMock(Client::class),
71 | 'GET',
72 | $this->createMock(PsrUri::class),
73 | ['origin' => 'https://notgitamp.audio'],
74 | );
75 |
76 | $this->gateway
77 | ->expects($this->once())
78 | ->method('getErrorHandler')
79 | ->willReturn(new DefaultErrorHandler())
80 | ;
81 |
82 | $this->handler->handleHandshake($this->gateway, $request, new Response());
83 | }
84 |
85 | public function testHandleHandshakeReturnsSuccessfulResponseWhenOriginsMatch(): void
86 | {
87 | $request = new Request(
88 | $this->createMock(Client::class),
89 | 'GET',
90 | $this->createMock(PsrUri::class),
91 | ['origin' => 'https://gitamp.audio'],
92 | );
93 |
94 | $response = wait($this->handler->handleHandshake($this->gateway, $request, new Response()));
95 |
96 | $this->assertSame(200, $response->getStatus());
97 | }
98 |
99 | public function testOnStartEmitsWithoutEvents(): void
100 | {
101 | $results = $this->createMock(Results::class);
102 |
103 | $results
104 | ->expects($this->once())
105 | ->method('hasEvents')
106 | ->will($this->returnValue(false))
107 | ;
108 |
109 | $this->listener
110 | ->expects($this->once())
111 | ->method('listen')
112 | ->will($this->returnValue(new Success($results)))
113 | ;
114 |
115 | Loop::run(function () {
116 | yield $this->handler->onStart($this->httpServer, $this->gateway);
117 |
118 | Loop::stop();
119 | });
120 | }
121 |
122 | public function testOnStartEmitsWithEvents(): void
123 | {
124 | $results = $this->createMock(Results::class);
125 |
126 | $results
127 | ->expects($this->once())
128 | ->method('hasEvents')
129 | ->will($this->returnValue(true))
130 | ;
131 |
132 | $this->listener
133 | ->expects($this->once())
134 | ->method('listen')
135 | ->will($this->returnValue(new Success($results)))
136 | ;
137 |
138 | $this->gateway
139 | ->expects($this->once())
140 | ->method('broadcast')
141 | ;
142 |
143 | Loop::run(function () {
144 | yield $this->handler->onStart($this->httpServer, $this->gateway);
145 |
146 | Loop::stop();
147 | });
148 | }
149 |
150 | public function testHandleClientWithoutExistingEvents(): void
151 | {
152 | $results = $this->createMock(Results::class);
153 |
154 | $results
155 | ->expects($this->once())
156 | ->method('hasEvents')
157 | ->willReturn(false)
158 | ;
159 |
160 | $this->listener
161 | ->expects($this->once())
162 | ->method('listen')
163 | ->willReturn(new Success($results))
164 | ;
165 |
166 | $this->gateway
167 | ->expects($this->once())
168 | ->method('broadcast')
169 | ;
170 |
171 | $websocketClient = $this->createMock(WebsocketClient::class);
172 |
173 | $websocketClient
174 | ->expects($this->never())
175 | ->method('send')
176 | ;
177 |
178 | $request = new Request(
179 | $this->createMock(Client::class),
180 | 'GET',
181 | $this->createMock(PsrUri::class),
182 | ['origin' => 'https://gitamp.audio'],
183 | );
184 |
185 | Loop::run(function () use ($websocketClient, $request) {
186 | Loop::defer(function () {
187 | Loop::stop();
188 | });
189 |
190 | yield $this->handler->onStart($this->httpServer, $this->gateway);
191 |
192 | yield $this->handler->handleClient($this->gateway, $websocketClient, $request, new Response());
193 | });
194 | }
195 |
196 | public function testHandleClientWithExistingEvents(): void
197 | {
198 | $results = $this->createMock(Results::class);
199 |
200 | $results
201 | ->expects($this->once())
202 | ->method('hasEvents')
203 | ->willReturn(true)
204 | ;
205 |
206 | $results
207 | ->expects($this->once())
208 | ->method('jsonEncode')
209 | ->willReturn('{}')
210 | ;
211 |
212 | $this->listener
213 | ->expects($this->once())
214 | ->method('listen')
215 | ->willReturn(new Success($results))
216 | ;
217 |
218 | $this->gateway
219 | ->expects($this->exactly(2))
220 | ->method('broadcast')
221 | ;
222 |
223 | $websocketClient = $this->createMock(WebsocketClient::class);
224 |
225 | $request = new Request(
226 | $this->createMock(Client::class),
227 | 'GET',
228 | $this->createMock(PsrUri::class),
229 | ['origin' => 'https://gitamp.audio'],
230 | );
231 |
232 | Loop::run(function () use ($websocketClient, $request) {
233 | Loop::defer(function () {
234 | Loop::stop();
235 | });
236 |
237 | yield $this->handler->onStart($this->httpServer, $this->gateway);
238 |
239 | yield $this->handler->handleClient($this->gateway, $websocketClient, $request, new Response());
240 | });
241 | }
242 |
243 | public function testOnStopReturnsNothing(): void
244 | {
245 | $this->assertNull(wait($this->handler->onStop($this->httpServer, $this->gateway)));
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |