Browse Source

Merge branch 'release-0.26'

release-0.26 v0.26.0
Simon Eisenmann 10 years ago
parent
commit
3dfd030b0b
  1. 2
      .gitignore
  2. 9
      .travis.yml
  3. 3
      AUTHORS
  4. 238
      CHANGELOG.md
  5. 4
      ChangeLog
  6. 11
      Godeps
  7. 79
      Makefile.am
  8. 2
      README.md
  9. 4
      configure.ac
  10. 2
      debian/gbp.conf
  11. 12
      dependencies.tsv
  12. 20
      doc/REST-API.txt
  13. 4
      go/buffercache/buffercache.go
  14. 49
      go/channelling/api.go
  15. 196
      go/channelling/api/api.go
  16. 98
      go/channelling/api/api_test.go
  17. 43
      go/channelling/api/handle_authentication.go
  18. 68
      go/channelling/api/handle_chat.go
  19. 43
      go/channelling/api/handle_conference.go
  20. 48
      go/channelling/api/handle_hello.go
  21. 35
      go/channelling/api/handle_room.go
  22. 53
      go/channelling/api/handle_self.go
  23. 60
      go/channelling/api/handle_sessions.go
  24. 36
      go/channelling/api/handle_users.go
  25. 41
      go/channelling/bus.go
  26. 306
      go/channelling/bus_manager.go
  27. 49
      go/channelling/client.go
  28. 26
      go/channelling/clientstats.go
  29. 20
      go/channelling/codec.go
  30. 2
      go/channelling/common_test.go
  31. 43
      go/channelling/config.go
  32. 27
      go/channelling/connection.go
  33. 4
      go/channelling/contact.go
  34. 27
      go/channelling/contact_manager.go
  35. 2
      go/channelling/context.go
  36. 42
      go/channelling/data.go
  37. 72
      go/channelling/hub.go
  38. 22
      go/channelling/imagecache.go
  39. 266
      go/channelling/pipeline.go
  40. 244
      go/channelling/pipeline_manager.go
  41. 14
      go/channelling/room_manager.go
  42. 4
      go/channelling/room_manager_test.go
  43. 27
      go/channelling/roomworker.go
  44. 2
      go/channelling/roomworker_test.go
  45. 2
      go/channelling/server/api.go
  46. 63
      go/channelling/server/config.go
  47. 98
      go/channelling/server/pipelines.go
  48. 6
      go/channelling/server/rooms.go
  49. 19
      go/channelling/server/sessions.go
  50. 12
      go/channelling/server/stats.go
  51. 2
      go/channelling/server/tls.go
  52. 8
      go/channelling/server/tokens.go
  53. 56
      go/channelling/server/users.go
  54. 144
      go/channelling/session.go
  55. 75
      go/channelling/session_manager.go
  56. 60
      go/channelling/sessionattestation.go
  57. 26
      go/channelling/sessioncreator.go
  58. 26
      go/channelling/sessionstore.go
  59. 29
      go/channelling/sessiontoken.go
  60. 29
      go/channelling/sessionupdate.go
  61. 37
      go/channelling/sink.go
  62. 2
      go/channelling/stats_manager.go
  63. 17
      go/channelling/tickets.go
  64. 7
      go/channelling/tokenprovider.go
  65. 26
      go/channelling/turndata.go
  66. 29
      go/channelling/unicaster.go
  67. 12
      go/channelling/user.go
  68. 26
      go/channelling/userstore.go
  69. 140
      go/natsconnection/natsconnection.go
  70. 32
      go/randomstring/randomstring.go
  71. 14
      server.conf.in
  72. 329
      src/app/spreed-webrtc-server/channelling_api.go
  73. 56
      src/app/spreed-webrtc-server/handler_image.go
  74. 30
      src/app/spreed-webrtc-server/handler_main.go
  75. 91
      src/app/spreed-webrtc-server/handler_room.go
  76. 79
      src/app/spreed-webrtc-server/handler_sandbox.go
  77. 59
      src/app/spreed-webrtc-server/handler_wellknown.go
  78. 27
      src/app/spreed-webrtc-server/handler_ws.go
  79. 272
      src/app/spreed-webrtc-server/main.go
  80. 17
      src/app/spreed-webrtc-server/utils.go
  81. 12
      static/js/controllers/uicontroller.js
  82. 53
      static/js/services/playpromise.js
  83. 9
      static/js/services/services.js
  84. 12
      static/js/services/videolayout.js

2
.gitignore vendored

@ -4,7 +4,7 @@
/src/github.com /src/github.com
/src/golang.struktur.de /src/golang.struktur.de
/*.pprof /*.pprof
/server.conf /server.conf*
/*.log /*.log
/changelog*.txt /changelog*.txt
/src/styles/.sass-cache /src/styles/.sass-cache

9
.travis.yml

@ -3,16 +3,13 @@
language: go language: go
go: go:
- 1.1
- 1.2
- 1.3 - 1.3
- 1.4 - 1.4
- 1.5 - 1.5
- 1.6 - 1.6
env: env:
- GEM_HOME=/var/lib/gems/1.9.1 USE_GODEPS=0 - GEM_HOME=/var/lib/gems/1.9.1
- GEM_HOME=/var/lib/gems/1.9.1 USE_GODEPS=1
before_install: before_install:
- sudo add-apt-repository -y ppa:chris-lea/node.js - sudo add-apt-repository -y ppa:chris-lea/node.js
@ -23,19 +20,17 @@ install:
- sudo gem1.9.1 install compass - sudo gem1.9.1 install compass
- sudo gem1.9.1 install scss-lint - sudo gem1.9.1 install scss-lint
- npm install - npm install
- if [ "$USE_GODEPS" = "1" ]; then wget https://raw.githubusercontent.com/pote/gpm/v1.3.2/bin/gpm && chmod +x gpm && sudo mv gpm /usr/local/bin; fi
script: script:
- ./autogen.sh - ./autogen.sh
- ./configure - ./configure
- if [ "$USE_GODEPS" = "0" ]; then make get; fi
- if [ "$USE_GODEPS" = "1" ]; then make gpm; fi
- make styleshint - make styleshint
# TODO(fancycode): enable styleslint once all styles have been fixed # TODO(fancycode): enable styleslint once all styles have been fixed
# - make styleslint # - make styleslint
- make styles - make styles
- make jshint - make jshint
- make javascript - make javascript
- make goget
- make test - make test
- make binary - make binary
- make build-i18n - make build-i18n

3
AUTHORS

@ -6,6 +6,7 @@
Simon Eisenmann <simon@struktur.de> Simon Eisenmann <simon@struktur.de>
Joachim Bauch <bauch@struktur.de> Joachim Bauch <bauch@struktur.de>
Evan Theurer <theurer@struktur.de> Evan Theurer <theurer@struktur.de>
Leon Klingele <klingele@struktur.de>
Translation: Translation:
Curt Frisemo <curt.frisemo@spreed.com> Curt Frisemo <curt.frisemo@spreed.com>

238
debian/changelog → CHANGELOG.md

@ -1,16 +1,85 @@
spreed-webrtc-server (0.24.13) trusty; urgency=medium ## 0.26.0
* Remove go-tip from travis until it works again.
* Update Dockerfile to Xenial base and install pinned Go dependencies (fixes #278).
* Update README.md
## 0.25.5
* Improve misleading log when pipelines API is enabled.
* Pipeline API is now optional and disabled by default.
* Update to Xenial base and install pinned Go dependencies (fixing #278).
* Added default sink.
* Added to and from userid to sink/pipeline API.
* Hide pipelines web API behind a configuration flag.
* Implement NATS sink outbound encoding.
* Extended Pipeline manager to support Sink creation.
* Implement sessionCreate via NATS.
* Implemented pipeline for Offer, Candidate and Bye.
## 0.25.4
* Wrap nats connections as reference.
* Updated release build logic, so it works better with packaging.
## 0.25.3
* Updated change log.
## 0.25.2
* Support promise based play as defined in Chrome 50 - see https://developers.google.com/web/updates/2016/03/play-returns-promise
* Always disable web worker for PDF.js and no longer rely on PDF.js catching the execption when the worker cannot be started, fixing Firefox 45+. * Always disable web worker for PDF.js and no longer rely on PDF.js catching the execption when the worker cannot be started, fixing Firefox 45+.
* Moved NATS connecting helpers to own submodule.
* Added outbound ringer timeout (35s).
* Fixed make target name.
* Refactored structure of Go source code to module and binary.
* Reconnect NATS forever and log NATS connection events.
* Implemented service discovery .well-known endpoint at /.well-known/spreed-configuration
* Removed obsolete file.
## 0.25.1
-- Simon Eisenmann <simon@struktur.de> Tue, 29 Mar 2016 13:40:35 +0200 * Use new changelog to retrieve VERSION.
* Added support for default and override config.
* Removed obsolete file.
spreed-webrtc-server (0.24.12) trusty; urgency=medium
## 0.25.0
* Added hints how CHANGELOG.md is created.
* Use markdown for changelog.
* Removed own debian folder, to avoid conflicts for packagers.
* Trigger NATS events non blocking through buffered channel.
* Split "release" target into binary and assets.
* Split "install" target into binary and assets. This way packaging can later move the static assets to a separate package.
* Brought back mediaDevices wrapper for gUM for Firefox >= 38 fixing #263 and #264.
* Added Go 1.6.
* Fixed tests to reflect busManager changes.
* Added startup bus event and a NATS client id.
* Removed auth bus event in favour of session bus event.
* Added docstrings and cleaned up code.
* Validate Offer and Answer content, so only events without _token key are triggered as channelling event to bus.
* Added support for NATS pub/sub messaging to trigger channeling events for external services.
* Added Leon to authors.
* cryptoRand.Int / pseudoRand.Intn to generate random integer. Previous way was modulo-biased
* Add missing characters to random string function, so we use the full upper+lowercase alphabet
* Avoid using LDFLAGS as this might be set to unexpected values in environment.
* Require a golang version of at least 1.3.0.
* Only run TravisCI builds against go1.3 and tip.
## 0.24.12
* Brought back mediaDevices wrapper for gUM for Firefox >= 38 fixing #263 and #264. * Brought back mediaDevices wrapper for gUM for Firefox >= 38 fixing #263 and #264.
-- Simon Eisenmann <simon@struktur.de> Tue, 01 Mar 2016 17:31:11 +0100
spreed-webrtc-server (0.24.11) trusty; urgency=medium ## 0.24.11
* Stop waiting on video early if first video track is enabled but muted. * Stop waiting on video early if first video track is enabled but muted.
* Use sh shebang instead of bash to be less Linux specific (#244). * Use sh shebang instead of bash to be less Linux specific (#244).
@ -23,18 +92,16 @@ spreed-webrtc-server (0.24.11) trusty; urgency=medium
* Firefox 44 has fixed gUM permission indicator, so limiting workaround to 43 and lower. * Firefox 44 has fixed gUM permission indicator, so limiting workaround to 43 and lower.
* Restrict VP9 experiment to Chrome >= 48. * Restrict VP9 experiment to Chrome >= 48.
-- Simon Eisenmann <simon@struktur.de> Thu, 18 Feb 2016 11:05:49 +0100
spreed-webrtc-server (0.24.10) trusty; urgency=medium ## 0.24.10
* Avoid to break when there is no mediaDevices. * Avoid to break when there is no mediaDevices.
* Added compatibility fix for Chrome 38 which stopped working when called from Chrome 46+ (Munge remote offer UDP/TLS/RTP/SAVPF to RTP/SAVPF). * Added compatibility fix for Chrome 38 which stopped working when called from Chrome 46+ (Munge remote offer UDP/TLS/RTP/SAVPF to RTP/SAVPF).
* Only stop user media automatically, when all tracks have ended. * Only stop user media automatically, when all tracks have ended.
* Stop waiting on video early if first video track is enabled but muted. * Stop waiting on video early if first video track is enabled but muted.
-- Simon Eisenmann <simon@struktur.de> Fri, 15 Jan 2016 12:57:13 +0100
spreed-webrtc-server (0.24.9) trusty; urgency=medium ## 0.24.9
* Added support for Firefox 43 API changes. * Added support for Firefox 43 API changes.
* Use mediaDevices API to enumarate input devices to avoid deprecation warning in Chrome. * Use mediaDevices API to enumarate input devices to avoid deprecation warning in Chrome.
@ -47,30 +114,26 @@ spreed-webrtc-server (0.24.9) trusty; urgency=medium
* Added travis to test Go 1.5 compatibility. * Added travis to test Go 1.5 compatibility.
* ODF and PDF presentations now have a white background to avoid issues with files with have no background on their own. * ODF and PDF presentations now have a white background to avoid issues with files with have no background on their own.
-- Simon Eisenmann <simon@struktur.de> Tue, 05 Jan 2016 14:48:27 +0100
spreed-webrtc-server (0.24.8) trusty; urgency=medium ## 0.24.8
* Avoid to scale up screen sharing when sharing not full screen. * Avoid to scale up screen sharing when sharing not full screen.
-- Simon Eisenmann <simon@struktur.de> Thu, 13 Aug 2015 16:21:22 +0200
spreed-webrtc-server (0.24.7) trusty; urgency=medium ## 0.24.7
* Fixed a problem where Chrome did not apply screen sharing constraints correctly and screen sharing was using a low resolution. * Fixed a problem where Chrome did not apply screen sharing constraints correctly and screen sharing was using a low resolution.
* Fixed a problem where sounds used as interval could not be disabled. * Fixed a problem where sounds used as interval could not be disabled.
* Added window.showCiphers helper for testing WebRTC stats API. * Added window.showCiphers helper for testing WebRTC stats API.
-- Simon Eisenmann <simon@struktur.de> Thu, 13 Aug 2015 16:01:41 +0200
spreed-webrtc-server (0.24.6) trusty; urgency=medium ## 0.24.6
* Make travis run 'make test'. * Make travis run 'make test'.
* Disable notifications on Android Chrome (see https://code.google.com/p/chromium/issues/detail?id=481856). * Disable notifications on Android Chrome (see https://code.google.com/p/chromium/issues/detail?id=481856).
-- Simon Eisenmann <simon@struktur.de> Mon, 10 Aug 2015 17:33:46 +0200
spreed-webrtc-server (0.24.5) trusty; urgency=medium ## 0.24.5
* Updated ua-parser to 0.7.9. * Updated ua-parser to 0.7.9.
* Fixed errors in unit tests. * Fixed errors in unit tests.
@ -82,18 +145,16 @@ spreed-webrtc-server (0.24.5) trusty; urgency=medium
* Fixed a problem where streams could not be started when they were disabled when call was started and server has renegotiation disabled. * Fixed a problem where streams could not be started when they were disabled when call was started and server has renegotiation disabled.
* Fixed a problem where the renegotiation shrine was ignored. * Fixed a problem where the renegotiation shrine was ignored.
-- Simon Eisenmann <simon@struktur.de> Fri, 07 Aug 2015 16:08:14 +0200
spreed-webrtc-server (0.24.4) precise; urgency=low ## 0.24.4
* Updated German translations. * Updated German translations.
* Fixed invalid experimental constraints. * Fixed invalid experimental constraints.
* Avoid to handle the main room as global room. * Avoid to handle the main room as global room.
-- Simon Eisenmann <simon@struktur.de> Wed, 29 Jul 2015 16:31:06 +0200 ## 0.24.3
spreed-webrtc-server (0.24.3) precise; urgency=low
* Removed deprecated API to fix Chromium 47 compatibility. * Removed deprecated API to fix Chromium 47 compatibility.
* Improved UI usability for smaller devices. * Improved UI usability for smaller devices.
* Increased the width of buddy list and chat. * Increased the width of buddy list and chat.
@ -108,15 +169,13 @@ spreed-webrtc-server (0.24.3) precise; urgency=low
* Fixed a problem where screen sharing streams were not cleaned up. * Fixed a problem where screen sharing streams were not cleaned up.
* Added support for custom type dialogs. * Added support for custom type dialogs.
-- Simon Eisenmann <simon@struktur.de> Tue, 28 Jul 2015 19:22:28 +0200
spreed-webrtc-server (0.24.2) precise; urgency=low ## 0.24.2
* Fixed javascript load order, so compiled scripts load properly. * Fixed javascript load order, so compiled scripts load properly.
-- Simon Eisenmann <simon@struktur.de> Fri, 03 Jul 2015 15:15:18 +0200
spreed-webrtc-server (0.24.1) precise; urgency=low ## 0.24.1
* Load sandboxes on demand, generated by server. * Load sandboxes on demand, generated by server.
* ODF and PDF sandboxes now use CSP from HTTP response header. * ODF and PDF sandboxes now use CSP from HTTP response header.
@ -124,9 +183,8 @@ spreed-webrtc-server (0.24.1) precise; urgency=low
* Sandbox iframes are now always created on demand. * Sandbox iframes are now always created on demand.
* Don't return users twice in "Welcome" from global room. * Don't return users twice in "Welcome" from global room.
-- Simon Eisenmann <simon@struktur.de> Fri, 03 Jul 2015 11:43:56 +0200
spreed-webrtc-server (0.24.0) precise; urgency=low ## 0.24.0
* Added hover actions on buddy picture in group chat. * Added hover actions on buddy picture in group chat.
* Jed.js was updated to 1.1.0 including API update for translations. * Jed.js was updated to 1.1.0 including API update for translations.
@ -165,17 +223,15 @@ spreed-webrtc-server (0.24.0) precise; urgency=low
* Styles were split up, so they can be built seperately. * Styles were split up, so they can be built seperately.
* Fixed a problem, where Chrome thought it already had an offer. * Fixed a problem, where Chrome thought it already had an offer.
-- Simon Eisenmann <simon@struktur.de> Tue, 16 Jun 2015 22:50:46 +0200
spreed-webrtc-server (0.23.8) precise; urgency=low ## 0.23.8
* Session subscriptions now notify close both ways. * Session subscriptions now notify close both ways.
* Reenable chat rooms on certain conditions related to peer connectivity. * Reenable chat rooms on certain conditions related to peer connectivity.
* Fixed an issue where replaced sessions cannot receive data from contacts in other rooms. * Fixed an issue where replaced sessions cannot receive data from contacts in other rooms.
-- Simon Eisenmann <simon@struktur.de> Wed, 08 Apr 2015 17:24:59 +0200
spreed-webrtc-server (0.23.7) precise; urgency=low ## 0.23.7
* Updated SCSS to match coding style. * Updated SCSS to match coding style.
* Updated sjcl.js to 1.0.2. * Updated sjcl.js to 1.0.2.
@ -189,9 +245,8 @@ spreed-webrtc-server (0.23.7) precise; urgency=low
* Channeling API now discards all incoming messages larger than 1MB. * Channeling API now discards all incoming messages larger than 1MB.
* Video component now corretly exits from full screen in all cases. * Video component now corretly exits from full screen in all cases.
-- Simon Eisenmann <simon@struktur.de> Thu, 05 Mar 2015 18:00:55 +0100
spreed-webrtc-server (0.23.6) precise; urgency=low ## 0.23.6
* Fixed Youtube module. * Fixed Youtube module.
* Contacts is now a module and can be disabled in server configuration. * Contacts is now a module and can be disabled in server configuration.
@ -203,16 +258,14 @@ spreed-webrtc-server (0.23.6) precise; urgency=low
* Text.js was updated. * Text.js was updated.
* CPU overuse detection (Chrome) is no longe experimental and now enabled by default. * CPU overuse detection (Chrome) is no longe experimental and now enabled by default.
-- Simon Eisenmann <simon@struktur.de> Fri, 20 Feb 2015 18:30:16 +0100
spreed-webrtc-server (0.23.5) precise; urgency=low ## 0.23.5
* No longer install config file in install target of Makefile. We leave it to the packaging. * No longer install config file in install target of Makefile. We leave it to the packaging.
* Sessions are no longer cleaned up when another connection replaced the session and a stale connection gets disconnected after that. * Sessions are no longer cleaned up when another connection replaced the session and a stale connection gets disconnected after that.
-- Simon Eisenmann <simon@struktur.de> Wed, 11 Feb 2015 11:16:47 +0100
spreed-webrtc-server (0.23.4) precise; urgency=low ## 0.23.4
* Cleanup of README. * Cleanup of README.
* Fixed a problem where videos were not sized correctly. * Fixed a problem where videos were not sized correctly.
@ -226,9 +279,8 @@ spreed-webrtc-server (0.23.4) precise; urgency=low
* Updated require.js and r.js to 2.1.5. * Updated require.js and r.js to 2.1.5.
* Fixed room reset when default room is disabled. * Fixed room reset when default room is disabled.
-- Simon Eisenmann <simon@struktur.de> Mon, 02 Feb 2015 17:18:53 +0100
spreed-webrtc-server (0.23.3) precise; urgency=low ## 0.23.3
* Improved room bar room change and leave buttons. * Improved room bar room change and leave buttons.
* Never hide room bar completely. * Never hide room bar completely.
@ -245,22 +297,19 @@ spreed-webrtc-server (0.23.3) precise; urgency=low
* Avoid showing settings automatically when not connected or still in authorizing phase. * Avoid showing settings automatically when not connected or still in authorizing phase.
* Added some missing CSS classes to allow easier UI mods. * Added some missing CSS classes to allow easier UI mods.
-- Simon Eisenmann <simon@struktur.de> Fri, 19 Dec 2014 17:15:10 +0100
spreed-webrtc-server (0.23.2) precise; urgency=low ## 0.23.2
* Do not build combined Javascript in strict mode to avoid compatibility issues. * Do not build combined Javascript in strict mode to avoid compatibility issues.
-- Simon Eisenmann <simon@struktur.de> Wed, 10 Dec 2014 17:18:17 +0100
spreed-webrtc-server (0.23.1) precise; urgency=low ## 0.23.1
* Fixed strict mode on release compile. * Fixed strict mode on release compile.
* Fixed prefix support of make install. * Fixed prefix support of make install.
-- Simon Eisenmann <simon@struktur.de> Wed, 10 Dec 2014 14:03:15 +0100
spreed-webrtc-server (0.23.0) precise; urgency=low ## 0.23.0
* Added support for renegotation in web client (disabled). * Added support for renegotation in web client (disabled).
* Rooms were refactored to be able to confirm joins. * Rooms were refactored to be able to confirm joins.
@ -286,9 +335,8 @@ spreed-webrtc-server (0.23.0) precise; urgency=low
* Missed call toast now always is shown. * Missed call toast now always is shown.
* Improved toast notification styles. * Improved toast notification styles.
-- Simon Eisenmann <simon@struktur.de> Tue, 09 Dec 2014 15:45:52 +0100
spreed-webrtc-server (0.22.8) precise; urgency=low ## 0.22.8
* Removed opacity transition from chat pane to avoid compositing issues. * Removed opacity transition from chat pane to avoid compositing issues.
* Fixed timeout of usermedia test. * Fixed timeout of usermedia test.
@ -297,9 +345,8 @@ spreed-webrtc-server (0.22.8) precise; urgency=low
* Make sure to stop stream after testing usermedia. * Make sure to stop stream after testing usermedia.
* Several small UI alignment issues. * Several small UI alignment issues.
-- Simon Eisenmann <simon@struktur.de> Tue, 11 Nov 2014 18:37:40 +0100
spreed-webrtc-server (0.22.7) precise; urgency=low ## 0.22.7
* Fixed a typo in getStreamById api. * Fixed a typo in getStreamById api.
* Roombar visibility is now controlled by layout. * Roombar visibility is now controlled by layout.
@ -309,9 +356,8 @@ spreed-webrtc-server (0.22.7) precise; urgency=low
* Fixed an issue where incoming chat messages failed when getting called from the sender at the same time. * Fixed an issue where incoming chat messages failed when getting called from the sender at the same time.
* No longer use dpkg-parsechangelog on configure. * No longer use dpkg-parsechangelog on configure.
-- Simon Eisenmann <simon@struktur.de> Mon, 03 Nov 2014 11:02:53 +0100
spreed-webrtc-server (0.22.6) precise; urgency=low ## 0.22.6
* Added missing gear to remove streams from peer connections. * Added missing gear to remove streams from peer connections.
* FireFox no longer shows remove videos multiple times. * FireFox no longer shows remove videos multiple times.
@ -319,19 +365,17 @@ spreed-webrtc-server (0.22.6) precise; urgency=low
* Added a bunch of experimental audio and video settings (disabled by default). * Added a bunch of experimental audio and video settings (disabled by default).
* Added an option to automatically use same device for output as is used for input (Windows only and enabled by default). * Added an option to automatically use same device for output as is used for input (Windows only and enabled by default).
-- Simon Eisenmann <simon@struktur.de> Thu, 23 Oct 2014 14:17:54 +0200
spreed-webrtc-server (0.22.5) precise; urgency=low ## 0.22.5
* Fixed an issue where the own video was not showing in democrazy layout. * Fixed an issue where the own video was not showing in democrazy layout.
* Own video is no longer delayed in democrazy layout. * Own video is no longer delayed in democrazy layout.
-- Simon Eisenmann <simon@struktur.de> Fri, 26 Sep 2014 22:29:35 +0200
spreed-webrtc-server (0.22.4) precise; urgency=low ## 0.22.4
* Optimized Makefile and cleaned up building. * Optimized Makefile and cleaned up building.
* WebODF was updated to 0.5.4. * WebODF was updated to ## 0.5.4.
* Video layout 'classroom' has been added. * Video layout 'classroom' has been added.
* Video layout 'smally' is now using black background. * Video layout 'smally' is now using black background.
* Several smaller layout improvements. * Several smaller layout improvements.
@ -341,9 +385,8 @@ spreed-webrtc-server (0.22.4) precise; urgency=low
* Video layout 'onepeople' is now selectable als "Large view". * Video layout 'onepeople' is now selectable als "Large view".
* The own audio level indicator is now visible again. * The own audio level indicator is now visible again.
-- Simon Eisenmann <simon@struktur.de> Fri, 26 Sep 2014 16:06:23 +0200
spreed-webrtc-server (0.22.3) precise; urgency=low ## 0.22.3
* Enable 1080p capturing for Chrome 38+. * Enable 1080p capturing for Chrome 38+.
* Added option to use 8 FPS video capturing. * Added option to use 8 FPS video capturing.
@ -355,23 +398,20 @@ spreed-webrtc-server (0.22.3) precise; urgency=low
* Fixed fast reenable of local video (added timeout). * Fixed fast reenable of local video (added timeout).
* Fixed issue where a failed peer connection did hangup the whole conference. * Fixed issue where a failed peer connection did hangup the whole conference.
* Added video layout self portrait. * Added video layout self portrait.
* Fixed call state resurrection when there was a heartbeat timeout. * Fixed call state resurrection when there was a heartbeat timeout.
-- Simon Eisenmann <simon@struktur.de> Fri, 19 Sep 2014 11:04:42 +0200
spreed-webrtc-server (0.22.2) precise; urgency=low ## 0.22.2
* Fixed room join after reconnect. * Fixed room join after reconnect.
-- Simon Eisenmann <simon@struktur.de> Tue, 09 Sep 2014 11:00:08 +0200
spreed-webrtc-server (0.22.1) precise; urgency=low ## 0.22.1
* Fixed load of local stored date when not logged in. * Fixed load of local stored date when not logged in.
-- Simon Eisenmann <simon@struktur.de> Fri, 05 Sep 2014 17:42:48 +0200
spreed-webrtc-server (0.22.0) precise; urgency=low ## 0.22.0
* WebODF was updated to 0.5.2. * WebODF was updated to 0.5.2.
* Multiple bugfixes and improvements to YouTube player. * Multiple bugfixes and improvements to YouTube player.
@ -380,7 +420,7 @@ spreed-webrtc-server (0.22.0) precise; urgency=low
* Added support for promises during initialization code. * Added support for promises during initialization code.
* Added support for plugin provided translations. * Added support for plugin provided translations.
* Stream lined reconnects. * Stream lined reconnects.
* Improved status update performance and avoid to do * Improved status update performance and avoid to do
several during connect/authentication phase. several during connect/authentication phase.
* Increased timeout to wait for remote video. * Increased timeout to wait for remote video.
* Screen sharing extension waiter timeout fixed. * Screen sharing extension waiter timeout fixed.
@ -404,9 +444,8 @@ spreed-webrtc-server (0.22.0) precise; urgency=low
* Fixed scaling of contact images. * Fixed scaling of contact images.
* Fixed chat room resume when it was previously deleted. * Fixed chat room resume when it was previously deleted.
-- Simon Eisenmann <simon@struktur.de> Fri, 05 Sep 2014 16:50:52 +0200
spreed-webrtc-server (0.21.0) precise; urgency=low ## 0.21.0
* The language is now available in appData service. * The language is now available in appData service.
* Implemented YouTube video sharing. * Implemented YouTube video sharing.
@ -424,9 +463,8 @@ spreed-webrtc-server (0.21.0) precise; urgency=low
* Contacts can now be removed in contact manager. * Contacts can now be removed in contact manager.
* Various other bug fixes. * Various other bug fixes.
-- Simon Eisenmann <simon@struktur.de> Fri, 25 Jul 2014 17:53:34 +0200
spreed-webrtc-server (0.20.0) precise; urgency=low ## 0.20.0
* Added presentation mode. * Added presentation mode.
* Added geolocation sharing in chat. * Added geolocation sharing in chat.
@ -438,17 +476,15 @@ spreed-webrtc-server (0.20.0) precise; urgency=low
* Added ES5 detection on startup. * Added ES5 detection on startup.
* Implemented a contact manager. * Implemented a contact manager.
-- Simon Eisenmann <longsleep@redemption.intranet.struktur.de> Fri, 11 Jul 2014 19:12:00 +0200
spreed-webrtc-server (0.19.1) precise; urgency=low ## 0.19.1
* Added Dockerfile. * Added Dockerfile.
* Updates to compile time dependencies. * Updates to compile time dependencies.
* Session data no longer overwrites contact data. * Session data no longer overwrites contact data.
-- Simon Eisenmann <simon@struktur.de> Mon, 30 Jun 2014 19:18:17 +0200
spreed-webrtc-server (0.19.0) precise; urgency=low ## 0.19.0
* Implemented authenticated sessions. * Implemented authenticated sessions.
* Implemented contacts. * Implemented contacts.
@ -466,25 +502,22 @@ spreed-webrtc-server (0.19.0) precise; urgency=low
* Multiple bug fixes. * Multiple bug fixes.
* Improved build system autoconf detections. * Improved build system autoconf detections.
-- Simon Eisenmann <simon@struktur.de> Mon, 23 Jun 2014 15:44:30 +0200
spreed-webrtc-server (0.18.1) precise; urgency=low ## 0.18.1
* Added autoconf/automake support. * Added autoconf/automake support.
* Build SCSS compressed in release mode. * Build SCSS compressed in release mode.
-- Simon Eisenmann <simon@struktur.de> Tue, 27 May 2014 11:16:22 +0200
spreed-webrtc-server (0.18.0) precise; urgency=low ## 0.18.0
* The project is now named Spreed WebRTC. All reference to the old * The project is now named Spreed WebRTC. All reference to the old
name Spreed Speak Freely have been replaced. name Spreed Speak Freely have been replaced.
* Cleanup of Javascript code to match coding guide lines. * Cleanup of Javascript code to match coding guide lines.
* Added various new targets to make to check javascript and scss code. * Added various new targets to make to check javascript and scss code.
-- Simon Eisenmann <simon@struktur.de> Fri, 23 May 2014 10:46:51 +0200
spreed-webrtc-server (0.17.5) precise; urgency=low ## 0.17.5
* Implemented server side support for user authentication and authorization. * Implemented server side support for user authentication and authorization.
* Added an REST API end point (see docs). * Added an REST API end point (see docs).
@ -504,9 +537,8 @@ spreed-webrtc-server (0.17.5) precise; urgency=low
* Added support to specify the default language by URL parameter (?lang=en). * Added support to specify the default language by URL parameter (?lang=en).
* Added support for .webp images as buddy images. * Added support for .webp images as buddy images.
-- Simon Eisenmann <simon@struktur.de> Thu, 22 May 2014 17:49:16 +0200
spreed-webrtc-server (0.17.4) precise; urgency=low ## 0.17.4
* Updated Japanese translation. * Updated Japanese translation.
* Allow Makefile variables CONFIG_FILE and CONFIG_PATH. * Allow Makefile variables CONFIG_FILE and CONFIG_PATH.
@ -522,9 +554,8 @@ spreed-webrtc-server (0.17.4) precise; urgency=low
* Use new websocket.Upgraded API. * Use new websocket.Upgraded API.
* No longer hang up on reload when not confirmed. * No longer hang up on reload when not confirmed.
-- Simon Eisenmann <simon@struktur.de> Thu, 24 Apr 2014 17:59:05 +0200
spreed-webrtc-server (0.17.3) precise; urgency=low ## 0.17.3
* Buddy images are now loaded with seperate URL calls. * Buddy images are now loaded with seperate URL calls.
* Updated Korean (ko) language. * Updated Korean (ko) language.
@ -537,9 +568,8 @@ spreed-webrtc-server (0.17.3) precise; urgency=low
* Refactored video layout renderer in seperate service. * Refactored video layout renderer in seperate service.
* Implemented alternaitve conference view (not enabled yet). * Implemented alternaitve conference view (not enabled yet).
-- Simon Eisenmann <simon@struktur.de> Wed, 16 Apr 2014 17:41:13 +0200
spreed-webrtc-server (0.17.2) precise; urgency=low ## 0.17.2
* Fixed timeouts when there was a disconnect. * Fixed timeouts when there was a disconnect.
* Use sleepy as submodule from external source. * Use sleepy as submodule from external source.
@ -560,9 +590,8 @@ spreed-webrtc-server (0.17.2) precise; urgency=low
* Fixed Javascript code injection with room names. * Fixed Javascript code injection with room names.
* Show current room name in title. * Show current room name in title.
-- Simon Eisenmann <simon@struktur.de> Fri, 11 Apr 2014 19:42:10 +0200
spreed-webrtc-server (0.17.1) precise; urgency=low ## 0.17.1
* Added translations for Korean and Chinese. * Added translations for Korean and Chinese.
* Multiple updates to 3rd party js libraries. * Multiple updates to 3rd party js libraries.
@ -570,9 +599,8 @@ spreed-webrtc-server (0.17.1) precise; urgency=low
* Bootstrap update to 3.1.1. * Bootstrap update to 3.1.1.
* No longer disconnect ongoing calls on websocket disconnect. * No longer disconnect ongoing calls on websocket disconnect.
-- Simon Eisenmann <simon@struktur.de> Fri, 04 Apr 2014 18:46:56 +0200
spreed-webrtc-server (0.17.0) precise; urgency=low ## 0.17.0
* TURN user names now use expiration time stamp. This fixes compatibility * TURN user names now use expiration time stamp. This fixes compatibility
with latest TURN REST specification and requires a reasonably recent with latest TURN REST specification and requires a reasonably recent
@ -586,17 +614,15 @@ spreed-webrtc-server (0.17.0) precise; urgency=low
* Use strong random number generator. * Use strong random number generator.
* Support configuring pprof HTTP server. * Support configuring pprof HTTP server.
-- Simon Eisenmann <simon@struktur.de> Fri, 28 Mar 2014 16:48:46 +0100
spreed-webrtc-server (0.16.1) precise; urgency=low ## 0.16.1
* Implemented chat session control UI. * Implemented chat session control UI.
* Layout controller refactorization. * Layout controller refactorization.
* Chat UI bugfixes. * Chat UI bugfixes.
-- Simon Eisenmann <simon@struktur.de> Fri, 21 Mar 2014 11:46:10 +0100
spreed-webrtc-server (0.16.0) precise; urgency=low ## 0.16.0
* Chat UI improvements. * Chat UI improvements.
* Screen sharing is now a scroll pane and no longer scaled down. * Screen sharing is now a scroll pane and no longer scaled down.
@ -609,11 +635,9 @@ spreed-webrtc-server (0.16.0) precise; urgency=low
numer of cpus for GOMAXPROCS per default. numer of cpus for GOMAXPROCS per default.
* Added server helper for stats and profiling. * Added server helper for stats and profiling.
-- Simon Eisenmann <simon@struktur.de> Mon, 17 Mar 2014 18:35:08 +0100
spreed-webrtc-server (0.15.0) precise; urgency=low ## 0.15.0
* Initial public release. * Initial public release.
-- Simon Eisenmann <simon@struktur.de> Thu, 13 Feb 2014 16:14:05 +0100

4
ChangeLog

@ -1 +1,3 @@
See debian/changelog or https://github.com/strukturag/spreed-webrtc for further information. See CHANGELOG.md or https://github.com/strukturag/spreed-webrtc for further information.
The changes are generated with git like `git log v0.24.12..HEAD --no-merges --format=" * %s"` and then manually formatted and added to CHANGELOG.md.

11
Godeps

@ -1,11 +0,0 @@
github.com/gorilla/context 215affda49addc4c8ef7e2534915df2c8c35c6cd
github.com/gorilla/mux ba336c9cfb43552c90de6cb2ceedd3271c747558
github.com/gorilla/securecookie aeade84400a85c6875264ae51c7a56ecdcb61751
github.com/gorilla/websocket 6eb6ad425a89d9da7a5549bc6da8f79ba5c17844
github.com/longsleep/pkac 0.0.1
github.com/satori/go.uuid afe1e2ddf0f05b7c29d388a3f8e76cb15c2231ca
github.com/strukturag/goacceptlanguageparser goacceptlanguageparser_v100
github.com/strukturag/httputils httputils_v012
github.com/strukturag/phoenix phoenix_v0133
github.com/strukturag/sloth v0.9.2
github.com/dlintw/goconf dcc070983490608a14480e3bf943bad464785df5

79
Makefile.am

@ -22,11 +22,14 @@ AUTOMAKE_OPTIONS = -Wno-portability
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
EXENAME := spreed-webrtc-server EXENAME := spreed-webrtc-server
GOPKG := github.com/strukturag/spreed-webrtc
GOPATH := "$(CURDIR)/vendor:$(CURDIR)"
CONFIG_FILE ?= spreed-webrtc-server.conf CONFIG_FILE ?= spreed-webrtc-server.conf
CONFIG_PATH ?= /etc CONFIG_PATH ?= /etc
GOBUILDFLAGS ?= GOBUILDFLAGS ?=
GOTESTFLAGS ?= GOTESTFLAGS ?=
SYSTEM_GOPATH = /usr/share/gocode/src/
BIN ?= @prefix@/sbin BIN ?= @prefix@/sbin
SHARE ?= @prefix@/share/spreed-webrtc-server SHARE ?= @prefix@/share/spreed-webrtc-server
@ -47,24 +50,26 @@ build: get binary assets
gopath: gopath:
@echo GOPATH=$(GOPATH) @echo GOPATH=$(GOPATH)
if READONLY_VENDOR_GOPATH goget:
export GOPATH = $(DIST):$(CURDIR) if [ -z "$(DEB_BUILDING)" ]; then GOPATH=$(GOPATH) go get github.com/rogpeppe/godeps; fi
get: $(DIST) if [ -z "$(DEB_BUILDING)" ]; then GOPATH=$(GOPATH) $(CURDIR)/vendor/bin/godeps -u dependencies.tsv; fi
ln -sf $(VENDOR_GOPATH)/src -t $(DIST) mkdir -p $(shell dirname "$(CURDIR)/vendor/src/$(GOPKG)")
else rm -f $(CURDIR)/vendor/src/$(GOPKG)
export GOPATH = $(VENDOR_GOPATH):$(CURDIR) ln -sfn $(PWD) $(CURDIR)/vendor/src/$(GOPKG)
get:
endif
$(GO) get app/...
getupdate: vendorclean get get: goget
gpm: gogetupdate: govendorclean goget
@if [ "$(GPM)" = "" ]; then echo "Command 'gpm' not found"; exit 1; fi
$(GPM) install dependencies.tsv:
set -e ;\
TMP=$$(mktemp -d) ;\
cp -r $(CURDIR)/vendor $$TMP ;\
GOPATH=$$TMP/vendor:$(CURDIR) $(CURDIR)/vendor/bin/godeps $(GOPKG)/src/app/spreed-webrtc-server ./go/... > $(CURDIR)/dependencies.tsv ;\
rm -rf $$TMP
binary: binary:
$(GO) build $(GOBUILDFLAGS) -o bin/$(EXENAME) -ldflags '$(LDFLAGS)' app/$(EXENAME) GOPATH=$(GOPATH) $(GO) build $(GOBUILDFLAGS) -o bin/$(EXENAME) -ldflags '$(INTERNALLDFLAGS)' app/$(EXENAME)
binaryrace: GOBUILDFLAGS := $(GOBUILDFLAGS) -race binaryrace: GOBUILDFLAGS := $(GOBUILDFLAGS) -race
binaryrace: binary binaryrace: binary
@ -72,16 +77,23 @@ binaryrace: binary
binaryall: GOBUILDFLAGS := $(GOBUILDFLAGS) -a binaryall: GOBUILDFLAGS := $(GOBUILDFLAGS) -a
binaryall: binary binaryall: binary
fmt: gofmt:
$(GO) fmt app/... GOPATH=$(GOPATH) $(GO) fmt app/... ./go/...
fmt: gofmt
test:
GOPATH=$(GOPATH) $(GO) test -v $(GOTESTFLAGS) app/... ./go/...
dist_gopath: $(DIST_SRC)
find $(SYSTEM_GOPATH) -mindepth 1 -maxdepth 1 -type d \
-exec ln -sf {} $(DIST_SRC) \;
test: get
$(GO) test $(GOTESTFLAGS) app/...
assets: javascript fonts assets: javascript fonts
releaseassets: RJSFLAGS = generateSourceMaps=false preserveLicenseComments=true release-assets: RJSFLAGS = generateSourceMaps=false preserveLicenseComments=true
releaseassets: assets release-assets: assets
fonts: fonts:
$(MKDIR_P) $(CURDIR)/static/fonts $(MKDIR_P) $(CURDIR)/static/fonts
@ -119,14 +131,20 @@ extract-i18n:
update-i18n: update-i18n:
cd $(CURDIR)/src/i18n && $(MAKE) update cd $(CURDIR)/src/i18n && $(MAKE) update
release: LDFLAGS = -X main.version $(PACKAGE_VERSION) -X main.defaultConfig $(CONFIG_PATH)/$(CONFIG_FILE) release-binary: GOPATH = "$(DIST):$(CURDIR)/vendor:$(CURDIR)"
release: OUTPUT = $(DIST_BIN) release-binary: INTERNALLDFLAGS = -X main.version $(PACKAGE_VERSION) -X main.defaultConfig $(CONFIG_PATH)/$(CONFIG_FILE)
release: $(DIST_BIN) binary releaseassets release-binary: OUTPUT = $(DIST_BIN)
release-binary: dist_gopath $(DIST_BIN) binary
release: release-binary release-assets
install: install-binary:
@echo "Installing binaries to: $(DESTDIR)$(BIN)" @echo "Installing binaries to: $(DESTDIR)$(BIN)"
@echo "Installing static resources to: $(DESTDIR)$(SHARE)"
$(INSTALL) -d $(DESTDIR)$(BIN) $(INSTALL) -d $(DESTDIR)$(BIN)
$(INSTALL) bin/$(EXENAME) $(DESTDIR)$(BIN)
install-assets:
@echo "Installing static resources to: $(DESTDIR)$(SHARE)"
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/html $(INSTALL) -d $(DESTDIR)$(SHARE)/www/html
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/html/sandboxes $(INSTALL) -d $(DESTDIR)$(SHARE)/www/html/sandboxes
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static $(INSTALL) -d $(DESTDIR)$(SHARE)/www/static
@ -137,7 +155,6 @@ install:
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/css $(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/css
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/js/libs/pdf $(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/js/libs/pdf
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/js/sandboxes $(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/js/sandboxes
$(INSTALL) bin/$(EXENAME) $(DESTDIR)$(BIN)
$(INSTALL) html/*.html $(DESTDIR)$(SHARE)/www/html $(INSTALL) html/*.html $(DESTDIR)$(SHARE)/www/html
$(INSTALL) html/sandboxes/*.html $(DESTDIR)$(SHARE)/www/html/sandboxes $(INSTALL) html/sandboxes/*.html $(DESTDIR)$(SHARE)/www/html/sandboxes
$(INSTALL) static/img/* $(DESTDIR)$(SHARE)/www/static/img $(INSTALL) static/img/* $(DESTDIR)$(SHARE)/www/static/img
@ -151,6 +168,8 @@ install:
$(INSTALL) -D static/js/libs/webodf.js $(DESTDIR)$(SHARE)/www/static/js/libs/webodf.js $(INSTALL) -D static/js/libs/webodf.js $(DESTDIR)$(SHARE)/www/static/js/libs/webodf.js
$(INSTALL) $(OUTPUT_JS)/sandboxes/*.js $(DESTDIR)$(SHARE)/www/static/js/sandboxes $(INSTALL) $(OUTPUT_JS)/sandboxes/*.js $(DESTDIR)$(SHARE)/www/static/js/sandboxes
install: install-binary install-assets
clean: clean:
$(GO) clean -i -r app/... 2>/dev/null || true $(GO) clean -i -r app/... 2>/dev/null || true
rm -rf $(CURDIR)/static/fonts rm -rf $(CURDIR)/static/fonts
@ -160,13 +179,13 @@ clean:
distclean: clean distclean: clean
rm -rf $(DIST) rm -rf $(DIST)
vendorclean: govendorclean:
rm -rf vendor/* rm -rf vendor/*
pristine: distclean vendorclean pristine: distclean govendorclean
rm -f server.conf rm -f server.conf
$(DIST): $(DIST_SRC):
$(MKDIR_P) $@ $(MKDIR_P) $@
$(DIST_BIN): $(DIST_BIN):
@ -181,4 +200,4 @@ tarball: distclean release install
cp server.conf.in $(TARPATH)/loader cp server.conf.in $(TARPATH)/loader
tar czf $(DIST)/$(PACKAGE_NAME)-$(PACKAGE_VERSION)_$(BUILD_OS)_$(BUILD_ARCH).tar.gz -C $(DIST) $(PACKAGE_NAME)-$(PACKAGE_VERSION) tar czf $(DIST)/$(PACKAGE_NAME)-$(PACKAGE_VERSION)_$(BUILD_OS)_$(BUILD_ARCH).tar.gz -C $(DIST) $(PACKAGE_NAME)-$(PACKAGE_VERSION)
.PHONY: clean distclean vendorclean pristine get getupdate build javascript fonts styles release releasetest dist_gopath install gopath binary binaryrace binaryall tarball assets .PHONY: clean distclean govendorclean pristine goget gogetupdate build javascript fonts styles release release-binary dist_gopath install install-binary install-assets gopath binary binaryrace binaryall tarball assets dependencies.tsv

2
README.md

@ -9,7 +9,7 @@ The latest source of Spreed WebRTC can be found on [GitHub](https://github.com/s
## Build prerequisites ## Build prerequisites
- [Go](http://golang.org) >= 1.1.0 - [Go](http://golang.org) >= 1.3.0
- [NodeJS](http://nodejs.org/) >= 0.6.0 - [NodeJS](http://nodejs.org/) >= 0.6.0
- [autoconf](http://www.gnu.org/software/autoconf/) - [autoconf](http://www.gnu.org/software/autoconf/)
- [automake](http://www.gnu.org/software/automake/) - [automake](http://www.gnu.org/software/automake/)

4
configure.ac

@ -33,7 +33,7 @@ AC_SUBST(I18N_COPYRIGHT)
AC_SUBST(I18N_BUG_ADDRESS) AC_SUBST(I18N_BUG_ADDRESS)
# minimum required versions # minimum required versions
GO_VERSION_MIN=1.1 GO_VERSION_MIN=1.3
NODEJS_VERSION_MIN=0.6.0 NODEJS_VERSION_MIN=0.6.0
NODEJS_VERSION_STYLES_MIN=0.10.0 NODEJS_VERSION_STYLES_MIN=0.10.0
SASS_VERSION_MIN=3.3.0 SASS_VERSION_MIN=3.3.0
@ -202,7 +202,7 @@ else
fi fi
AC_SUBST(NODEJS_SUPPORT_PO2JSON) AC_SUBST(NODEJS_SUPPORT_PO2JSON)
VERSION=`head -n1 $PWD/debian/changelog | $AWK -F'[[()]]' '{print $2}'` VERSION=`head -n1 $PWD/CHANGELOG.md | $AWK '{print $2}'`
PACKAGE_VERSION="$VERSION" PACKAGE_VERSION="$VERSION"
PACKAGE_STRING="$PACKAGE_NAME $PACKAGE_VERSION" PACKAGE_STRING="$PACKAGE_NAME $PACKAGE_VERSION"
AC_DEFINE_UNQUOTED(VERSION, ["$VERSION"], [Version number of package]) AC_DEFINE_UNQUOTED(VERSION, ["$VERSION"], [Version number of package])

2
debian/gbp.conf vendored

@ -1,2 +0,0 @@
[DEFAULT]
debian-tag = v%(version)s

12
dependencies.tsv

@ -0,0 +1,12 @@
github.com/dlintw/goconf git dcc070983490608a14480e3bf943bad464785df5 2012-02-28T08:26:10Z
github.com/gorilla/context git 215affda49addc4c8ef7e2534915df2c8c35c6cd 2014-12-17T16:02:51Z
github.com/gorilla/mux git ba336c9cfb43552c90de6cb2ceedd3271c747558 2015-07-17T15:03:03Z
github.com/gorilla/securecookie git aeade84400a85c6875264ae51c7a56ecdcb61751 2015-07-16T23:32:44Z
github.com/gorilla/websocket git 6eb6ad425a89d9da7a5549bc6da8f79ba5c17844 2015-07-14T14:06:27Z
github.com/longsleep/pkac git 68bf8859f58dd84332ee41c07eba357fb3818ba3 2014-05-01T18:13:13Z
github.com/nats-io/nats git 355b5b97e0842dc94f1106729aa88e33e06317ca 2015-12-09T21:13:14Z
github.com/satori/go.uuid git afe1e2ddf0f05b7c29d388a3f8e76cb15c2231ca 2015-06-15T02:45:37Z
github.com/strukturag/goacceptlanguageparser git 68066e68c2940059aadc6e19661610cf428b6647 2014-02-13T13:31:23Z
github.com/strukturag/httputils git afbf05c71ac03ee7989c96d033a9571ba4ded468 2014-07-02T01:35:33Z
github.com/strukturag/phoenix git c3429c4e93588d848606263a7f96f91c90e43178 2016-03-02T12:52:52Z
github.com/strukturag/sloth git 74a8bcf67368de59baafe5d3e17aee9875564cfc 2015-04-22T08:59:42Z
1 github.com/dlintw/goconf git dcc070983490608a14480e3bf943bad464785df5 2012-02-28T08:26:10Z
2 github.com/gorilla/context git 215affda49addc4c8ef7e2534915df2c8c35c6cd 2014-12-17T16:02:51Z
3 github.com/gorilla/mux git ba336c9cfb43552c90de6cb2ceedd3271c747558 2015-07-17T15:03:03Z
4 github.com/gorilla/securecookie git aeade84400a85c6875264ae51c7a56ecdcb61751 2015-07-16T23:32:44Z
5 github.com/gorilla/websocket git 6eb6ad425a89d9da7a5549bc6da8f79ba5c17844 2015-07-14T14:06:27Z
6 github.com/longsleep/pkac git 68bf8859f58dd84332ee41c07eba357fb3818ba3 2014-05-01T18:13:13Z
7 github.com/nats-io/nats git 355b5b97e0842dc94f1106729aa88e33e06317ca 2015-12-09T21:13:14Z
8 github.com/satori/go.uuid git afe1e2ddf0f05b7c29d388a3f8e76cb15c2231ca 2015-06-15T02:45:37Z
9 github.com/strukturag/goacceptlanguageparser git 68066e68c2940059aadc6e19661610cf428b6647 2014-02-13T13:31:23Z
10 github.com/strukturag/httputils git afbf05c71ac03ee7989c96d033a9571ba4ded468 2014-07-02T01:35:33Z
11 github.com/strukturag/phoenix git c3429c4e93588d848606263a7f96f91c90e43178 2016-03-02T12:52:52Z
12 github.com/strukturag/sloth git 74a8bcf67368de59baafe5d3e17aee9875564cfc 2015-04-22T08:59:42Z

20
doc/REST-API.txt

@ -1,7 +1,7 @@
Spreed WebRTC REST API v1.0.0 Spreed WebRTC REST API v1.0.0
=============================================== ===============================================
(c)2014 struktur AG (c)2016 struktur AG
The server provides a REST api end point to provide functionality outside the The server provides a REST api end point to provide functionality outside the
the channeling API or without a established web socket connection. the channeling API or without a established web socket connection.
@ -13,12 +13,26 @@ API or there was a problem while JSON encoding.
Some end points of this API require a existing session from the channeling Some end points of this API require a existing session from the channeling
API, incuding the private secret session ID. To get these it is sufficient API, incuding the private secret session ID. To get these it is sufficient
to connect to the channeling API with a websocket connection. The server to connect to the channeling API with a websocket connection. The server
sends the session ID (Id) and secure session ID (Sid) within the Self sends the session ID (Id) and secure session ID (Sid) within the Self
document after connection was established. document after connection was established.
Available end points with request methods and content-type: Available end points with request methods and content-type:
/.well-known/spreed-configuration
The well-known end points can be used to discover the base addresses for
other end points. Spreed WebRTC provides this for compatibility with
larger setups so clients using this can find the Spreed WebRTC end point.
GET application/x-www-form-urlencoded
No parameters.
Response 200:
{
"spreed-webrtc_endpoint": "http://localhost:8093"
}
/api/v1/config /api/v1/config
The config end points returns the server configuration. As it is available The config end points returns the server configuration. As it is available
@ -178,4 +192,4 @@ https://github.com/strukturag/spreed-webrtc
For questions, contact mailto:opensource@struktur.de. For questions, contact mailto:opensource@struktur.de.
(c)2014 struktur AG (c)2016 struktur AG

4
src/app/spreed-webrtc-server/buffercache.go → go/buffercache/buffercache.go vendored

@ -19,7 +19,7 @@
* *
*/ */
package main package buffercache
import ( import (
"bytes" "bytes"
@ -161,7 +161,7 @@ func (cache *bufferCache) Wrap(data []byte) Buffer {
return &directBuffer{refcnt: 1, cache: cache, buf: bytes.NewBuffer(data)} return &directBuffer{refcnt: 1, cache: cache, buf: bytes.NewBuffer(data)}
} }
func readAll(dest Buffer, r io.Reader) error { func ReadAll(dest Buffer, r io.Reader) error {
var err error var err error
defer func() { defer func() {
e := recover() e := recover()

49
go/channelling/api.go

@ -0,0 +1,49 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type ChannellingAPI interface {
OnConnect(*Client, *Session) (interface{}, error)
OnDisconnect(*Client, *Session)
OnIncoming(Sender, *Session, *DataIncoming) (interface{}, error)
}
type ChannellingAPIConsumer interface {
SetChannellingAPI(ChannellingAPI)
GetChannellingAPI() ChannellingAPI
}
type channellingAPIConsumer struct {
ChannellingAPI ChannellingAPI
}
func NewChannellingAPIConsumer() ChannellingAPIConsumer {
return &channellingAPIConsumer{}
}
func (c *channellingAPIConsumer) SetChannellingAPI(api ChannellingAPI) {
c.ChannellingAPI = api
}
func (c *channellingAPIConsumer) GetChannellingAPI() ChannellingAPI {
return c.ChannellingAPI
}

196
go/channelling/api/api.go

@ -0,0 +1,196 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"log"
"github.com/strukturag/spreed-webrtc/go/channelling"
)
const (
maxConferenceSize = 100
apiVersion = 1.4 // Keep this in sync with CHANNELING-API docs.Hand
)
type channellingAPI struct {
RoomStatusManager channelling.RoomStatusManager
SessionEncoder channelling.SessionEncoder
SessionManager channelling.SessionManager
StatsCounter channelling.StatsCounter
ContactManager channelling.ContactManager
TurnDataCreator channelling.TurnDataCreator
Unicaster channelling.Unicaster
BusManager channelling.BusManager
PipelineManager channelling.PipelineManager
config *channelling.Config
}
// New creates and initializes a new ChannellingAPI using
// various other services for initialization. It is intended to handle
// incoming and outgoing channeling API events from clients.
func New(config *channelling.Config,
roomStatus channelling.RoomStatusManager,
sessionEncoder channelling.SessionEncoder,
sessionManager channelling.SessionManager,
statsCounter channelling.StatsCounter,
contactManager channelling.ContactManager,
turnDataCreator channelling.TurnDataCreator,
unicaster channelling.Unicaster,
busManager channelling.BusManager,
pipelineManager channelling.PipelineManager) channelling.ChannellingAPI {
return &channellingAPI{
roomStatus,
sessionEncoder,
sessionManager,
statsCounter,
contactManager,
turnDataCreator,
unicaster,
busManager,
pipelineManager,
config,
}
}
func (api *channellingAPI) OnConnect(client *channelling.Client, session *channelling.Session) (interface{}, error) {
api.Unicaster.OnConnect(client, session)
self, err := api.HandleSelf(session)
if err == nil {
api.BusManager.Trigger(channelling.BusManagerConnect, session.Id, "", nil, nil)
}
return self, err
}
func (api *channellingAPI) OnDisconnect(client *channelling.Client, session *channelling.Session) {
api.Unicaster.OnDisconnect(client, session)
api.BusManager.Trigger(channelling.BusManagerDisconnect, session.Id, "", nil, nil)
}
func (api *channellingAPI) OnIncoming(sender channelling.Sender, session *channelling.Session, msg *channelling.DataIncoming) (interface{}, error) {
var pipeline *channelling.Pipeline
switch msg.Type {
case "Self":
return api.HandleSelf(session)
case "Hello":
if msg.Hello == nil {
return nil, channelling.NewDataError("bad_request", "message did not contain Hello")
}
return api.HandleHello(session, msg.Hello, sender)
case "Offer":
if msg.Offer == nil || msg.Offer.Offer == nil {
log.Println("Received invalid offer message.", msg)
break
}
if _, ok := msg.Offer.Offer["_token"]; !ok {
pipeline = api.PipelineManager.GetPipeline(channelling.PipelineNamespaceCall, sender, session, msg.Offer.To)
// Trigger offer event when offer has no token, so this is
// not triggered for peerxfer and peerscreenshare offers.
api.BusManager.Trigger(channelling.BusManagerOffer, session.Id, msg.Offer.To, nil, pipeline)
}
session.Unicast(msg.Offer.To, msg.Offer, pipeline)
case "Candidate":
if msg.Candidate == nil || msg.Candidate.Candidate == nil {
log.Println("Received invalid candidate message.", msg)
break
}
pipeline = api.PipelineManager.GetPipeline(channelling.PipelineNamespaceCall, sender, session, msg.Candidate.To)
session.Unicast(msg.Candidate.To, msg.Candidate, pipeline)
case "Answer":
if msg.Answer == nil || msg.Answer.Answer == nil {
log.Println("Received invalid answer message.", msg)
break
}
if _, ok := msg.Answer.Answer["_token"]; !ok {
pipeline = api.PipelineManager.GetPipeline(channelling.PipelineNamespaceCall, sender, session, msg.Answer.To)
// Trigger answer event when answer has no token. so this is
// not triggered for peerxfer and peerscreenshare answers.
api.BusManager.Trigger(channelling.BusManagerAnswer, session.Id, msg.Answer.To, nil, pipeline)
}
session.Unicast(msg.Answer.To, msg.Answer, pipeline)
case "Users":
return api.HandleUsers(session)
case "Authentication":
if msg.Authentication == nil || msg.Authentication.Authentication == nil {
return nil, channelling.NewDataError("bad_request", "message did not contain Authentication")
}
return api.HandleAuthentication(session, msg.Authentication.Authentication)
case "Bye":
if msg.Bye == nil {
log.Println("Received invalid bye message.", msg)
break
}
pipeline = api.PipelineManager.GetPipeline(channelling.PipelineNamespaceCall, sender, session, msg.Bye.To)
api.BusManager.Trigger(channelling.BusManagerBye, session.Id, msg.Bye.To, nil, pipeline)
session.Unicast(msg.Bye.To, msg.Bye, pipeline)
if pipeline != nil {
pipeline.Close()
}
case "Status":
if msg.Status == nil {
log.Println("Received invalid status message.", msg)
break
}
//log.Println("Status", msg.Status)
session.Update(&channelling.SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status})
session.BroadcastStatus()
case "Chat":
if msg.Chat == nil || msg.Chat.Chat == nil {
log.Println("Received invalid chat message.", msg)
break
}
api.HandleChat(session, msg.Chat)
case "Conference":
if msg.Conference == nil {
log.Println("Received invalid conference message.", msg)
break
}
api.HandleConference(session, msg.Conference)
case "Alive":
return msg.Alive, nil
case "Sessions":
if msg.Sessions == nil || msg.Sessions.Sessions == nil {
return nil, channelling.NewDataError("bad_request", "message did not contain Sessions")
}
return api.HandleSessions(session, msg.Sessions.Sessions)
case "Room":
if msg.Room == nil {
return nil, channelling.NewDataError("bad_request", "message did not contain Room")
}
return api.HandleRoom(session, msg.Room)
default:
log.Println("OnText unhandled message type", msg.Type)
}
return nil, nil
}

98
src/app/spreed-webrtc-server/channelling_api_test.go → go/channelling/api/api_test.go

@ -19,50 +19,59 @@
* *
*/ */
package main package api
import ( import (
"errors" "errors"
"fmt" "fmt"
"testing" "testing"
"github.com/gorilla/securecookie"
"github.com/strukturag/spreed-webrtc/go/buffercache"
"github.com/strukturag/spreed-webrtc/go/channelling"
) )
type fakeClient struct { type fakeClient struct {
} }
func (fake *fakeClient) Send(_ Buffer) { func (fake *fakeClient) Index() uint64 {
return 0
}
func (fake *fakeClient) Send(_ buffercache.Buffer) {
} }
type fakeRoomManager struct { type fakeRoomManager struct {
joinedRoomID string joinedRoomID string
leftRoomID string leftRoomID string
roomUsers []*DataSession roomUsers []*channelling.DataSession
joinedID string joinedID string
joinError error joinError error
leftID string leftID string
broadcasts []interface{} broadcasts []interface{}
updatedRoom *DataRoom updatedRoom *channelling.DataRoom
updateError error updateError error
} }
func (fake *fakeRoomManager) RoomUsers(session *Session) []*DataSession { func (fake *fakeRoomManager) RoomUsers(session *channelling.Session) []*channelling.DataSession {
return fake.roomUsers return fake.roomUsers
} }
func (fake *fakeRoomManager) JoinRoom(id, roomName, roomType string, _ *DataRoomCredentials, session *Session, sessionAuthenticated bool, _ Sender) (*DataRoom, error) { func (fake *fakeRoomManager) JoinRoom(id, roomName, roomType string, _ *channelling.DataRoomCredentials, session *channelling.Session, sessionAuthenticated bool, _ channelling.Sender) (*channelling.DataRoom, error) {
fake.joinedID = id fake.joinedID = id
return &DataRoom{Name: roomName, Type: roomType}, fake.joinError return &channelling.DataRoom{Name: roomName, Type: roomType}, fake.joinError
} }
func (fake *fakeRoomManager) LeaveRoom(roomID, sessionID string) { func (fake *fakeRoomManager) LeaveRoom(roomID, sessionID string) {
fake.leftID = roomID fake.leftID = roomID
} }
func (fake *fakeRoomManager) Broadcast(_, _ string, outgoing *DataOutgoing) { func (fake *fakeRoomManager) Broadcast(_, _ string, outgoing *channelling.DataOutgoing) {
fake.broadcasts = append(fake.broadcasts, outgoing.Data) fake.broadcasts = append(fake.broadcasts, outgoing.Data)
} }
func (fake *fakeRoomManager) UpdateRoom(_ *Session, _ *DataRoom) (*DataRoom, error) { func (fake *fakeRoomManager) UpdateRoom(_ *channelling.Session, _ *channelling.DataRoom) (*channelling.DataRoom, error) {
return fake.updatedRoom, fake.updateError return fake.updatedRoom, fake.updateError
} }
@ -73,22 +82,22 @@ func (fake *fakeRoomManager) MakeRoomID(roomName, roomType string) string {
return fmt.Sprintf("%s:%s", roomType, roomName) return fmt.Sprintf("%s:%s", roomType, roomName)
} }
func NewTestChannellingAPI() (ChannellingAPI, *fakeClient, *Session, *fakeRoomManager) { func NewTestChannellingAPI() (channelling.ChannellingAPI, *fakeClient, *channelling.Session, *fakeRoomManager) {
apiConsumer := channelling.NewChannellingAPIConsumer()
client, roomManager := &fakeClient{}, &fakeRoomManager{} client, roomManager := &fakeClient{}, &fakeRoomManager{}
session := &Session{ sessionNonces := securecookie.New(securecookie.GenerateRandomKey(64), nil)
attestations: sessionNonces, session := channelling.NewSession(nil, nil, roomManager, roomManager, nil, sessionNonces, "", "")
Broadcaster: roomManager, busManager := channelling.NewBusManager(apiConsumer, "", false, "")
RoomStatusManager: roomManager, api := New(nil, roomManager, nil, nil, nil, nil, nil, nil, busManager, nil)
} apiConsumer.SetChannellingAPI(api)
session.attestation = &SessionAttestation{s: session} return api, client, session, roomManager
return NewChannellingAPI(nil, roomManager, nil, nil, nil, nil, nil, nil), client, session, roomManager
} }
func Test_ChannellingAPI_OnIncoming_HelloMessage_JoinsTheSelectedRoom(t *testing.T) { func Test_ChannellingAPI_OnIncoming_HelloMessage_JoinsTheSelectedRoom(t *testing.T) {
roomID, roomName, ua := "Room:foobar", "foobar", "unit tests" roomID, roomName, ua := "Room:foobar", "foobar", "unit tests"
api, client, session, roomManager := NewTestChannellingAPI() api, client, session, roomManager := NewTestChannellingAPI()
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName, Ua: ua}}) api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Hello", Hello: &channelling.DataHello{Id: roomName, Ua: ua}})
if roomManager.joinedID != roomID { if roomManager.joinedID != roomID {
t.Errorf("Expected to have joined room %v, but got %v", roomID, roomManager.joinedID) t.Errorf("Expected to have joined room %v, but got %v", roomID, roomManager.joinedID)
@ -98,7 +107,7 @@ func Test_ChannellingAPI_OnIncoming_HelloMessage_JoinsTheSelectedRoom(t *testing
t.Fatalf("Expected 1 broadcast, but got %d", broadcastCount) t.Fatalf("Expected 1 broadcast, but got %d", broadcastCount)
} }
dataSession, ok := roomManager.broadcasts[0].(*DataSession) dataSession, ok := roomManager.broadcasts[0].(*channelling.DataSession)
if !ok { if !ok {
t.Fatal("Expected a session data broadcast") t.Fatal("Expected a session data broadcast")
} }
@ -112,8 +121,8 @@ func Test_ChannellingAPI_OnIncoming_HelloMessage_LeavesAnyPreviouslyJoinedRooms(
roomID, roomName := "Room:foobar", "foobar" roomID, roomName := "Room:foobar", "foobar"
api, client, session, roomManager := NewTestChannellingAPI() api, client, session, roomManager := NewTestChannellingAPI()
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}}) api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Hello", Hello: &channelling.DataHello{Id: roomName}})
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: "baz"}}) api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Hello", Hello: &channelling.DataHello{Id: "baz"}})
if roomManager.leftID != roomID { if roomManager.leftID != roomID {
t.Errorf("Expected to have left room %v, but got %v", roomID, roomManager.leftID) t.Errorf("Expected to have left room %v, but got %v", roomID, roomManager.leftID)
@ -123,7 +132,7 @@ func Test_ChannellingAPI_OnIncoming_HelloMessage_LeavesAnyPreviouslyJoinedRooms(
t.Fatalf("Expected 3 broadcasts, but got %d", broadcastCount) t.Fatalf("Expected 3 broadcasts, but got %d", broadcastCount)
} }
dataSession, ok := roomManager.broadcasts[1].(*DataSession) dataSession, ok := roomManager.broadcasts[1].(*channelling.DataSession)
if !ok { if !ok {
t.Fatal("Expected a session data broadcast") t.Fatal("Expected a session data broadcast")
} }
@ -137,7 +146,7 @@ func Test_ChannellingAPI_OnIncoming_HelloMessage_DoesNotJoinIfNotPermitted(t *te
api, client, session, roomManager := NewTestChannellingAPI() api, client, session, roomManager := NewTestChannellingAPI()
roomManager.joinError = errors.New("Can't enter this room") roomManager.joinError = errors.New("Can't enter this room")
api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{}}) api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Hello", Hello: &channelling.DataHello{}})
if broadcastCount := len(roomManager.broadcasts); broadcastCount != 0 { if broadcastCount := len(roomManager.broadcasts); broadcastCount != 0 {
t.Fatalf("Expected no broadcasts, but got %d", broadcastCount) t.Fatalf("Expected no broadcasts, but got %d", broadcastCount)
@ -147,14 +156,14 @@ func Test_ChannellingAPI_OnIncoming_HelloMessage_DoesNotJoinIfNotPermitted(t *te
func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAWelcome(t *testing.T) { func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAWelcome(t *testing.T) {
roomID := "a-room" roomID := "a-room"
api, client, session, roomManager := NewTestChannellingAPI() api, client, session, roomManager := NewTestChannellingAPI()
roomManager.roomUsers = []*DataSession{&DataSession{}} roomManager.roomUsers = []*channelling.DataSession{&channelling.DataSession{}}
reply, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomID}}) reply, err := api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Hello", Hello: &channelling.DataHello{Id: roomID}})
if err != nil { if err != nil {
t.Fatalf("Unexpected error %v", err) t.Fatalf("Unexpected error %v", err)
} }
welcome, ok := reply.(*DataWelcome) welcome, ok := reply.(*channelling.DataWelcome)
if !ok { if !ok {
t.Fatalf("Expected response %#v to be a Welcome", reply) t.Fatalf("Expected response %#v to be a Welcome", reply)
} }
@ -174,9 +183,9 @@ func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAWelcome(t *testing
func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) { func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) {
api, client, session, roomManager := NewTestChannellingAPI() api, client, session, roomManager := NewTestChannellingAPI()
roomManager.joinError = NewDataError("bad_join", "") roomManager.joinError = channelling.NewDataError("bad_join", "")
_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{}}) _, err := api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Hello", Hello: &channelling.DataHello{}})
assertDataError(t, err, "bad_join") assertDataError(t, err, "bad_join")
} }
@ -184,19 +193,19 @@ func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAnErrorIfTheRoomCan
func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpdatedRoom(t *testing.T) { func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpdatedRoom(t *testing.T) {
roomName := "foo" roomName := "foo"
api, client, session, roomManager := NewTestChannellingAPI() api, client, session, roomManager := NewTestChannellingAPI()
roomManager.updatedRoom = &DataRoom{Name: "FOO"} roomManager.updatedRoom = &channelling.DataRoom{Name: "FOO"}
_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}}) _, err := api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Hello", Hello: &channelling.DataHello{Id: roomName}})
if err != nil { if err != nil {
t.Fatalf("Unexpected error %v", err) t.Fatalf("Unexpected error %v", err)
} }
reply, err := api.OnIncoming(client, session, &DataIncoming{Type: "Room", Room: &DataRoom{Name: roomName}}) reply, err := api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Room", Room: &channelling.DataRoom{Name: roomName}})
if err != nil { if err != nil {
t.Fatalf("Unexpected error %v", err) t.Fatalf("Unexpected error %v", err)
} }
room, ok := reply.(*DataRoom) room, ok := reply.(*channelling.DataRoom)
if !ok { if !ok {
t.Fatalf("Expected response message to be a Room") t.Fatalf("Expected response message to be a Room")
} }
@ -209,7 +218,7 @@ func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpda
t.Fatalf("Expected 1 broadcasts, but got %d", broadcastCount) t.Fatalf("Expected 1 broadcasts, but got %d", broadcastCount)
} }
if _, ok := roomManager.broadcasts[1].(*DataRoom); !ok { if _, ok := roomManager.broadcasts[1].(*channelling.DataRoom); !ok {
t.Fatal("Expected a room data broadcast") t.Fatal("Expected a room data broadcast")
} }
} }
@ -217,13 +226,30 @@ func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpda
func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAnErrorIfUpdatingTheRoomFails(t *testing.T) { func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAnErrorIfUpdatingTheRoomFails(t *testing.T) {
roomName := "foo" roomName := "foo"
api, client, session, roomManager := NewTestChannellingAPI() api, client, session, roomManager := NewTestChannellingAPI()
roomManager.updateError = NewDataError("a_room_error", "") roomManager.updateError = channelling.NewDataError("a_room_error", "")
_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}}) _, err := api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Hello", Hello: &channelling.DataHello{Id: roomName}})
if err != nil { if err != nil {
t.Fatalf("Unexpected error %v", err) t.Fatalf("Unexpected error %v", err)
} }
_, err = api.OnIncoming(client, session, &DataIncoming{Type: "Room", Room: &DataRoom{Name: roomName}}) _, err = api.OnIncoming(client, session, &channelling.DataIncoming{Type: "Room", Room: &channelling.DataRoom{Name: roomName}})
assertDataError(t, err, "a_room_error") assertDataError(t, err, "a_room_error")
} }
func assertDataError(t *testing.T, err error, code string) {
if err == nil {
t.Error("Expected an error, but none was returned")
return
}
dataError, ok := err.(*channelling.DataError)
if !ok {
t.Errorf("Expected error %#v to be a *DataError", err)
return
}
if code != dataError.Code {
t.Errorf("Expected error code to be %v, but was %v", code, dataError.Code)
}
}

43
go/channelling/api/handle_authentication.go

@ -0,0 +1,43 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"log"
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleAuthentication(session *channelling.Session, st *channelling.SessionToken) (*channelling.DataSelf, error) {
if err := api.SessionManager.Authenticate(session, st, ""); err != nil {
log.Println("Authentication failed", err, st.Userid, st.Nonce)
return nil, err
}
log.Println("Authentication success", session.Userid())
self, err := api.HandleSelf(session)
if err == nil {
session.BroadcastStatus()
}
return self, err
}

68
go/channelling/api/handle_chat.go

@ -0,0 +1,68 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"log"
"time"
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleChat(session *channelling.Session, chat *channelling.DataChat) {
// TODO(longsleep): Limit sent chat messages per incoming connection.
msg := chat.Chat
to := chat.To
if !msg.NoEcho {
session.Unicast(session.Id, chat, nil)
}
msg.Time = time.Now().Format(time.RFC3339)
if to == "" {
// TODO(longsleep): Check if chat broadcast is allowed.
if session.Hello {
api.StatsCounter.CountBroadcastChat()
session.Broadcast(chat)
}
} else {
if msg.Status != nil {
if msg.Status.ContactRequest != nil {
if !api.config.WithModule("contacts") {
return
}
if err := api.ContactManager.ContactrequestHandler(session, to, msg.Status.ContactRequest); err != nil {
log.Println("Ignoring invalid contact request.", err)
return
}
msg.Status.ContactRequest.Userid = session.Userid()
}
} else {
api.StatsCounter.CountUnicastChat()
}
session.Unicast(to, chat, nil)
if msg.Mid != "" {
// Send out delivery confirmation status chat message.
session.Unicast(session.Id, &channelling.DataChat{To: to, Type: "Chat", Chat: &channelling.DataChatMessage{Mid: msg.Mid, Status: &channelling.DataChatStatus{State: "sent"}}}, nil)
}
}
}

43
go/channelling/api/handle_conference.go

@ -0,0 +1,43 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"log"
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleConference(session *channelling.Session, conference *channelling.DataConference) {
// Check conference maximum size.
if len(conference.Conference) > maxConferenceSize {
log.Println("Refusing to create conference above limit.", len(conference.Conference))
return
}
// Send conference update to anyone.
for _, id := range conference.Conference {
if id != session.Id {
session.Unicast(id, conference, nil)
}
}
}

48
go/channelling/api/handle_hello.go

@ -0,0 +1,48 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleHello(session *channelling.Session, hello *channelling.DataHello, sender channelling.Sender) (*channelling.DataWelcome, error) {
// TODO(longsleep): Filter room id and user agent.
session.Update(&channelling.SessionUpdate{Types: []string{"Ua"}, Ua: hello.Ua})
// Compatibily for old clients.
roomName := hello.Name
if roomName == "" {
roomName = hello.Id
}
room, err := session.JoinRoom(roomName, hello.Type, hello.Credentials, sender)
if err != nil {
return nil, err
}
return &channelling.DataWelcome{
Type: "Welcome",
Room: room,
Users: api.RoomStatusManager.RoomUsers(session),
}, nil
}

35
go/channelling/api/handle_room.go

@ -0,0 +1,35 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleRoom(session *channelling.Session, room *channelling.DataRoom) (*channelling.DataRoom, error) {
room, err := api.RoomStatusManager.UpdateRoom(session, room)
if err == nil {
session.Broadcast(room)
}
return room, err
}

53
go/channelling/api/handle_self.go

@ -0,0 +1,53 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"log"
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleSelf(session *channelling.Session) (*channelling.DataSelf, error) {
token, err := api.SessionEncoder.EncodeSessionToken(session)
if err != nil {
log.Println("Error in OnRegister", err)
return nil, err
}
log.Println("Created new session token", len(token), token)
self := &channelling.DataSelf{
Type: "Self",
Id: session.Id,
Sid: session.Sid,
Userid: session.Userid(),
Suserid: api.SessionEncoder.EncodeSessionUserID(session),
Token: token,
Version: api.config.Version,
ApiVersion: apiVersion,
Turn: api.TurnDataCreator.CreateTurnData(session),
Stun: api.config.StunURIs,
}
api.BusManager.Trigger(channelling.BusManagerSession, session.Id, session.Userid(), nil, nil)
return self, nil
}

60
go/channelling/api/handle_sessions.go

@ -0,0 +1,60 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleSessions(session *channelling.Session, sessions *channelling.DataSessionsRequest) (*channelling.DataSessions, error) {
switch sessions.Type {
case "contact":
if !api.config.WithModule("contacts") {
return nil, channelling.NewDataError("contacts_not_enabled", "incoming contacts session request with contacts disabled")
}
userID, err := api.ContactManager.GetContactID(session, sessions.Token)
if err != nil {
return nil, err
}
return &channelling.DataSessions{
Type: "Sessions",
Users: api.SessionManager.GetUserSessions(session, userID),
Sessions: sessions,
}, nil
case "session":
id, err := session.DecodeAttestation(sessions.Token)
if err != nil {
return nil, channelling.NewDataError("bad_attestation", err.Error())
}
session, ok := api.Unicaster.GetSession(id)
if !ok {
return nil, channelling.NewDataError("no_such_session", "cannot retrieve session")
}
return &channelling.DataSessions{
Type: "Sessions",
Users: []*channelling.DataSession{session.Data()},
Sessions: sessions,
}, nil
default:
return nil, channelling.NewDataError("bad_request", "unknown sessions request type")
}
}

36
go/channelling/api/handle_users.go

@ -0,0 +1,36 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package api
import (
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleUsers(session *channelling.Session) (sessions *channelling.DataSessions, err error) {
if session.Hello {
sessions = &channelling.DataSessions{Type: "Users", Users: api.RoomStatusManager.RoomUsers(session)}
} else {
err = channelling.NewDataError("not_in_room", "Cannot list users without a current room")
}
return
}

41
go/channelling/bus.go

@ -0,0 +1,41 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type SessionCreateRequest struct {
Id string
Session *DataSession
Room *DataRoom
SetAsDefault bool
}
type DataSink struct {
SubjectOut string `json:subject_out"`
SubjectIn string `json:subject_in"`
}
type DataSinkOutgoing struct {
Outgoing *DataOutgoing
ToUserid string
FromUserid string
Pipe string `json:",omitempty"`
}

306
go/channelling/bus_manager.go

@ -0,0 +1,306 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
import (
"errors"
"fmt"
"log"
"sync"
"time"
"github.com/nats-io/nats"
"github.com/strukturag/spreed-webrtc/go/natsconnection"
)
const (
BusManagerStartup = "startup"
BusManagerOffer = "offer"
BusManagerAnswer = "answer"
BusManagerBye = "bye"
BusManagerConnect = "connect"
BusManagerDisconnect = "disconnect"
BusManagerSession = "session"
)
// A BusManager provides the API to interact with a bus.
type BusManager interface {
ChannellingAPIConsumer
Start()
Publish(subject string, v interface{}) error
Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error
Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) error
Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error)
BindRecvChan(subject string, channel interface{}) (*nats.Subscription, error)
BindSendChan(subject string, channel interface{}) error
PrefixSubject(string) string
CreateSink(string) Sink
}
// A BusTrigger is a container to serialize trigger events
// for the bus backend.
type BusTrigger struct {
Id string
Name string
From string
Payload string `json:",omitempty"`
Data interface{} `json:",omitempty"`
Pipeline string `json:",omitempty"`
}
// BusSubjectTrigger returns the bus subject for trigger payloads.
func BusSubjectTrigger(prefix, suffix string) string {
return fmt.Sprintf("%s.%s", prefix, suffix)
}
// NewBusManager creates and initializes a new BusMager with the
// provided flags for NATS support. It is intended to connect the
// backend bus with a easy to use API to send and receive bus data.
func NewBusManager(apiConsumer ChannellingAPIConsumer, id string, useNats bool, subjectPrefix string) BusManager {
var b BusManager
var err error
if useNats {
b, err = newNatsBus(apiConsumer, id, subjectPrefix)
if err == nil {
log.Println("NATS bus connected")
} else {
log.Println("Error connecting NATS bus", err)
b = &noopBus{apiConsumer, id}
}
} else {
b = &noopBus{apiConsumer, id}
}
return b
}
type noopBus struct {
ChannellingAPIConsumer
id string
}
func (bus *noopBus) Start() {
// noop
}
func (bus *noopBus) Publish(subject string, v interface{}) error {
return nil
}
func (bus *noopBus) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error {
return nil
}
func (bus *noopBus) Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) error {
return nil
}
func (bus *noopBus) PrefixSubject(subject string) string {
return subject
}
func (bus *noopBus) BindRecvChan(subject string, channel interface{}) (*nats.Subscription, error) {
return nil, nil
}
func (bus *noopBus) BindSendChan(subject string, channel interface{}) error {
return nil
}
func (bus *noopBus) Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error) {
return nil, nil
}
func (bus *noopBus) CreateSink(id string) Sink {
return nil
}
type natsBus struct {
ChannellingAPIConsumer
id string
prefix string
ec *natsconnection.EncodedConnection
triggerQueue chan *busQueueEntry
}
func newNatsBus(apiConsumer ChannellingAPIConsumer, id, prefix string) (*natsBus, error) {
ec, err := natsconnection.EstablishJSONEncodedConnection(nil)
if err != nil {
return nil, err
}
if prefix == "" {
prefix = "channelling.trigger"
}
// Create buffered channel for outbound NATS data.
triggerQueue := make(chan *busQueueEntry, 50)
return &natsBus{apiConsumer, id, prefix, ec, triggerQueue}, nil
}
func (bus *natsBus) Start() {
// Start go routine to process outbount NATS publishing.
go chPublish(bus.ec, bus.triggerQueue)
bus.Trigger(BusManagerStartup, bus.id, "", nil, nil)
}
func (bus *natsBus) Publish(subject string, v interface{}) error {
return bus.ec.Publish(subject, v)
}
func (bus *natsBus) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error {
return bus.ec.Request(subject, v, vPtr, timeout)
}
func (bus *natsBus) Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) (err error) {
trigger := &BusTrigger{
Id: bus.id,
Name: name,
From: from,
Payload: payload,
Data: data,
}
if pipeline != nil {
trigger.Pipeline = pipeline.GetID()
}
entry := &busQueueEntry{BusSubjectTrigger(bus.prefix, name), trigger}
select {
case bus.triggerQueue <- entry:
// sent ok
default:
log.Println("Failed to queue NATS event - queue full?")
err = errors.New("NATS trigger queue full")
}
return err
}
func (bus *natsBus) PrefixSubject(sub string) string {
return fmt.Sprintf("%s.%s", bus.prefix, sub)
}
func (bus *natsBus) Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error) {
return bus.ec.Subscribe(subject, cb)
}
func (bus *natsBus) BindRecvChan(subject string, channel interface{}) (*nats.Subscription, error) {
return bus.ec.BindRecvChan(subject, channel)
}
func (bus *natsBus) BindSendChan(subject string, channel interface{}) error {
return bus.ec.BindSendChan(subject, channel)
}
func (bus *natsBus) CreateSink(id string) (sink Sink) {
sink = newNatsSink(bus, id)
return
}
type busQueueEntry struct {
subject string
data interface{}
}
func chPublish(ec *natsconnection.EncodedConnection, channel chan (*busQueueEntry)) {
for {
entry := <-channel
err := ec.Publish(entry.subject, entry.data)
if err != nil {
log.Println("Failed to publish to NATS", entry.subject, err)
}
}
}
type natsSink struct {
sync.RWMutex
id string
bm BusManager
closed bool
SubjectOut string
SubjectIn string
sub *nats.Subscription
sendQueue chan *DataSinkOutgoing
}
func newNatsSink(bm BusManager, id string) *natsSink {
sink := &natsSink{
id: id,
bm: bm,
SubjectOut: bm.PrefixSubject(fmt.Sprintf("sink.%s.out", id)),
SubjectIn: bm.PrefixSubject(fmt.Sprintf("sink.%s.in", id)),
}
sink.sendQueue = make(chan *DataSinkOutgoing, 100)
bm.BindSendChan(sink.SubjectOut, sink.sendQueue)
return sink
}
func (sink *natsSink) Write(outgoing *DataSinkOutgoing) (err error) {
if sink.Enabled() {
log.Println("Sending via NATS sink", sink.SubjectOut, outgoing)
sink.sendQueue <- outgoing
}
return err
}
func (sink *natsSink) Enabled() bool {
sink.RLock()
defer sink.RUnlock()
return sink.closed == false
}
func (sink *natsSink) Close() {
sink.Lock()
defer sink.Unlock()
if sink.sub != nil {
err := sink.sub.Unsubscribe()
if err != nil {
log.Println("Failed to unsubscribe NATS sink", err)
} else {
sink.sub = nil
}
}
sink.closed = true
}
func (sink *natsSink) Export() *DataSink {
return &DataSink{
SubjectOut: sink.SubjectOut,
SubjectIn: sink.SubjectIn,
}
}
func (sink *natsSink) BindRecvChan(channel interface{}) (*nats.Subscription, error) {
sink.Lock()
defer sink.Unlock()
if sink.sub != nil {
sink.sub.Unsubscribe()
sink.sub = nil
}
sub, err := sink.bm.BindRecvChan(sink.SubjectIn, channel)
if err != nil {
return nil, err
}
sink.sub = sub
return sub, nil
}

49
src/app/spreed-webrtc-server/client.go → go/channelling/client.go

@ -19,36 +19,35 @@
* *
*/ */
package main package channelling
import ( import (
"log" "log"
"github.com/strukturag/spreed-webrtc/go/buffercache"
) )
type Sender interface { type Sender interface {
Send(Buffer)
}
type Client interface {
Sender
Session() *Session
Index() uint64 Index() uint64
Close() Send(buffercache.Buffer)
ReplaceAndClose(Client)
} }
type client struct { type Client struct {
Codec
ChannellingAPI
Connection Connection
session *Session Codec
ChannellingAPI ChannellingAPI
session *Session
} }
func NewClient(codec Codec, api ChannellingAPI, session *Session) *client { func NewClient(codec Codec, api ChannellingAPI, session *Session) *Client {
return &client{codec, api, nil, session} return &Client{
Codec: codec,
ChannellingAPI: api,
session: session,
}
} }
func (client *client) OnConnect(conn Connection) { func (client *Client) OnConnect(conn Connection) {
client.Connection = conn client.Connection = conn
if reply, err := client.ChannellingAPI.OnConnect(client, client.session); err == nil { if reply, err := client.ChannellingAPI.OnConnect(client, client.session); err == nil {
client.reply("", reply) client.reply("", reply)
@ -57,38 +56,38 @@ func (client *client) OnConnect(conn Connection) {
} }
} }
func (client *client) OnDisconnect() { func (client *Client) OnDisconnect() {
client.session.Close() client.session.Close()
client.ChannellingAPI.OnDisconnect(client, client.session) client.ChannellingAPI.OnDisconnect(client, client.session)
} }
func (client *client) OnText(b Buffer) { func (client *Client) OnText(b buffercache.Buffer) {
incoming, err := client.DecodeIncoming(b) incoming, err := client.Codec.DecodeIncoming(b)
if err != nil { if err != nil {
log.Println("OnText error while processing incoming message", err) log.Println("OnText error while processing incoming message", err)
return return
} }
if reply, err := client.OnIncoming(client, client.session, incoming); err != nil { if reply, err := client.ChannellingAPI.OnIncoming(client, client.session, incoming); err != nil {
client.reply(incoming.Iid, err) client.reply(incoming.Iid, err)
} else if reply != nil { } else if reply != nil {
client.reply(incoming.Iid, reply) client.reply(incoming.Iid, reply)
} }
} }
func (client *client) reply(iid string, m interface{}) { func (client *Client) reply(iid string, m interface{}) {
outgoing := &DataOutgoing{From: client.session.Id, Iid: iid, Data: m} outgoing := &DataOutgoing{From: client.session.Id, Iid: iid, Data: m}
if b, err := client.EncodeOutgoing(outgoing); err == nil { if b, err := client.Codec.EncodeOutgoing(outgoing); err == nil {
client.Send(b) client.Connection.Send(b)
b.Decref() b.Decref()
} }
} }
func (client *client) Session() *Session { func (client *Client) Session() *Session {
return client.session return client.session
} }
func (client *client) ReplaceAndClose(oldClient Client) { func (client *Client) ReplaceAndClose(oldClient *Client) {
oldSession := oldClient.Session() oldSession := oldClient.Session()
client.session.Replace(oldSession) client.session.Replace(oldSession)
go func() { go func() {

26
go/channelling/clientstats.go

@ -0,0 +1,26 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type ClientStats interface {
ClientInfo(details bool) (int, map[string]*DataSession, map[string]string)
}

20
src/app/spreed-webrtc-server/incoming_codec.go → go/channelling/codec.go

@ -19,43 +19,45 @@
* *
*/ */
package main package channelling
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"log" "log"
"github.com/strukturag/spreed-webrtc/go/buffercache"
) )
type IncomingDecoder interface { type IncomingDecoder interface {
DecodeIncoming(Buffer) (*DataIncoming, error) DecodeIncoming(buffercache.Buffer) (*DataIncoming, error)
} }
type OutgoingEncoder interface { type OutgoingEncoder interface {
EncodeOutgoing(*DataOutgoing) (Buffer, error) EncodeOutgoing(*DataOutgoing) (buffercache.Buffer, error)
} }
type Codec interface { type Codec interface {
NewBuffer() Buffer NewBuffer() buffercache.Buffer
IncomingDecoder IncomingDecoder
OutgoingEncoder OutgoingEncoder
} }
type incomingCodec struct { type incomingCodec struct {
buffers BufferCache buffers buffercache.BufferCache
incomingLimit int incomingLimit int
} }
func NewCodec(incomingLimit int) Codec { func NewCodec(incomingLimit int) Codec {
return &incomingCodec{NewBufferCache(1024, bytes.MinRead), incomingLimit} return &incomingCodec{buffercache.NewBufferCache(1024, bytes.MinRead), incomingLimit}
} }
func (codec incomingCodec) NewBuffer() Buffer { func (codec incomingCodec) NewBuffer() buffercache.Buffer {
return codec.buffers.New() return codec.buffers.New()
} }
func (codec incomingCodec) DecodeIncoming(b Buffer) (*DataIncoming, error) { func (codec incomingCodec) DecodeIncoming(b buffercache.Buffer) (*DataIncoming, error) {
length := b.GetBuffer().Len() length := b.GetBuffer().Len()
if length > codec.incomingLimit { if length > codec.incomingLimit {
return nil, errors.New("Incoming message size limit exceeded") return nil, errors.New("Incoming message size limit exceeded")
@ -64,7 +66,7 @@ func (codec incomingCodec) DecodeIncoming(b Buffer) (*DataIncoming, error) {
return incoming, json.Unmarshal(b.Bytes(), incoming) return incoming, json.Unmarshal(b.Bytes(), incoming)
} }
func (codec incomingCodec) EncodeOutgoing(outgoing *DataOutgoing) (Buffer, error) { func (codec incomingCodec) EncodeOutgoing(outgoing *DataOutgoing) (buffercache.Buffer, error) {
b := codec.NewBuffer() b := codec.NewBuffer()
if err := json.NewEncoder(b).Encode(outgoing); err != nil { if err := json.NewEncoder(b).Encode(outgoing); err != nil {
log.Println("Error while encoding JSON", err) log.Println("Error while encoding JSON", err)

2
src/app/spreed-webrtc-server/common_test.go → go/channelling/common_test.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"testing" "testing"

43
go/channelling/config.go

@ -0,0 +1,43 @@
package channelling
import (
"net/http"
)
type Config struct {
Title string // Title
Ver string `json:"-"` // Version (not exported to Javascript)
S string // Static URL prefix with version
B string // Base URL
Token string // Server token
Renegotiation bool // Renegotiation flag
StunURIs []string // STUN server URIs
TurnURIs []string // TURN server URIs
Tokens bool // True when we got a tokens file
Version string // Server version number
UsersEnabled bool // Flag if users are enabled
UsersAllowRegistration bool // Flag if users can register
UsersMode string // Users mode string
DefaultRoomEnabled bool // Flag if default room ("") is enabled
Plugin string // Plugin to load
AuthorizeRoomCreation bool // Whether a user account is required to create rooms
AuthorizeRoomJoin bool // Whether a user account is required to join rooms
Modules []string // List of enabled modules
ModulesTable map[string]bool `json:"-"` // Map of enabled modules
GlobalRoomID string `json:"-"` // Id of the global room (not exported to Javascript)
ContentSecurityPolicy string `json:"-"` // HTML content security policy
ContentSecurityPolicyReportOnly string `json:"-"` // HTML content security policy in report only mode
RoomTypeDefault string `json:"-"` // New rooms default to this type
}
func (config *Config) WithModule(m string) bool {
if val, ok := config.ModulesTable[m]; ok && val {
return true
}
return false
}
func (config *Config) Get(request *http.Request) (int, interface{}, http.Header) {
return 200, config, http.Header{"Content-Type": {"application/json; charset=utf-8"}}
}

27
src/app/spreed-webrtc-server/connection.go → go/channelling/connection.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"container/list" "container/list"
@ -28,6 +28,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/strukturag/spreed-webrtc/go/buffercache"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -55,17 +57,17 @@ const (
type Connection interface { type Connection interface {
Index() uint64 Index() uint64
Send(Buffer) Send(buffercache.Buffer)
Close() Close()
readPump() ReadPump()
writePump() WritePump()
} }
type ConnectionHandler interface { type ConnectionHandler interface {
NewBuffer() Buffer NewBuffer() buffercache.Buffer
OnConnect(Connection) OnConnect(Connection)
OnDisconnect() OnDisconnect()
OnText(Buffer) OnText(buffercache.Buffer)
} }
type connection struct { type connection struct {
@ -116,7 +118,7 @@ func (c *connection) Close() {
break break
} }
c.queue.Remove(head) c.queue.Remove(head)
message := head.Value.(Buffer) message := head.Value.(buffercache.Buffer)
message.Decref() message.Decref()
} }
c.condition.Signal() c.condition.Signal()
@ -124,7 +126,7 @@ func (c *connection) Close() {
} }
// readPump pumps messages from the websocket connection to the hub. // readPump pumps messages from the websocket connection to the hub.
func (c *connection) readPump() { func (c *connection) ReadPump() {
c.ws.SetReadLimit(maxMessageSize) c.ws.SetReadLimit(maxMessageSize)
c.ws.SetReadDeadline(time.Now().Add(pongWait)) c.ws.SetReadDeadline(time.Now().Add(pongWait))
c.ws.SetPongHandler(func(string) error { c.ws.SetPongHandler(func(string) error {
@ -161,7 +163,7 @@ func (c *connection) readPump() {
times.PushBack(now) times.PushBack(now)
message := c.handler.NewBuffer() message := c.handler.NewBuffer()
err = readAll(message, r) err = buffercache.ReadAll(message, r)
if err != nil { if err != nil {
message.Decref() message.Decref()
break break
@ -176,7 +178,7 @@ func (c *connection) readPump() {
} }
// Write message to outbound queue. // Write message to outbound queue.
func (c *connection) Send(message Buffer) { func (c *connection) Send(message buffercache.Buffer) {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
if c.isClosed { if c.isClosed {
@ -190,11 +192,10 @@ func (c *connection) Send(message Buffer) {
message.Incref() message.Incref()
c.queue.PushBack(message) c.queue.PushBack(message)
c.condition.Signal() c.condition.Signal()
} }
// writePump pumps messages from the queue to the websocket connection. // writePump pumps messages from the queue to the websocket connection.
func (c *connection) writePump() { func (c *connection) WritePump() {
var timer *time.Timer var timer *time.Timer
ping := false ping := false
@ -232,7 +233,7 @@ func (c *connection) writePump() {
break break
} }
c.queue.Remove(head) c.queue.Remove(head)
message := head.Value.(Buffer) message := head.Value.(buffercache.Buffer)
if ping { if ping {
// Send ping. // Send ping.
ping = false ping = false

4
src/app/spreed-webrtc-server/contact.go → go/channelling/contact.go

@ -19,9 +19,7 @@
* *
*/ */
package main package channelling
import ()
type Contact struct { type Contact struct {
A string A string

27
go/channelling/contact_manager.go

@ -0,0 +1,27 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type ContactManager interface {
ContactrequestHandler(*Session, string, *DataContactRequest) error
GetContactID(*Session, string) (string, error)
}

2
src/app/spreed-webrtc-server/context.go → go/channelling/context.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
type Context struct { type Context struct {
App string // Main client script App string // Main client script

42
src/app/spreed-webrtc-server/channelling.go → go/channelling/data.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
type DataError struct { type DataError struct {
Type string Type string
@ -63,7 +63,7 @@ type DataRoom struct {
type DataOffer struct { type DataOffer struct {
Type string Type string
To string To string
Offer interface{} Offer map[string]interface{}
} }
type DataCandidate struct { type DataCandidate struct {
@ -75,7 +75,7 @@ type DataCandidate struct {
type DataAnswer struct { type DataAnswer struct {
Type string Type string
To string To string
Answer interface{} Answer map[string]interface{}
} }
type DataSelf struct { type DataSelf struct {
@ -182,27 +182,27 @@ type DataAutoCall struct {
type DataIncoming struct { type DataIncoming struct {
Type string Type string
Hello *DataHello Hello *DataHello `json:",omitempty"`
Offer *DataOffer Offer *DataOffer `json:",omitempty"`
Candidate *DataCandidate Candidate *DataCandidate `json:",omitempty"`
Answer *DataAnswer Answer *DataAnswer `json:",omitempty"`
Bye *DataBye Bye *DataBye `json:",omitempty"`
Status *DataStatus Status *DataStatus `json:",omitempty"`
Chat *DataChat Chat *DataChat `json:",omitempty"`
Conference *DataConference Conference *DataConference `json:",omitempty"`
Alive *DataAlive Alive *DataAlive `json:",omitempty"`
Authentication *DataAuthentication Authentication *DataAuthentication `json:",omitempty"`
Sessions *DataSessions Sessions *DataSessions `json:",omitempty"`
Room *DataRoom Room *DataRoom `json:",omitempty"`
Iid string `json:",omitempty"` Iid string `json:",omitempty"`
} }
type DataOutgoing struct { type DataOutgoing struct {
Data interface{} Data interface{} `json:",omitempty"`
From string From string `json:",omitempty"`
To string To string `json:",omitempty"`
Iid string `json:",omitempty"` Iid string `json:",omitempty"`
A string `json:",omitempty"` A string `json:",omitempty"`
} }
type DataSessions struct { type DataSessions struct {

72
src/app/spreed-webrtc-server/hub.go → go/channelling/hub.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"crypto/aes" "crypto/aes"
@ -36,35 +36,9 @@ import (
) )
const ( const (
turnTTL = 3600 // XXX(longsleep): Add to config file. turnTTL = 3600 // XXX(longsleep): Add to config file.
maxBroadcastPerSecond = 1000
maxUsersLength = 5000
) )
type SessionStore interface {
GetSession(id string) (session *Session, ok bool)
}
type Unicaster interface {
SessionStore
OnConnect(Client, *Session)
OnDisconnect(Client, *Session)
Unicast(to string, outgoing *DataOutgoing)
}
type ContactManager interface {
contactrequestHandler(*Session, string, *DataContactRequest) error
getContactID(*Session, string) (string, error)
}
type TurnDataCreator interface {
CreateTurnData(*Session) *DataTurn
}
type ClientStats interface {
ClientInfo(details bool) (int, map[string]*DataSession, map[string]string)
}
type Hub interface { type Hub interface {
ClientStats ClientStats
Unicaster Unicaster
@ -74,7 +48,7 @@ type Hub interface {
type hub struct { type hub struct {
OutgoingEncoder OutgoingEncoder
clients map[string]Client clients map[string]*Client
config *Config config *Config
turnSecret []byte turnSecret []byte
mutex sync.RWMutex mutex sync.RWMutex
@ -82,10 +56,9 @@ type hub struct {
} }
func NewHub(config *Config, sessionSecret, encryptionSecret, turnSecret []byte, encoder OutgoingEncoder) Hub { func NewHub(config *Config, sessionSecret, encryptionSecret, turnSecret []byte, encoder OutgoingEncoder) Hub {
h := &hub{ h := &hub{
OutgoingEncoder: encoder, OutgoingEncoder: encoder,
clients: make(map[string]Client), clients: make(map[string]*Client),
config: config, config: config,
turnSecret: turnSecret, turnSecret: turnSecret,
} }
@ -94,8 +67,8 @@ func NewHub(config *Config, sessionSecret, encryptionSecret, turnSecret []byte,
h.contacts.MaxAge(0) // Forever h.contacts.MaxAge(0) // Forever
h.contacts.HashFunc(sha256.New) h.contacts.HashFunc(sha256.New)
h.contacts.BlockFunc(aes.NewCipher) h.contacts.BlockFunc(aes.NewCipher)
return h
return h
} }
func (h *hub) ClientInfo(details bool) (clientCount int, sessions map[string]*DataSession, connections map[string]string) { func (h *hub) ClientInfo(details bool) (clientCount int, sessions map[string]*DataSession, connections map[string]string) {
@ -119,7 +92,6 @@ func (h *hub) ClientInfo(details bool) (clientCount int, sessions map[string]*Da
} }
func (h *hub) CreateTurnData(session *Session) *DataTurn { func (h *hub) CreateTurnData(session *Session) *DataTurn {
// Create turn data credentials for shared secret auth with TURN // Create turn data credentials for shared secret auth with TURN
// server. See http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 // server. See http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
// and https://code.google.com/p/rfc5766-turn-server/ REST API auth // and https://code.google.com/p/rfc5766-turn-server/ REST API auth
@ -136,20 +108,21 @@ func (h *hub) CreateTurnData(session *Session) *DataTurn {
user := fmt.Sprintf("%d:%s", expiration, id) user := fmt.Sprintf("%d:%s", expiration, id)
foo.Write([]byte(user)) foo.Write([]byte(user))
password := base64.StdEncoding.EncodeToString(foo.Sum(nil)) password := base64.StdEncoding.EncodeToString(foo.Sum(nil))
return &DataTurn{user, password, turnTTL, h.config.TurnURIs}
return &DataTurn{user, password, turnTTL, h.config.TurnURIs}
} }
func (h *hub) GetSession(id string) (session *Session, ok bool) { func (h *hub) GetSession(id string) (session *Session, ok bool) {
var client Client var client *Client
client, ok = h.GetClient(id) client, ok = h.GetClient(id)
if ok { if ok {
session = client.Session() session = client.Session()
} }
return return
} }
func (h *hub) OnConnect(client Client, session *Session) { func (h *hub) OnConnect(client *Client, session *Session) {
h.mutex.Lock() h.mutex.Lock()
log.Printf("Created client %d with id %s\n", client.Index(), session.Id) log.Printf("Created client %d with id %s\n", client.Index(), session.Id)
// Register connection or replace existing one. // Register connection or replace existing one.
@ -161,7 +134,7 @@ func (h *hub) OnConnect(client Client, session *Session) {
h.mutex.Unlock() h.mutex.Unlock()
} }
func (h *hub) OnDisconnect(client Client, session *Session) { func (h *hub) OnDisconnect(client *Client, session *Session) {
h.mutex.Lock() h.mutex.Lock()
if ec, ok := h.clients[session.Id]; ok { if ec, ok := h.clients[session.Id]; ok {
if ec == client { if ec == client {
@ -174,26 +147,32 @@ func (h *hub) OnDisconnect(client Client, session *Session) {
h.mutex.Unlock() h.mutex.Unlock()
} }
func (h *hub) GetClient(sessionID string) (client Client, ok bool) { func (h *hub) GetClient(sessionID string) (client *Client, ok bool) {
h.mutex.RLock() h.mutex.RLock()
client, ok = h.clients[sessionID] client, ok = h.clients[sessionID]
h.mutex.RUnlock() h.mutex.RUnlock()
return return
} }
func (h *hub) Unicast(to string, outgoing *DataOutgoing) { func (h *hub) Unicast(to string, outgoing *DataOutgoing, pipeline *Pipeline) {
if message, err := h.EncodeOutgoing(outgoing); err == nil { client, ok := h.GetClient(to)
client, ok := h.GetClient(to) if pipeline != nil {
if !ok { if complete := pipeline.FlushOutgoing(h, client, to, outgoing); complete {
log.Println("Unicast To not found", to)
return return
} }
}
if !ok {
log.Println("Unicast To not found", to)
return
}
if message, err := h.EncodeOutgoing(outgoing); err == nil {
client.Send(message) client.Send(message)
message.Decref() message.Decref()
} }
} }
func (h *hub) getContactID(session *Session, token string) (userid string, err error) { func (h *hub) GetContactID(session *Session, token string) (userid string, err error) {
contact := &Contact{} contact := &Contact{}
err = h.contacts.Decode("contact", token, contact) err = h.contacts.Decode("contact", token, contact)
if err != nil { if err != nil {
@ -210,11 +189,11 @@ func (h *hub) getContactID(session *Session, token string) (userid string, err e
if userid == "" { if userid == "" {
err = fmt.Errorf("Ignoring foreign contact token", contact.A, contact.B) err = fmt.Errorf("Ignoring foreign contact token", contact.A, contact.B)
} }
return return
} }
func (h *hub) contactrequestHandler(session *Session, to string, cr *DataContactRequest) error { func (h *hub) ContactrequestHandler(session *Session, to string, cr *DataContactRequest) error {
var err error var err error
if cr.Success { if cr.Success {
@ -274,5 +253,4 @@ func (h *hub) contactrequestHandler(session *Session, to string, cr *DataContact
} }
return err return err
} }

22
src/app/spreed-webrtc-server/images.go → go/channelling/imagecache.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"bytes" "bytes"
@ -43,11 +43,25 @@ type Image struct {
data []byte data []byte
} }
func (img *Image) LastChangeID() string {
return img.lastChangeId
}
func (img *Image) LastChange() time.Time {
return img.lastChange
}
func (img *Image) MimeType() string {
return img.mimetype
}
func (img *Image) Reader() *bytes.Reader {
return bytes.NewReader(img.data)
}
type ImageCache interface { type ImageCache interface {
Update(sessionId string, image string) string Update(sessionId string, image string) string
Get(imageId string) *Image Get(imageId string) *Image
Delete(sessionId string) Delete(sessionId string)
} }
@ -136,6 +150,7 @@ func (self *imageCache) Update(sessionId string, image string) string {
if ok { if ok {
result += "/" + filename result += "/" + filename
} }
return result return result
} }
@ -143,6 +158,7 @@ func (self *imageCache) Get(imageId string) *Image {
self.mutex.RLock() self.mutex.RLock()
image := self.images[imageId] image := self.images[imageId]
self.mutex.RUnlock() self.mutex.RUnlock()
return image return image
} }

266
go/channelling/pipeline.go

@ -0,0 +1,266 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
import (
"bytes"
"encoding/json"
"errors"
"log"
"sync"
"time"
"github.com/strukturag/spreed-webrtc/go/buffercache"
)
type PipelineFeedLine struct {
Seq int
Msg *DataOutgoing
}
type Pipeline struct {
PipelineManager PipelineManager
mutex sync.RWMutex
namespace string
id string
from *Session
to *Session
expires *time.Time
data []*DataSinkOutgoing
sink Sink
recvQueue chan *DataIncoming
closed bool
}
func NewPipeline(manager PipelineManager,
namespace string,
id string,
from *Session,
duration time.Duration) *Pipeline {
pipeline := &Pipeline{
PipelineManager: manager,
namespace: namespace,
id: id,
from: from,
recvQueue: make(chan *DataIncoming, 100),
}
go pipeline.receive()
pipeline.Refresh(duration)
return pipeline
}
func (pipeline *Pipeline) receive() {
// TODO(longsleep): Call to ToSession() should be avoided because it locks.
api := pipeline.PipelineManager.GetChannellingAPI()
for data := range pipeline.recvQueue {
_, err := api.OnIncoming(nil, pipeline.ToSession(), data)
if err != nil {
// TODO(longsleep): Handle reply and error.
log.Println("Pipeline receive incoming error", err)
}
}
log.Println("Pipeline receive done")
}
func (pipeline *Pipeline) GetID() string {
return pipeline.id
}
func (pipeline *Pipeline) Refresh(duration time.Duration) {
pipeline.mutex.Lock()
pipeline.refresh(duration)
pipeline.mutex.Unlock()
}
func (pipeline *Pipeline) refresh(duration time.Duration) {
expiration := time.Now().Add(duration)
pipeline.expires = &expiration
}
func (pipeline *Pipeline) Add(msg *DataSinkOutgoing) *Pipeline {
msg.Pipe = pipeline.id
pipeline.mutex.Lock()
pipeline.data = append(pipeline.data, msg)
pipeline.refresh(30 * time.Second)
pipeline.mutex.Unlock()
return pipeline
}
func (pipeline *Pipeline) Send(b buffercache.Buffer) {
// Noop.
}
func (pipeline *Pipeline) Index() uint64 {
return 0
}
func (pipeline *Pipeline) Close() {
pipeline.mutex.Lock()
if !pipeline.closed {
pipeline.expires = nil
if pipeline.sink != nil {
pipeline.sink = nil
}
close(pipeline.recvQueue)
pipeline.closed = true
log.Println("Closed pipeline")
}
pipeline.mutex.Unlock()
}
func (pipeline *Pipeline) Expired() bool {
var expired bool
pipeline.mutex.RLock()
if pipeline.expires == nil {
expired = true
} else {
expired = pipeline.expires.Before(time.Now())
}
pipeline.mutex.RUnlock()
return expired
}
func (pipeline *Pipeline) FromSession() *Session {
pipeline.mutex.RLock()
defer pipeline.mutex.RUnlock()
return pipeline.from
}
func (pipeline *Pipeline) ToSession() *Session {
pipeline.mutex.RLock()
defer pipeline.mutex.RUnlock()
return pipeline.to
}
func (pipeline *Pipeline) JSONFeed(since, limit int) ([]byte, error) {
pipeline.mutex.RLock()
var lineRaw []byte
var line *PipelineFeedLine
var buffer bytes.Buffer
var err error
data := pipeline.data[since:]
count := 0
for seq, msg := range data {
line = &PipelineFeedLine{
Seq: seq + since,
Msg: msg.Outgoing,
}
lineRaw, err = json.Marshal(line)
if err != nil {
return nil, err
}
buffer.Write(lineRaw)
buffer.WriteString("\n")
count++
if limit > 0 && count >= limit {
break
}
}
pipeline.mutex.RUnlock()
return buffer.Bytes(), nil
}
func (pipeline *Pipeline) FlushOutgoing(hub Hub, client *Client, to string, outgoing *DataOutgoing) bool {
//log.Println("Flush outgoing via pipeline", to, client == nil)
if client == nil {
sinkOutgoing := &DataSinkOutgoing{
Outgoing: outgoing,
}
pipeline.mutex.Lock()
sink := pipeline.sink
toSession := pipeline.to
fromSession := pipeline.from
for {
if sink != nil && sink.Enabled() {
// Sink it.
pipeline.mutex.Unlock()
break
}
sink, toSession = pipeline.PipelineManager.FindSinkAndSession(to)
if sink != nil {
pipeline.to = toSession
err := pipeline.attach(sink)
if err == nil {
pipeline.mutex.Unlock()
// Create incoming receiver.
sink.BindRecvChan(pipeline.recvQueue)
// Sink it.
break
}
}
// Not pipelined, do nothing.
pipeline.mutex.Unlock()
break
}
if fromSession != nil {
sinkOutgoing.FromUserid = fromSession.Userid()
}
if toSession != nil {
sinkOutgoing.ToUserid = toSession.Userid()
}
pipeline.Add(sinkOutgoing)
if sink != nil {
// Pipelined, sink data.
sink.Write(sinkOutgoing)
return true
}
}
return false
}
func (pipeline *Pipeline) Attach(sink Sink) error {
pipeline.mutex.Lock()
defer pipeline.mutex.Unlock()
// Sink existing data first.
log.Println("Attach sink to pipeline", pipeline.id)
err := pipeline.attach(sink)
if err == nil {
for _, msg := range pipeline.data {
log.Println("Flushing pipeline to sink after attach", len(pipeline.data))
sink.Write(msg)
}
}
return err
}
func (pipeline *Pipeline) attach(sink Sink) error {
if pipeline.sink != nil {
return errors.New("pipeline already attached to sink")
}
pipeline.sink = sink
return nil
}

244
go/channelling/pipeline_manager.go

@ -0,0 +1,244 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
import (
"fmt"
"log"
"sync"
"time"
)
const (
PipelineNamespaceCall = "call"
)
type PipelineManager interface {
BusManager
SessionStore
UserStore
SessionCreator
GetPipelineByID(id string) (pipeline *Pipeline, ok bool)
GetPipeline(namespace string, sender Sender, session *Session, to string) *Pipeline
FindSinkAndSession(to string) (Sink, *Session)
}
type pipelineManager struct {
BusManager
SessionStore
UserStore
SessionCreator
mutex sync.RWMutex
pipelineTable map[string]*Pipeline
sessionTable map[string]*Session
sessionByBusIDTable map[string]*Session
sessionSinkTable map[string]Sink
duration time.Duration
defaultSinkID string
enabled bool
}
func NewPipelineManager(busManager BusManager, sessionStore SessionStore, userStore UserStore, sessionCreator SessionCreator) PipelineManager {
plm := &pipelineManager{
BusManager: busManager,
SessionStore: sessionStore,
UserStore: userStore,
SessionCreator: sessionCreator,
pipelineTable: make(map[string]*Pipeline),
sessionTable: make(map[string]*Session),
sessionByBusIDTable: make(map[string]*Session),
sessionSinkTable: make(map[string]Sink),
duration: 60 * time.Second,
}
return plm
}
func (plm *pipelineManager) Start() {
plm.enabled = true
plm.start()
plm.Subscribe("channelling.session.create", plm.sessionCreate)
plm.Subscribe("channelling.session.close", plm.sessionClose)
}
func (plm *pipelineManager) cleanup() {
plm.mutex.Lock()
for id, pipeline := range plm.pipelineTable {
if pipeline.Expired() {
pipeline.Close()
delete(plm.pipelineTable, id)
}
}
plm.mutex.Unlock()
}
func (plm *pipelineManager) start() {
c := time.Tick(30 * time.Second)
go func() {
for _ = range c {
plm.cleanup()
}
}()
}
func (plm *pipelineManager) sessionCreate(subject, reply string, msg *SessionCreateRequest) {
log.Println("sessionCreate via NATS", subject, reply, msg)
if msg.Session == nil || msg.Id == "" {
return
}
var sink Sink
plm.mutex.Lock()
session, ok := plm.sessionByBusIDTable[msg.Id]
if ok {
// Remove existing session with same ID.
delete(plm.sessionTable, session.Id)
sink, _ = plm.sessionSinkTable[session.Id]
delete(plm.sessionSinkTable, session.Id)
session.Close()
}
session = plm.CreateSession(nil, "")
plm.sessionByBusIDTable[msg.Id] = session
plm.sessionTable[session.Id] = session
if sink == nil {
sink = plm.CreateSink(msg.Id)
log.Println("Created NATS sink", msg.Id)
}
if reply != "" {
// Always reply with our sink data
plm.Publish(reply, sink.Export())
}
plm.sessionSinkTable[session.Id] = sink
if msg.SetAsDefault {
plm.defaultSinkID = session.Id
log.Println("Using NATS sink as default session", session.Id)
}
plm.mutex.Unlock()
if msg.Session.Status != nil {
session.Status = msg.Session.Status
}
if msg.Session.Userid != "" {
session.SetUseridFake(msg.Session.Userid)
}
if msg.Room != nil {
room, err := session.JoinRoom(msg.Room.Name, msg.Room.Type, msg.Room.Credentials, nil)
log.Println("Joined NATS session to room", room, err)
}
session.BroadcastStatus()
}
func (plm *pipelineManager) sessionClose(subject, reply string, id string) {
log.Println("sessionClose via NATS", subject, reply, id)
if id == "" {
return
}
plm.mutex.Lock()
session, ok := plm.sessionByBusIDTable[id]
if ok {
delete(plm.sessionByBusIDTable, id)
delete(plm.sessionTable, session.Id)
if sink, ok := plm.sessionSinkTable[session.Id]; ok {
delete(plm.sessionSinkTable, session.Id)
sink.Close()
}
}
plm.mutex.Unlock()
if ok {
session.Close()
}
}
func (plm *pipelineManager) GetPipelineByID(id string) (*Pipeline, bool) {
plm.mutex.RLock()
pipeline, ok := plm.pipelineTable[id]
plm.mutex.RUnlock()
return pipeline, ok
}
func (plm *pipelineManager) PipelineID(namespace string, sender Sender, session *Session, to string) string {
return fmt.Sprintf("%s.%s.%s", namespace, session.Id, to)
}
func (plm *pipelineManager) GetPipeline(namespace string, sender Sender, session *Session, to string) *Pipeline {
if !plm.enabled {
return nil
}
id := plm.PipelineID(namespace, sender, session, to)
plm.mutex.Lock()
pipeline, ok := plm.pipelineTable[id]
if ok {
// Refresh. We do not care if the pipeline is expired.
pipeline.Refresh(plm.duration)
plm.mutex.Unlock()
return pipeline
}
log.Println("Creating pipeline", namespace, id)
pipeline = NewPipeline(plm, namespace, id, session, plm.duration)
plm.pipelineTable[id] = pipeline
plm.mutex.Unlock()
return pipeline
}
func (plm *pipelineManager) FindSinkAndSession(to string) (sink Sink, session *Session) {
plm.mutex.RLock()
var found bool
if sink, found = plm.sessionSinkTable[to]; found {
session, _ = plm.sessionTable[to]
plm.mutex.RUnlock()
if sink.Enabled() {
log.Println("Pipeline sink found via manager", sink)
return sink, session
}
} else {
plm.mutex.RUnlock()
}
if plm.defaultSinkID != "" && to != plm.defaultSinkID {
// Keep target to while returning a the default sink.
log.Println("Find sink via default sink ID", plm.defaultSinkID)
sink, _ = plm.FindSinkAndSession(plm.defaultSinkID)
if sink != nil {
if session, found = plm.GetSession(to); found {
return
}
}
}
return nil, nil
}

14
src/app/spreed-webrtc-server/room_manager.go → go/channelling/room_manager.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"fmt" "fmt"
@ -65,8 +65,8 @@ func NewRoomManager(config *Config, encoder OutgoingEncoder) RoomManager {
OutgoingEncoder: encoder, OutgoingEncoder: encoder,
roomTable: make(map[string]RoomWorker), roomTable: make(map[string]RoomWorker),
} }
if config.globalRoomID != "" { if config.GlobalRoomID != "" {
rm.globalRoomID = rm.MakeRoomID(config.globalRoomID, "") rm.globalRoomID = rm.MakeRoomID(config.GlobalRoomID, "")
} }
rm.defaultRoomID = rm.MakeRoomID("", "") rm.defaultRoomID = rm.MakeRoomID("", "")
return rm return rm
@ -111,7 +111,8 @@ func (rooms *roomManager) UpdateRoom(session *Session, room *DataRoom) (*DataRoo
return room, roomWorker.Update(room) return room, roomWorker.Update(room)
} }
// Set default room type if room was not found. // Set default room type if room was not found.
room.Type = rooms.roomTypeDefault room.Type = rooms.RoomTypeDefault
// TODO(lcooper): We should almost certainly return an error in this case. // TODO(lcooper): We should almost certainly return an error in this case.
return room, nil return room, nil
} }
@ -147,6 +148,7 @@ func (rooms *roomManager) RoomInfo(includeSessions bool) (count int, sessionInfo
sessionInfo[roomid] = room.SessionIDs() sessionInfo[roomid] = room.SessionIDs()
} }
} }
return return
} }
@ -154,6 +156,7 @@ func (rooms *roomManager) Get(roomID string) (room RoomWorker, ok bool) {
rooms.RLock() rooms.RLock()
room, ok = rooms.roomTable[roomID] room, ok = rooms.roomTable[roomID]
rooms.RUnlock() rooms.RUnlock()
return return
} }
@ -211,7 +214,8 @@ func (rooms *roomManager) GlobalUsers() []*roomUser {
func (rooms *roomManager) MakeRoomID(roomName, roomType string) string { func (rooms *roomManager) MakeRoomID(roomName, roomType string) string {
if roomType == "" { if roomType == "" {
roomType = rooms.roomTypeDefault roomType = rooms.RoomTypeDefault
} }
return fmt.Sprintf("%s:%s", roomType, roomName) return fmt.Sprintf("%s:%s", roomType, roomName)
} }

4
src/app/spreed-webrtc-server/room_manager_test.go → go/channelling/room_manager_test.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"testing" "testing"
@ -27,7 +27,7 @@ import (
func NewTestRoomManager() (RoomManager, *Config) { func NewTestRoomManager() (RoomManager, *Config) {
config := &Config{ config := &Config{
roomTypeDefault: "Room", RoomTypeDefault: "Room",
} }
return NewRoomManager(config, nil), config return NewRoomManager(config, nil), config
} }

27
src/app/spreed-webrtc-server/roomworker.go → go/channelling/roomworker.go

@ -19,18 +19,21 @@
* *
*/ */
package main package channelling
import ( import (
"crypto/subtle" "crypto/subtle"
"log" "log"
"sync" "sync"
"time" "time"
"github.com/strukturag/spreed-webrtc/go/buffercache"
) )
const ( const (
roomMaxWorkers = 10000 roomMaxWorkers = 10000
roomExpiryDuration = 60 * time.Second roomExpiryDuration = 60 * time.Second
maxUsersLength = 5000
) )
type RoomWorker interface { type RoomWorker interface {
@ -39,7 +42,7 @@ type RoomWorker interface {
Users() []*roomUser Users() []*roomUser
Update(*DataRoom) error Update(*DataRoom) error
GetUsers() []*DataSession GetUsers() []*DataSession
Broadcast(sessionID string, buf Buffer) Broadcast(sessionID string, buf buffercache.Buffer)
Join(*DataRoomCredentials, *Session, Sender) (*DataRoom, error) Join(*DataRoomCredentials, *Session, Sender) (*DataRoom, error)
Leave(sessionID string) Leave(sessionID string)
} }
@ -68,7 +71,6 @@ type roomUser struct {
} }
func NewRoomWorker(manager *roomManager, roomID, roomName, roomType string, credentials *DataRoomCredentials) RoomWorker { func NewRoomWorker(manager *roomManager, roomID, roomName, roomType string, credentials *DataRoomCredentials) RoomWorker {
log.Printf("Creating worker for room '%s'\n", roomID) log.Printf("Creating worker for room '%s'\n", roomID)
r := &roomWorker{ r := &roomWorker{
@ -91,11 +93,9 @@ func NewRoomWorker(manager *roomManager, roomID, roomName, roomType string, cred
}) })
return r return r
} }
func (r *roomWorker) Start() { func (r *roomWorker) Start() {
// Main blocking worker. // Main blocking worker.
L: L:
for { for {
@ -122,7 +122,6 @@ L:
r.timer.Stop() r.timer.Stop()
close(r.workers) close(r.workers)
//fmt.Println("Exit worker", r.Id) //fmt.Println("Exit worker", r.Id)
} }
func (r *roomWorker) SessionIDs() []string { func (r *roomWorker) SessionIDs() []string {
@ -132,23 +131,22 @@ func (r *roomWorker) SessionIDs() []string {
for id := range r.users { for id := range r.users {
sessions = append(sessions, id) sessions = append(sessions, id)
} }
return sessions return sessions
} }
func (r *roomWorker) Users() []*roomUser { func (r *roomWorker) Users() []*roomUser {
r.mutex.RLock() r.mutex.RLock()
defer r.mutex.RUnlock() defer r.mutex.RUnlock()
users := make([]*roomUser, 0, len(r.users)) users := make([]*roomUser, 0, len(r.users))
for _, user := range r.users { for _, user := range r.users {
users = append(users, user) users = append(users, user)
} }
return users
return users
} }
func (r *roomWorker) Run(f func()) bool { func (r *roomWorker) Run(f func()) bool {
select { select {
case r.workers <- f: case r.workers <- f:
return true return true
@ -156,7 +154,6 @@ func (r *roomWorker) Run(f func()) bool {
log.Printf("Room worker channel full or closed '%s'\n", r.id) log.Printf("Room worker channel full or closed '%s'\n", r.id)
return false return false
} }
} }
func (r *roomWorker) Update(room *DataRoom) error { func (r *roomWorker) Update(room *DataRoom) error {
@ -178,6 +175,7 @@ func (r *roomWorker) Update(room *DataRoom) error {
fault <- nil fault <- nil
} }
r.Run(worker) r.Run(worker)
return <-fault return <-fault
} }
@ -223,13 +221,12 @@ func (r *roomWorker) GetUsers() []*DataSession {
return <-out return <-out
} }
func (r *roomWorker) Broadcast(sessionID string, message Buffer) { func (r *roomWorker) Broadcast(sessionID string, message buffercache.Buffer) {
worker := func() { worker := func() {
r.mutex.RLock() r.mutex.RLock()
for id, user := range r.users { for id, user := range r.users {
if id == sessionID { if id == sessionID || user.Sender == nil {
// Skip broadcast to self. // Skip broadcast to self or non existing sender.
continue continue
} }
//fmt.Printf("%s\n", m.Message) //fmt.Printf("%s\n", m.Message)
@ -241,7 +238,6 @@ func (r *roomWorker) Broadcast(sessionID string, message Buffer) {
message.Incref() message.Incref()
r.Run(worker) r.Run(worker)
} }
type joinResult struct { type joinResult struct {
@ -280,6 +276,7 @@ func (r *roomWorker) Join(credentials *DataRoomCredentials, session *Session, se
} }
r.Run(worker) r.Run(worker)
result := <-results result := <-results
return result.DataRoom, result.error return result.DataRoom, result.error
} }

2
src/app/spreed-webrtc-server/roomworker_test.go → go/channelling/roomworker_test.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"testing" "testing"

2
src/app/spreed-webrtc-server/api.go → go/channelling/server/api.go

@ -19,7 +19,7 @@
* *
*/ */
package main package server
import () import ()

63
src/app/spreed-webrtc-server/config.go → go/channelling/server/config.go

@ -19,44 +19,20 @@
* *
*/ */
package main package server
import ( import (
"fmt" "fmt"
"github.com/strukturag/phoenix"
"log" "log"
"net/http"
"strings" "strings"
"time" "time"
)
type Config struct { "github.com/strukturag/spreed-webrtc/go/channelling"
Title string // Title
ver string // Version (not exported to Javascript)
S string // Static URL prefix with version
B string // Base URL
Token string // Server token
Renegotiation bool // Renegotiation flag
StunURIs []string // STUN server URIs
TurnURIs []string // TURN server URIs
Tokens bool // True when we got a tokens file
Version string // Server version number
UsersEnabled bool // Flag if users are enabled
UsersAllowRegistration bool // Flag if users can register
UsersMode string // Users mode string
DefaultRoomEnabled bool // Flag if default room ("") is enabled
Plugin string // Plugin to load
AuthorizeRoomCreation bool // Whether a user account is required to create rooms
AuthorizeRoomJoin bool // Whether a user account is required to join rooms
Modules []string // List of enabled modules
modulesTable map[string]bool // Map of enabled modules
globalRoomID string // Id of the global room (not exported to Javascript)
contentSecurityPolicy string // HTML content security policy
contentSecurityPolicyReportOnly string // HTML content security policy in report only mode
roomTypeDefault string // New rooms default to this type
}
func NewConfig(container phoenix.Container, tokens bool) *Config { "github.com/strukturag/phoenix"
)
func NewConfig(container phoenix.Container, tokens bool) *channelling.Config {
ver := container.GetStringDefault("app", "ver", "") ver := container.GetStringDefault("app", "ver", "")
version := container.Version() version := container.Version()
@ -107,9 +83,9 @@ func NewConfig(container phoenix.Container, tokens bool) *Config {
} }
log.Println("Enabled modules:", modules) log.Println("Enabled modules:", modules)
return &Config{ return &channelling.Config{
Title: container.GetStringDefault("app", "title", "Spreed WebRTC"), Title: container.GetStringDefault("app", "title", "Spreed WebRTC"),
ver: ver, Ver: ver,
S: fmt.Sprintf("static/ver=%s", ver), S: fmt.Sprintf("static/ver=%s", ver),
B: basePath, B: basePath,
Token: serverToken, Token: serverToken,
@ -126,27 +102,14 @@ func NewConfig(container phoenix.Container, tokens bool) *Config {
AuthorizeRoomCreation: container.GetBoolDefault("app", "authorizeRoomCreation", false), AuthorizeRoomCreation: container.GetBoolDefault("app", "authorizeRoomCreation", false),
AuthorizeRoomJoin: container.GetBoolDefault("app", "authorizeRoomJoin", false), AuthorizeRoomJoin: container.GetBoolDefault("app", "authorizeRoomJoin", false),
Modules: modules, Modules: modules,
modulesTable: modulesTable, ModulesTable: modulesTable,
globalRoomID: container.GetStringDefault("app", "globalRoom", ""), GlobalRoomID: container.GetStringDefault("app", "globalRoom", ""),
contentSecurityPolicy: container.GetStringDefault("app", "contentSecurityPolicy", ""), ContentSecurityPolicy: container.GetStringDefault("app", "contentSecurityPolicy", ""),
contentSecurityPolicyReportOnly: container.GetStringDefault("app", "contentSecurityPolicyReportOnly", ""), ContentSecurityPolicyReportOnly: container.GetStringDefault("app", "contentSecurityPolicyReportOnly", ""),
roomTypeDefault: "Room", RoomTypeDefault: "Room",
} }
} }
func (config *Config) Get(request *http.Request) (int, interface{}, http.Header) {
return 200, config, http.Header{"Content-Type": {"application/json; charset=utf-8"}}
}
func (config *Config) WithModule(m string) bool {
if val, ok := config.modulesTable[m]; ok && val {
return true
}
return false
}
// Helper function to clean up string arrays. // Helper function to clean up string arrays.
func trimAndRemoveDuplicates(data *[]string) { func trimAndRemoveDuplicates(data *[]string) {
found := make(map[string]bool) found := make(map[string]bool)

98
go/channelling/server/pipelines.go

@ -0,0 +1,98 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package server
import (
"encoding/json"
"net/http"
"strconv"
"github.com/strukturag/spreed-webrtc/go/channelling"
"github.com/gorilla/mux"
)
type Pipelines struct {
channelling.PipelineManager
API channelling.ChannellingAPI
}
func (pipelines *Pipelines) Get(request *http.Request) (int, interface{}, http.Header) {
vars := mux.Vars(request)
id, ok := vars["id"]
if !ok {
return http.StatusNotFound, "", nil
}
pipeline, ok := pipelines.GetPipelineByID(id)
if !ok {
return http.StatusNotFound, "", nil
}
since := 0
limit := 0
if sinceParam := request.Form.Get("since"); sinceParam != "" {
since, _ = strconv.Atoi(sinceParam)
}
if limitParam := request.Form.Get("limit"); limitParam != "" {
limit, _ = strconv.Atoi(limitParam)
}
result, err := pipeline.JSONFeed(since, limit)
if err != nil {
return http.StatusInternalServerError, err.Error(), nil
}
return http.StatusOK, result, nil
}
func (pipelines *Pipelines) Post(request *http.Request) (int, interface{}, http.Header) {
vars := mux.Vars(request)
id, ok := vars["id"]
if !ok {
return http.StatusNotFound, "", nil
}
pipeline, ok := pipelines.GetPipelineByID(id)
if !ok {
return http.StatusNotFound, "", nil
}
var incoming channelling.DataIncoming
dec := json.NewDecoder(request.Body)
if err := dec.Decode(&incoming); err != nil {
return http.StatusBadRequest, err.Error(), nil
}
result := &channelling.DataOutgoing{
From: pipeline.FromSession().Id,
Iid: incoming.Iid,
}
reply, err := pipelines.API.OnIncoming(pipeline, pipeline.ToSession(), &incoming)
if err == nil {
result.Data = reply
} else {
result.Data = err
}
return http.StatusOK, result, nil
}

6
src/app/spreed-webrtc-server/rooms.go → go/channelling/server/rooms.go

@ -19,11 +19,13 @@
* *
*/ */
package main package server
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/strukturag/spreed-webrtc/go/randomstring"
) )
type Room struct { type Room struct {
@ -36,7 +38,7 @@ type Rooms struct {
func (rooms *Rooms) Post(request *http.Request) (int, interface{}, http.Header) { func (rooms *Rooms) Post(request *http.Request) (int, interface{}, http.Header) {
name := NewRandomString(11) name := randomstring.NewRandomString(11)
return 200, &Room{name, fmt.Sprintf("/%s", name)}, http.Header{"Content-Type": {"application/json"}} return 200, &Room{name, fmt.Sprintf("/%s", name)}, http.Header{"Content-Type": {"application/json"}}
} }

19
src/app/spreed-webrtc-server/sessions.go → go/channelling/server/sessions.go

@ -19,14 +19,17 @@
* *
*/ */
package main package server
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/gorilla/mux"
"log" "log"
"net/http" "net/http"
"github.com/strukturag/spreed-webrtc/go/channelling"
"github.com/gorilla/mux"
) )
type SessionNonce struct { type SessionNonce struct {
@ -43,9 +46,9 @@ type SessionNonceRequest struct {
} }
type Sessions struct { type Sessions struct {
SessionValidator channelling.SessionValidator
SessionStore channelling.SessionStore
users *Users Users *Users
} }
// Patch is used to add a userid to a given session (login). // Patch is used to add a userid to a given session (login).
@ -87,8 +90,8 @@ func (sessions *Sessions) Patch(request *http.Request) (int, interface{}, http.H
var userid string var userid string
// Validate with users handler. // Validate with users handler.
if sessions.users.handler != nil { if sessions.Users.handler != nil {
userid, err = sessions.users.handler.Validate(&snr, request) userid, err = sessions.Users.handler.Validate(&snr, request)
if err != nil { if err != nil {
error = true error = true
log.Println("Session patch failed - users validation failed.", err) log.Println("Session patch failed - users validation failed.", err)
@ -107,7 +110,7 @@ func (sessions *Sessions) Patch(request *http.Request) (int, interface{}, http.H
if !error { if !error {
// FIXME(longsleep): Not running this might reveal error state with a timing attack. // FIXME(longsleep): Not running this might reveal error state with a timing attack.
if session, ok := sessions.GetSession(snr.Id); ok { if session, ok := sessions.GetSession(snr.Id); ok {
nonce, err = session.Authorize(sessions.Realm(), &SessionToken{Id: snr.Id, Sid: snr.Sid, Userid: userid}) nonce, err = session.Authorize(sessions.Realm(), &channelling.SessionToken{Id: snr.Id, Sid: snr.Sid, Userid: userid})
} else { } else {
err = errors.New("no such session") err = errors.New("no such session")
} }

12
src/app/spreed-webrtc-server/stats.go → go/channelling/server/stats.go

@ -19,21 +19,23 @@
* *
*/ */
package main package server
import ( import (
"net/http" "net/http"
"runtime" "runtime"
"time" "time"
"github.com/strukturag/spreed-webrtc/go/channelling"
) )
type Stat struct { type Stat struct {
details bool details bool
Runtime *RuntimeStat `json:"runtime"` Runtime *RuntimeStat `json:"runtime"`
Hub *HubStat `json:"hub"` Hub *channelling.HubStat `json:"hub"`
} }
func NewStat(details bool, statsGenerator StatsGenerator) *Stat { func NewStat(details bool, statsGenerator channelling.StatsGenerator) *Stat {
stat := &Stat{ stat := &Stat{
details: details, details: details,
Runtime: &RuntimeStat{}, Runtime: &RuntimeStat{},
@ -69,7 +71,7 @@ func (stat *RuntimeStat) Read() {
} }
type Stats struct { type Stats struct {
StatsGenerator channelling.StatsGenerator
} }
func (stats *Stats) Get(request *http.Request) (int, interface{}, http.Header) { func (stats *Stats) Get(request *http.Request) (int, interface{}, http.Header) {

2
src/app/spreed-webrtc-server/tls.go → go/channelling/server/tls.go

@ -32,7 +32,7 @@
* *
*/ */
package main package server
import ( import (
"crypto" "crypto"

8
src/app/spreed-webrtc-server/tokens.go → go/channelling/server/tokens.go

@ -19,12 +19,14 @@
* *
*/ */
package main package server
import ( import (
"log" "log"
"net/http" "net/http"
"strings" "strings"
"github.com/strukturag/spreed-webrtc/go/channelling"
) )
type Token struct { type Token struct {
@ -33,7 +35,7 @@ type Token struct {
} }
type Tokens struct { type Tokens struct {
provider TokenProvider Provider channelling.TokenProvider
} }
func (tokens Tokens) Post(request *http.Request) (int, interface{}, http.Header) { func (tokens Tokens) Post(request *http.Request) (int, interface{}, http.Header) {
@ -44,7 +46,7 @@ func (tokens Tokens) Post(request *http.Request) (int, interface{}, http.Header)
return 413, NewApiError("auth_too_large", "Auth too large"), http.Header{"Content-Type": {"application/json"}} return 413, NewApiError("auth_too_large", "Auth too large"), http.Header{"Content-Type": {"application/json"}}
} }
valid := tokens.provider(strings.ToLower(auth)) valid := tokens.Provider(strings.ToLower(auth))
if valid != "" { if valid != "" {
log.Printf("Good incoming token request: %s\n", auth) log.Printf("Good incoming token request: %s\n", auth)

56
src/app/spreed-webrtc-server/users.go → go/channelling/server/users.go

@ -19,7 +19,7 @@
* *
*/ */
package main package server
import ( import (
"crypto" "crypto"
@ -34,15 +34,18 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"github.com/longsleep/pkac"
"github.com/satori/go.uuid"
"github.com/strukturag/phoenix"
"log" "log"
"math/big" "math/big"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/strukturag/spreed-webrtc/go/channelling"
"github.com/longsleep/pkac"
"github.com/satori/go.uuid"
"github.com/strukturag/phoenix"
) )
var ( var (
@ -290,41 +293,27 @@ func (un *UserNonce) Response() (int, interface{}, http.Header) {
} }
type Users struct { type Users struct {
SessionValidator channelling.SessionStore
SessionManager channelling.SessionValidator
SessionStore channelling.SessionManager
realm string realm string
handler UsersHandler handler UsersHandler
} }
func NewUsers(sessionStore SessionStore, sessionValidator SessionValidator, sessionManager SessionManager, mode, realm string, runtime phoenix.Runtime) *Users { func NewUsers(sessionStore channelling.SessionStore, sessionValidator channelling.SessionValidator, sessionManager channelling.SessionManager, mode, realm string, runtime phoenix.Runtime) *Users {
var users = &Users{ var users = &Users{
sessionStore,
sessionValidator, sessionValidator,
sessionManager, sessionManager,
sessionStore,
realm, realm,
nil, nil,
} }
// Create handler based on mode.
var handler UsersHandler var handler UsersHandler
var err error var err error
// Create handler based on mode.
if handler, err = users.createHandler(mode, runtime); handler != nil && err == nil { if handler, err = users.createHandler(mode, runtime); handler != nil && err == nil {
users.handler = handler users.handler = handler
// Register handler Get.
sessionManager.RetrieveUsersWith(func(request *http.Request) (userid string, err error) {
userid, err = handler.Get(request)
if err != nil {
log.Printf("Failed to get userid from handler: %s", err)
} else {
if userid != "" {
log.Printf("Users handler get success: %s\n", userid)
}
}
return
})
log.Printf("Enabled users handler '%s'\n", mode) log.Printf("Enabled users handler '%s'\n", mode)
} else if err != nil { } else if err != nil {
log.Printf("Failed to enable handler '%s': %s\n", mode, err) log.Printf("Failed to enable handler '%s': %s\n", mode, err)
@ -419,6 +408,21 @@ func (users *Users) createHandler(mode string, runtime phoenix.Runtime) (handler
} }
func (users *Users) GetUserID(request *http.Request) (userid string, err error) {
if users.handler == nil {
return
}
userid, err = users.handler.Get(request)
if err != nil {
log.Printf("Failed to get userid from handler: %s", err)
} else {
if userid != "" {
log.Printf("Users handler get success: %s\n", userid)
}
}
return
}
// Post is used to create new userids for this server. // Post is used to create new userids for this server.
func (users *Users) Post(request *http.Request) (int, interface{}, http.Header) { func (users *Users) Post(request *http.Request) (int, interface{}, http.Header) {
@ -462,8 +466,8 @@ func (users *Users) Post(request *http.Request) (int, interface{}, http.Header)
nonce string nonce string
err error err error
) )
if session, ok := users.GetSession(snr.Id); ok { if session, ok := users.SessionStore.GetSession(snr.Id); ok {
nonce, err = session.Authorize(users.Realm(), &SessionToken{Id: snr.Id, Sid: snr.Sid, Userid: userid}) nonce, err = session.Authorize(users.Realm(), &channelling.SessionToken{Id: snr.Id, Sid: snr.Sid, Userid: userid})
} else { } else {
err = errors.New("no such session") err = errors.New("no such session")
} }

144
src/app/spreed-webrtc-server/session.go → go/channelling/session.go

@ -19,47 +19,54 @@
* *
*/ */
package main package channelling
import ( import (
"fmt" "fmt"
"github.com/gorilla/securecookie"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/gorilla/securecookie"
) )
var sessionNonces *securecookie.SecureCookie var sessionNonces *securecookie.SecureCookie
type Session struct { type Session struct {
SessionManager SessionManager SessionManager
Unicaster Unicaster Unicaster
Broadcaster Broadcaster Broadcaster
RoomStatusManager RoomStatusManager RoomStatusManager
buddyImages ImageCache buddyImages ImageCache
Id string Id string
Sid string Sid string
Ua string Ua string
UpdateRev uint64 UpdateRev uint64
Status interface{} Status interface{}
Nonce string Nonce string
Prio int Prio int
Hello bool Hello bool
Roomid string Roomid string
mutex sync.RWMutex mutex sync.RWMutex
userid string userid string
fake bool fake bool
stamp int64 stamp int64
attestation *SessionAttestation attestation *SessionAttestation
attestations *securecookie.SecureCookie attestations *securecookie.SecureCookie
subscriptions map[string]*Session subscriptions map[string]*Session
subscribers map[string]*Session subscribers map[string]*Session
disconnected bool disconnected bool
replaced bool replaced bool
} }
func NewSession(manager SessionManager, unicaster Unicaster, broadcaster Broadcaster, rooms RoomStatusManager, buddyImages ImageCache, attestations *securecookie.SecureCookie, id, sid string) *Session { func NewSession(manager SessionManager,
unicaster Unicaster,
broadcaster Broadcaster,
rooms RoomStatusManager,
buddyImages ImageCache,
attestations *securecookie.SecureCookie,
id,
sid string) *Session {
session := &Session{ session := &Session{
SessionManager: manager, SessionManager: manager,
Unicaster: unicaster, Unicaster: unicaster,
@ -75,8 +82,8 @@ func NewSession(manager SessionManager, unicaster Unicaster, broadcaster Broadca
subscribers: make(map[string]*Session), subscribers: make(map[string]*Session),
} }
session.NewAttestation() session.NewAttestation()
return session
return session
} }
func (s *Session) authenticated() (authenticated bool) { func (s *Session) authenticated() (authenticated bool) {
@ -152,6 +159,7 @@ func (s *Session) JoinRoom(roomName, roomType string, credentials *DataRoomCrede
} else { } else {
s.Hello = false s.Hello = false
} }
return room, err return room, err
} }
@ -186,7 +194,7 @@ func (s *Session) BroadcastStatus() {
s.mutex.RUnlock() s.mutex.RUnlock()
} }
func (s *Session) Unicast(to string, m interface{}) { func (s *Session) Unicast(to string, m interface{}, pipeline *Pipeline) {
s.mutex.RLock() s.mutex.RLock()
outgoing := &DataOutgoing{ outgoing := &DataOutgoing{
From: s.Id, From: s.Id,
@ -196,7 +204,7 @@ func (s *Session) Unicast(to string, m interface{}) {
} }
s.mutex.RUnlock() s.mutex.RUnlock()
s.Unicaster.Unicast(to, outgoing) s.Unicaster.Unicast(to, outgoing, pipeline)
} }
func (s *Session) Close() { func (s *Session) Close() {
@ -227,12 +235,12 @@ func (s *Session) Close() {
} }
for _, session := range s.subscribers { for _, session := range s.subscribers {
s.Unicaster.Unicast(session.Id, outgoing) s.Unicaster.Unicast(session.Id, outgoing, nil)
} }
for _, session := range s.subscriptions { for _, session := range s.subscriptions {
session.RemoveSubscriber(s.Id) session.RemoveSubscriber(s.Id)
s.Unicaster.Unicast(session.Id, outgoing) s.Unicaster.Unicast(session.Id, outgoing, nil)
} }
s.SessionManager.DestroySession(s.Id, s.userid) s.SessionManager.DestroySession(s.Id, s.userid)
@ -248,7 +256,6 @@ func (s *Session) Close() {
} }
func (s *Session) Replace(oldSession *Session) { func (s *Session) Replace(oldSession *Session) {
oldSession.mutex.Lock() oldSession.mutex.Lock()
if oldSession.disconnected { if oldSession.disconnected {
oldSession.mutex.Unlock() oldSession.mutex.Unlock()
@ -265,7 +272,6 @@ func (s *Session) Replace(oldSession *Session) {
// Mark old session as replaced. // Mark old session as replaced.
oldSession.replaced = true oldSession.replaced = true
oldSession.mutex.Unlock() oldSession.mutex.Unlock()
} }
func (s *Session) Update(update *SessionUpdate) uint64 { func (s *Session) Update(update *SessionUpdate) uint64 {
@ -301,11 +307,9 @@ func (s *Session) Update(update *SessionUpdate) uint64 {
s.UpdateRev++ s.UpdateRev++
return s.UpdateRev return s.UpdateRev
} }
func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { func (s *Session) Authorize(realm string, st *SessionToken) (string, error) {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
@ -324,11 +328,9 @@ func (s *Session) Authorize(realm string, st *SessionToken) (string, error) {
} }
return s.Nonce, err return s.Nonce, err
} }
func (s *Session) Authenticate(realm string, st *SessionToken, userid string) error { func (s *Session) Authenticate(realm string, st *SessionToken, userid string) error {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
@ -352,12 +354,11 @@ func (s *Session) Authenticate(realm string, st *SessionToken, userid string) er
s.userid = userid s.userid = userid
s.stamp = time.Now().Unix() s.stamp = time.Now().Unix()
s.UpdateRev++ s.UpdateRev++
return nil
return nil
} }
func (s *Session) Token() *SessionToken { func (s *Session) Token() *SessionToken {
s.mutex.RLock() s.mutex.RLock()
defer s.mutex.RUnlock() defer s.mutex.RUnlock()
@ -365,7 +366,6 @@ func (s *Session) Token() *SessionToken {
} }
func (s *Session) Data() *DataSession { func (s *Session) Data() *DataSession {
s.mutex.RLock() s.mutex.RLock()
defer s.mutex.RUnlock() defer s.mutex.RUnlock()
@ -378,25 +378,21 @@ func (s *Session) Data() *DataSession {
Prio: s.Prio, Prio: s.Prio,
stamp: s.stamp, stamp: s.stamp,
} }
} }
func (s *Session) Userid() (userid string) { func (s *Session) Userid() (userid string) {
s.mutex.RLock() s.mutex.RLock()
userid = s.userid userid = s.userid
s.mutex.RUnlock() s.mutex.RUnlock()
return
return
} }
func (s *Session) SetUseridFake(userid string) { func (s *Session) SetUseridFake(userid string) {
s.mutex.Lock() s.mutex.Lock()
s.userid = userid s.userid = userid
s.fake = true s.fake = true
s.mutex.Unlock() s.mutex.Unlock()
} }
func (s *Session) NewAttestation() { func (s *Session) NewAttestation() {
@ -406,58 +402,14 @@ func (s *Session) NewAttestation() {
s.attestation.Update() s.attestation.Update()
} }
func (s *Session) UpdateAttestation() { func (s *Session) UpdateAttestation() (string, error) {
s.mutex.Lock() s.mutex.Lock()
s.attestation.Update() defer s.mutex.Unlock()
s.mutex.Unlock() return s.attestation.Update()
}
type SessionUpdate struct {
Types []string
Ua string
Prio int
Status interface{}
}
type SessionToken struct {
Id string // Public session id.
Sid string // Secret session id.
Userid string // Public user id.
Nonce string `json:"Nonce,omitempty"` // User autentication nonce.
}
type SessionAttestation struct {
refresh int64
token string
s *Session
}
func (sa *SessionAttestation) Update() (string, error) {
token, err := sa.Encode()
if err == nil {
sa.token = token
sa.refresh = time.Now().Unix() + 180 // expires after 3 minutes
}
return token, err
}
func (sa *SessionAttestation) Token() (token string) {
if sa.refresh < time.Now().Unix() {
token, _ = sa.Update()
} else {
token = sa.token
}
return
}
func (sa *SessionAttestation) Encode() (string, error) {
return sa.s.attestations.Encode("attestation", sa.s.Id)
} }
func (sa *SessionAttestation) Decode(token string) (string, error) { func (s *Session) DecodeAttestation(token string) (string, error) {
var id string return s.attestation.Decode(token)
err := sa.s.attestations.Decode("attestation", token, &id)
return id, err
} }
func init() { func init() {

75
src/app/spreed-webrtc-server/session_manager.go → go/channelling/session_manager.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"crypto/sha256" "crypto/sha256"
@ -35,11 +35,13 @@ type UserStats interface {
type SessionManager interface { type SessionManager interface {
UserStats UserStats
RetrieveUsersWith(func(*http.Request) (string, error)) SessionStore
CreateSession(*http.Request) *Session UserStore
SessionCreator
DestroySession(sessionID, userID string) DestroySession(sessionID, userID string)
Authenticate(*Session, *SessionToken, string) error Authenticate(*Session, *SessionToken, string) error
GetUserSessions(session *Session, id string) []*DataSession GetUserSessions(session *Session, id string) []*DataSession
DecodeSessionToken(token string) (st *SessionToken)
} }
type sessionManager struct { type sessionManager struct {
@ -48,12 +50,13 @@ type sessionManager struct {
Unicaster Unicaster
Broadcaster Broadcaster
RoomStatusManager RoomStatusManager
buddyImages ImageCache buddyImages ImageCache
config *Config config *Config
userTable map[string]*User userTable map[string]*User
fakesessionTable map[string]*Session sessionTable map[string]*Session
useridRetriever func(*http.Request) (string, error) sessionByUserIDTable map[string]*Session
attestations *securecookie.SecureCookie useridRetriever func(*http.Request) (string, error)
attestations *securecookie.SecureCookie
} }
func NewSessionManager(config *Config, tickets Tickets, unicaster Unicaster, broadcaster Broadcaster, rooms RoomStatusManager, buddyImages ImageCache, sessionSecret []byte) SessionManager { func NewSessionManager(config *Config, tickets Tickets, unicaster Unicaster, broadcaster Broadcaster, rooms RoomStatusManager, buddyImages ImageCache, sessionSecret []byte) SessionManager {
@ -67,6 +70,7 @@ func NewSessionManager(config *Config, tickets Tickets, unicaster Unicaster, bro
config, config,
make(map[string]*User), make(map[string]*User),
make(map[string]*Session), make(map[string]*Session),
make(map[string]*Session),
nil, nil,
nil, nil,
} }
@ -89,32 +93,35 @@ func (sessionManager *sessionManager) UserInfo(details bool) (userCount int, use
users[userid] = user.Data() users[userid] = user.Data()
} }
} }
return return
} }
func (sessionManager *sessionManager) RetrieveUsersWith(retriever func(*http.Request) (string, error)) { // GetSession returns the client-less sessions created directly by sessionManager.
sessionManager.useridRetriever = retriever func (sessionManager *sessionManager) GetSession(id string) (*Session, bool) {
sessionManager.RLock()
defer sessionManager.RUnlock()
session, ok := sessionManager.sessionTable[id]
return session, ok
} }
func (sessionManager *sessionManager) CreateSession(request *http.Request) *Session { func (sessionManager *sessionManager) GetUser(id string) (*User, bool) {
request.ParseForm() sessionManager.RLock()
token := request.FormValue("t") defer sessionManager.RUnlock()
st := sessionManager.DecodeSessionToken(token)
user, ok := sessionManager.userTable[id]
var userid string return user, ok
if sessionManager.config.UsersEnabled { }
if sessionManager.useridRetriever != nil {
userid, _ = sessionManager.useridRetriever(request)
if userid == "" {
userid = st.Userid
}
}
}
func (sessionManager *sessionManager) CreateSession(st *SessionToken, userid string) *Session {
if st == nil {
st = sessionManager.DecodeSessionToken("")
}
session := NewSession(sessionManager, sessionManager.Unicaster, sessionManager.Broadcaster, sessionManager.RoomStatusManager, sessionManager.buddyImages, sessionManager.attestations, st.Id, st.Sid) session := NewSession(sessionManager, sessionManager.Unicaster, sessionManager.Broadcaster, sessionManager.RoomStatusManager, sessionManager.buddyImages, sessionManager.attestations, st.Id, st.Sid)
if userid != "" { if userid != "" {
// XXX(lcooper): Should errors be handled here? // Errors are ignored here, session is returned without userID when auth failed.
sessionManager.Authenticate(session, st, userid) sessionManager.Authenticate(session, st, userid)
} }
@ -127,10 +134,15 @@ func (sessionManager *sessionManager) DestroySession(sessionID, userID string) {
} }
sessionManager.Lock() sessionManager.Lock()
user, ok := sessionManager.userTable[userID] if user, ok := sessionManager.userTable[userID]; ok && user.RemoveSession(sessionID) {
if ok && user.RemoveSession(sessionID) {
delete(sessionManager.userTable, userID) delete(sessionManager.userTable, userID)
} }
if _, ok := sessionManager.sessionTable[sessionID]; ok {
delete(sessionManager.sessionTable, sessionID)
}
if session, ok := sessionManager.sessionByUserIDTable[userID]; ok && session.Id == sessionID {
delete(sessionManager.sessionByUserIDTable, sessionID)
}
sessionManager.Unlock() sessionManager.Unlock()
} }
@ -149,6 +161,7 @@ func (sessionManager *sessionManager) Authenticate(session *Session, st *Session
} }
sessionManager.Unlock() sessionManager.Unlock()
user.AddSession(session) user.AddSession(session)
return nil return nil
} }
@ -163,12 +176,13 @@ func (sessionManager *sessionManager) GetUserSessions(session *Session, userid s
if !ok { if !ok {
// No user. Create fake session. // No user. Create fake session.
sessionManager.Lock() sessionManager.Lock()
session, ok := sessionManager.fakesessionTable[userid] session, ok := sessionManager.sessionByUserIDTable[userid]
if !ok { if !ok {
st := sessionManager.FakeSessionToken(userid) st := sessionManager.FakeSessionToken(userid)
session = NewSession(sessionManager, sessionManager.Unicaster, sessionManager.Broadcaster, sessionManager.RoomStatusManager, sessionManager.buddyImages, sessionManager.attestations, st.Id, st.Sid) session = NewSession(sessionManager, sessionManager.Unicaster, sessionManager.Broadcaster, sessionManager.RoomStatusManager, sessionManager.buddyImages, sessionManager.attestations, st.Id, st.Sid)
session.SetUseridFake(st.Userid) session.SetUseridFake(st.Userid)
sessionManager.fakesessionTable[userid] = session sessionManager.sessionByUserIDTable[userid] = session
sessionManager.sessionTable[session.Id] = session
} }
sessionManager.Unlock() sessionManager.Unlock()
users = make([]*DataSession, 1, 1) users = make([]*DataSession, 1, 1)
@ -177,5 +191,6 @@ func (sessionManager *sessionManager) GetUserSessions(session *Session, userid s
// Add sessions for foreign user. // Add sessions for foreign user.
users = user.SubscribeSessions(session) users = user.SubscribeSessions(session)
} }
return return
} }

60
go/channelling/sessionattestation.go

@ -0,0 +1,60 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
import (
"time"
)
type SessionAttestation struct {
refresh int64
token string
s *Session
}
func (sa *SessionAttestation) Update() (string, error) {
token, err := sa.Encode()
if err == nil {
sa.token = token
sa.refresh = time.Now().Unix() + 180 // expires after 3 minutes
}
return token, err
}
func (sa *SessionAttestation) Token() (token string) {
if sa.refresh < time.Now().Unix() {
token, _ = sa.Update()
} else {
token = sa.token
}
return
}
func (sa *SessionAttestation) Encode() (string, error) {
return sa.s.attestations.Encode("attestation", sa.s.Id)
}
func (sa *SessionAttestation) Decode(token string) (string, error) {
var id string
err := sa.s.attestations.Decode("attestation", token, &id)
return id, err
}

26
go/channelling/sessioncreator.go

@ -0,0 +1,26 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type SessionCreator interface {
CreateSession(st *SessionToken, userid string) *Session
}

26
go/channelling/sessionstore.go

@ -0,0 +1,26 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type SessionStore interface {
GetSession(id string) (session *Session, ok bool)
}

29
go/channelling/sessiontoken.go

@ -0,0 +1,29 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type SessionToken struct {
Id string // Public session id.
Sid string // Secret session id.
Userid string // Public user id.
Nonce string `json:"Nonce,omitempty"` // User autentication nonce.
}

29
go/channelling/sessionupdate.go

@ -0,0 +1,29 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type SessionUpdate struct {
Types []string
Ua string
Prio int
Status interface{}
}

37
go/channelling/sink.go

@ -0,0 +1,37 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
import (
"github.com/nats-io/nats"
)
// Sink connects a Pipeline with end points in both directions by
// getting attached to a Pipeline.
type Sink interface {
// Write sends outgoing data on the sink
Write(*DataSinkOutgoing) error
Enabled() bool
Close()
Export() *DataSink
BindRecvChan(channel interface{}) (*nats.Subscription, error)
}

2
src/app/spreed-webrtc-server/stats_manager.go → go/channelling/stats_manager.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"sync/atomic" "sync/atomic"

17
src/app/spreed-webrtc-server/tickets.go → go/channelling/tickets.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"crypto/aes" "crypto/aes"
@ -29,6 +29,8 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/strukturag/spreed-webrtc/go/randomstring"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
) )
@ -86,7 +88,7 @@ func (tickets *tickets) DecodeSessionToken(token string) (st *SessionToken) {
} }
if st == nil || err != nil { if st == nil || err != nil {
sid := NewRandomString(32) sid := randomstring.NewRandomString(32)
id, _ := tickets.Encode("id", sid) id, _ := tickets.Encode("id", sid)
st = &SessionToken{Id: id, Sid: sid} st = &SessionToken{Id: id, Sid: sid}
log.Println("Created new session id", id) log.Println("Created new session id", id)
@ -94,13 +96,12 @@ func (tickets *tickets) DecodeSessionToken(token string) (st *SessionToken) {
return return
} }
func (tickets *tickets) FakeSessionToken(userid string) *SessionToken { func (tickets *tickets) FakeSessionToken(userid string) (st *SessionToken) {
st := &SessionToken{} sid := fmt.Sprintf("fake-%s", randomstring.NewRandomString(27))
st.Sid = fmt.Sprintf("fake-%s", NewRandomString(27)) id, _ := tickets.Encode("id", sid)
st.Id, _ = tickets.Encode("id", st.Sid) st = &SessionToken{Id: id, Sid: sid, Userid: userid}
st.Userid = userid
log.Println("Created new fake session id", st.Id) log.Println("Created new fake session id", st.Id)
return st return
} }
func (tickets *tickets) ValidateSession(id, sid string) bool { func (tickets *tickets) ValidateSession(id, sid string) bool {

7
src/app/spreed-webrtc-server/tokenprovider.go → go/channelling/tokenprovider.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"encoding/csv" "encoding/csv"
@ -47,11 +47,11 @@ func (tf *TokenFile) ReloadIfModified() error {
tf.Info = info tf.Info = info
tf.Reload() tf.Reload()
} }
return nil return nil
} }
func reloadRokens(tf *TokenFile) { func reloadRokens(tf *TokenFile) {
r, err := os.Open(tf.Path) r, err := os.Open(tf.Path)
if err != nil { if err != nil {
panic(err) panic(err)
@ -71,11 +71,9 @@ func reloadRokens(tf *TokenFile) {
for _, record := range records { for _, record := range records {
tf.Tokens[strings.ToLower(record[0])] = true tf.Tokens[strings.ToLower(record[0])] = true
} }
} }
func TokenFileProvider(filename string) TokenProvider { func TokenFileProvider(filename string) TokenProvider {
tf := &TokenFile{Path: filename} tf := &TokenFile{Path: filename}
tf.Reload = func() { reloadRokens(tf) } tf.Reload = func() { reloadRokens(tf) }
return func(token string) string { return func(token string) string {
@ -86,5 +84,4 @@ func TokenFileProvider(filename string) TokenProvider {
} }
return token return token
} }
} }

26
go/channelling/turndata.go

@ -0,0 +1,26 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type TurnDataCreator interface {
CreateTurnData(*Session) *DataTurn
}

29
go/channelling/unicaster.go

@ -0,0 +1,29 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type Unicaster interface {
SessionStore
OnConnect(*Client, *Session)
OnDisconnect(*Client, *Session)
Unicast(to string, outgoing *DataOutgoing, pipeline *Pipeline)
}

12
src/app/spreed-webrtc-server/user.go → go/channelling/user.go

@ -19,7 +19,7 @@
* *
*/ */
package main package channelling
import ( import (
"log" "log"
@ -34,13 +34,12 @@ type User struct {
} }
func NewUser(id string) *User { func NewUser(id string) *User {
user := &User{ user := &User{
Id: id, Id: id,
sessionTable: make(map[string]*Session), sessionTable: make(map[string]*Session),
} }
return user
return user
} }
// AddSession adds a session to the session table and returns true if // AddSession adds a session to the session table and returns true if
@ -54,6 +53,7 @@ func (u *User) AddSession(s *Session) bool {
first = true first = true
} }
u.mutex.Unlock() u.mutex.Unlock()
return first return first
} }
@ -68,12 +68,14 @@ func (u *User) RemoveSession(sessionID string) bool {
last = true last = true
} }
u.mutex.Unlock() u.mutex.Unlock()
return last return last
} }
func (u *User) Data() *DataUser { func (u *User) Data() *DataUser {
u.mutex.RLock() u.mutex.RLock()
defer u.mutex.RUnlock() defer u.mutex.RUnlock()
return &DataUser{ return &DataUser{
Id: u.Id, Id: u.Id,
Sessions: len(u.sessionTable), Sessions: len(u.sessionTable),
@ -81,7 +83,6 @@ func (u *User) Data() *DataUser {
} }
func (u *User) SubscribeSessions(from *Session) []*DataSession { func (u *User) SubscribeSessions(from *Session) []*DataSession {
sessions := make([]*DataSession, 0, len(u.sessionTable)) sessions := make([]*DataSession, 0, len(u.sessionTable))
u.mutex.RLock() u.mutex.RLock()
defer u.mutex.RUnlock() defer u.mutex.RUnlock()
@ -91,8 +92,8 @@ func (u *User) SubscribeSessions(from *Session) []*DataSession {
sessions = append(sessions, session.Data()) sessions = append(sessions, session.Data())
} }
sort.Sort(ByPrioAndStamp(sessions)) sort.Sort(ByPrioAndStamp(sessions))
return sessions
return sessions
} }
type ByPrioAndStamp []*DataSession type ByPrioAndStamp []*DataSession
@ -112,5 +113,6 @@ func (a ByPrioAndStamp) Less(i, j int) bool {
if a[i].Prio == a[j].Prio { if a[i].Prio == a[j].Prio {
return a[i].stamp < a[j].stamp return a[i].stamp < a[j].stamp
} }
return false return false
} }

26
go/channelling/userstore.go

@ -0,0 +1,26 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
type UserStore interface {
GetUser(id string) (user *User, ok bool)
}

140
go/natsconnection/natsconnection.go

@ -0,0 +1,140 @@
package natsconnection
import (
"errors"
"log"
"time"
"github.com/nats-io/nats"
)
// DefaultNatsEstablishTimeout is the default timeout for
// calls to EstablishNatsConnection.
var DefaultEstablishTimeout = 60 * time.Second
// DefaultNatsURL is the default NATS server URL used for
// calls to NewConnection and EstablishConnection.
var DefaultURL = nats.DefaultURL
// Connection implements the wrapped nats.Conn.
type Connection struct {
*nats.Conn
}
// EncodedConnection implements the wrapped nats.EncodedConn.
type EncodedConnection struct {
*nats.EncodedConn
}
// NewConnection creates a connetion to the default NATS server
// and tries to establish the connection. It returns the connection
// and any connection error encountered.
func NewConnection() (*Connection, error) {
opts := &nats.Options{
Url: DefaultURL,
AllowReconnect: true,
MaxReconnect: -1, // Reconnect forever.
ReconnectWait: nats.DefaultReconnectWait,
Timeout: nats.DefaultTimeout,
PingInterval: nats.DefaultPingInterval,
MaxPingsOut: nats.DefaultMaxPingOut,
SubChanLen: nats.DefaultMaxChanLen,
ClosedCB: func(conn *nats.Conn) {
log.Println("NATS connection closed")
},
DisconnectedCB: func(conn *nats.Conn) {
log.Println("NATS disconnected")
},
ReconnectedCB: func(conn *nats.Conn) {
log.Println("NATS reconnected")
},
AsyncErrorCB: func(conn *nats.Conn, sub *nats.Subscription, err error) {
log.Println("NATS async error", sub, err)
},
}
nc, err := opts.Connect()
if err != nil {
return nil, err
}
return &Connection{nc}, nil
}
// NewJSONEncodedConnection creates a JSON-encoded connetion to the
// default NATS server and tries to establish the connection. It
// returns the JSON-encoded connection and any connection error
// encountered.
func NewJSONEncodedConnection() (*EncodedConnection, error) {
nc, err := NewConnection()
if err != nil {
return nil, err
}
ec, err := nats.NewEncodedConn(nc.Conn, nats.JSON_ENCODER)
if err != nil {
return nil, err
}
return &EncodedConnection{ec}, nil
}
// EstablishConnection is a blocking way to create and establish
// connection to the default NATS server. The function will only return
// after a timeout has reached or a connection has been established. It
// returns the connection and and any timeout error encountered.
func EstablishConnection(timeout *time.Duration) (*Connection, error) {
if timeout == nil {
timeout = &DefaultEstablishTimeout
}
connch := make(chan *Connection, 1)
errch := make(chan error, 1)
go func() {
notify := true
for {
nc, err := NewConnection()
if err == nil {
connch <- nc
break
}
switch err {
case nats.ErrTimeout:
fallthrough
case nats.ErrNoServers:
if notify {
notify = false
log.Println("Waiting for NATS server to become available")
}
time.Sleep(1 * time.Second)
continue
default:
errch <- err
break
}
}
}()
select {
case conn := <-connch:
return conn, nil
case err := <-errch:
return nil, err
case <-time.After(*timeout):
return nil, errors.New("NATS connection: timeout")
}
}
// EstablishJSONEncodedConnection is a blocking way to create and establish
// JSON-encoded connection to the default NATS server. The function will
// only return after a timeout has reached or a connection has been
// established. It returns the JSON-encoded connection and and any timeout
// error encountered.
func EstablishJSONEncodedConnection(timeout *time.Duration) (*EncodedConnection, error) {
nc, err := EstablishConnection(timeout)
if err != nil {
return nil, err
}
ec, err := nats.NewEncodedConn(nc.Conn, nats.JSON_ENCODER)
if err != nil {
return nil, err
}
return &EncodedConnection{ec}, nil
}

32
src/app/spreed-webrtc-server/random.go → go/randomstring/randomstring.go

@ -19,34 +19,38 @@
* *
*/ */
package main package randomstring
import ( import (
"crypto/rand" "crypto/rand"
"math/big"
pseudoRand "math/rand" pseudoRand "math/rand"
"time" "time"
) )
const ( const (
dict = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW0123456789" dict = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
) )
func NewRandomString(length int) string { func newRandomInt(max *big.Int) int {
rand, err := rand.Int(rand.Reader, max)
if err != nil {
// Fallback to pseudo-random
return pseudoRand.Intn(int(max.Int64()))
}
return int(rand.Int64())
}
// NewRandomString returns a alphanumeric random string with
// the specified length using crypto/rand with fallback to
// math/rand on error.
func NewRandomString(length int) string {
buf := make([]byte, length) buf := make([]byte, length)
_, err := rand.Read(buf) max := big.NewInt(int64(len(dict)))
if err != nil { for i := 0; i < length; i++ {
// fallback to pseudo-random buf[i] = dict[newRandomInt(max)]
for i := 0; i < length; i++ {
buf[i] = dict[pseudoRand.Intn(len(dict))]
}
} else {
for i := 0; i < length; i++ {
buf[i] = dict[int(buf[i])%len(dict)]
}
} }
return string(buf) return string(buf)
} }
func init() { func init() {

14
server.conf.in

@ -88,6 +88,8 @@ encryptionSecret = tne-default-encryption-block-key
; Whether a user account is required to create a room. This only has an effect ; Whether a user account is required to create a room. This only has an effect
; if user accounts are enabled. Optional, defaults to false. ; if user accounts are enabled. Optional, defaults to false.
;authorizeRoomCreation = false ;authorizeRoomCreation = false
; Wether the pipelines API should be enabled. Optional, defaults to false.
;pipelinesEnabled = false
; Server token is a public random string which is used to enhance security of ; Server token is a public random string which is used to enhance security of
; server generated security tokens. When the serverToken is changed all existing ; server generated security tokens. When the serverToken is changed all existing
; nonces become invalid. Use 32 or 64 characters (eg. 16 or 32 byte hex). ; nonces become invalid. Use 32 or 64 characters (eg. 16 or 32 byte hex).
@ -194,3 +196,15 @@ enabled = false
; enable userid creation/registration. Users are created according the settings ; enable userid creation/registration. Users are created according the settings
; of the currently configured mode (see above). ; of the currently configured mode (see above).
;allowRegistration = false ;allowRegistration = false
[nats]
; Set to true, to enable triggering channelling events via NATS
;channelling_trigger = false
;channelling_trigger_subject = channelling.trigger
; NATS server URL
;url = nats://127.0.0.1:4222
; NATS connect establish timeout in seconds
;establishTimeout = 60
; Use client_id to distinguish between multipe servers. The value is sent
; together with every NATS request. Defaults to empty.
;client_id =

329
src/app/spreed-webrtc-server/channelling_api.go

@ -1,329 +0,0 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"log"
"time"
)
const (
maxConferenceSize = 100
apiVersion = 1.4 // Keep this in sync with CHANNELING-API docs.Hand
)
type ChannellingAPI interface {
OnConnect(Client, *Session) (interface{}, error)
OnDisconnect(Client, *Session)
OnIncoming(Sender, *Session, *DataIncoming) (interface{}, error)
}
type channellingAPI struct {
*Config
RoomStatusManager
SessionEncoder
SessionManager
StatsCounter
ContactManager
TurnDataCreator
Unicaster
}
func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEncoder SessionEncoder, sessionManager SessionManager, statsCounter StatsCounter, contactManager ContactManager, turnDataCreator TurnDataCreator, unicaster Unicaster) ChannellingAPI {
return &channellingAPI{
config,
roomStatus,
sessionEncoder,
sessionManager,
statsCounter,
contactManager,
turnDataCreator,
unicaster,
}
}
func (api *channellingAPI) OnConnect(client Client, session *Session) (interface{}, error) {
api.Unicaster.OnConnect(client, session)
return api.HandleSelf(session)
}
func (api *channellingAPI) OnDisconnect(client Client, session *Session) {
api.Unicaster.OnDisconnect(client, session)
}
func (api *channellingAPI) OnIncoming(sender Sender, session *Session, msg *DataIncoming) (interface{}, error) {
switch msg.Type {
case "Self":
return api.HandleSelf(session)
case "Hello":
if msg.Hello == nil {
return nil, NewDataError("bad_request", "message did not contain Hello")
}
return api.HandleHello(session, msg.Hello, sender)
case "Offer":
if msg.Offer == nil {
log.Println("Received invalid offer message.", msg)
break
}
// TODO(longsleep): Validate offer
session.Unicast(msg.Offer.To, msg.Offer)
case "Candidate":
if msg.Candidate == nil {
log.Println("Received invalid candidate message.", msg)
break
}
// TODO(longsleep): Validate candidate
session.Unicast(msg.Candidate.To, msg.Candidate)
case "Answer":
if msg.Answer == nil {
log.Println("Received invalid answer message.", msg)
break
}
// TODO(longsleep): Validate Answer
session.Unicast(msg.Answer.To, msg.Answer)
case "Users":
return api.HandleUsers(session)
case "Authentication":
if msg.Authentication == nil || msg.Authentication.Authentication == nil {
return nil, NewDataError("bad_request", "message did not contain Authentication")
}
return api.HandleAuthentication(session, msg.Authentication.Authentication)
case "Bye":
if msg.Bye == nil {
log.Println("Received invalid bye message.", msg)
break
}
session.Unicast(msg.Bye.To, msg.Bye)
case "Status":
if msg.Status == nil {
log.Println("Received invalid status message.", msg)
break
}
//log.Println("Status", msg.Status)
session.Update(&SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status})
session.BroadcastStatus()
case "Chat":
if msg.Chat == nil || msg.Chat.Chat == nil {
log.Println("Received invalid chat message.", msg)
break
}
api.HandleChat(session, msg.Chat)
case "Conference":
if msg.Conference == nil {
log.Println("Received invalid conference message.", msg)
break
}
api.HandleConference(session, msg.Conference)
case "Alive":
return msg.Alive, nil
case "Sessions":
if msg.Sessions == nil || msg.Sessions.Sessions == nil {
return nil, NewDataError("bad_request", "message did not contain Sessions")
}
return api.HandleSessions(session, msg.Sessions.Sessions)
case "Room":
if msg.Room == nil {
return nil, NewDataError("bad_request", "message did not contain Room")
}
return api.HandleRoom(session, msg.Room)
default:
log.Println("OnText unhandled message type", msg.Type)
}
return nil, nil
}
func (api *channellingAPI) HandleSelf(session *Session) (*DataSelf, error) {
token, err := api.EncodeSessionToken(session)
if err != nil {
log.Println("Error in OnRegister", err)
return nil, err
}
log.Println("Created new session token", len(token), token)
self := &DataSelf{
Type: "Self",
Id: session.Id,
Sid: session.Sid,
Userid: session.Userid(),
Suserid: api.EncodeSessionUserID(session),
Token: token,
Version: api.Version,
ApiVersion: apiVersion,
Turn: api.CreateTurnData(session),
Stun: api.StunURIs,
}
return self, nil
}
func (api *channellingAPI) HandleHello(session *Session, hello *DataHello, sender Sender) (*DataWelcome, error) {
// TODO(longsleep): Filter room id and user agent.
session.Update(&SessionUpdate{Types: []string{"Ua"}, Ua: hello.Ua})
// Compatibily for old clients.
roomName := hello.Name
if roomName == "" {
roomName = hello.Id
}
room, err := session.JoinRoom(roomName, hello.Type, hello.Credentials, sender)
if err != nil {
return nil, err
}
return &DataWelcome{
Type: "Welcome",
Room: room,
Users: api.RoomUsers(session),
}, nil
}
func (api *channellingAPI) HandleUsers(session *Session) (sessions *DataSessions, err error) {
if session.Hello {
sessions = &DataSessions{Type: "Users", Users: api.RoomUsers(session)}
} else {
err = NewDataError("not_in_room", "Cannot list users without a current room")
}
return
}
func (api *channellingAPI) HandleAuthentication(session *Session, st *SessionToken) (*DataSelf, error) {
if err := api.Authenticate(session, st, ""); err != nil {
log.Println("Authentication failed", err, st.Userid, st.Nonce)
return nil, err
}
log.Println("Authentication success", session.Userid())
self, err := api.HandleSelf(session)
if err == nil {
session.BroadcastStatus()
}
return self, err
}
func (api *channellingAPI) HandleChat(session *Session, chat *DataChat) {
// TODO(longsleep): Limit sent chat messages per incoming connection.
msg := chat.Chat
to := chat.To
if !msg.NoEcho {
session.Unicast(session.Id, chat)
}
msg.Time = time.Now().Format(time.RFC3339)
if to == "" {
// TODO(longsleep): Check if chat broadcast is allowed.
if session.Hello {
api.CountBroadcastChat()
session.Broadcast(chat)
}
} else {
if msg.Status != nil {
if msg.Status.ContactRequest != nil {
if !api.Config.WithModule("contacts") {
return
}
if err := api.contactrequestHandler(session, to, msg.Status.ContactRequest); err != nil {
log.Println("Ignoring invalid contact request.", err)
return
}
msg.Status.ContactRequest.Userid = session.Userid()
}
} else {
api.CountUnicastChat()
}
session.Unicast(to, chat)
if msg.Mid != "" {
// Send out delivery confirmation status chat message.
session.Unicast(session.Id, &DataChat{To: to, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Mid, Status: &DataChatStatus{State: "sent"}}})
}
}
}
func (api *channellingAPI) HandleConference(session *Session, conference *DataConference) {
// Check conference maximum size.
if len(conference.Conference) > maxConferenceSize {
log.Println("Refusing to create conference above limit.", len(conference.Conference))
return
}
// Send conference update to anyone.
for _, id := range conference.Conference {
if id != session.Id {
session.Unicast(id, conference)
}
}
}
func (api *channellingAPI) HandleSessions(session *Session, sessions *DataSessionsRequest) (*DataSessions, error) {
switch sessions.Type {
case "contact":
if !api.Config.WithModule("contacts") {
return nil, NewDataError("contacts_not_enabled", "incoming contacts session request with contacts disabled")
}
userID, err := api.getContactID(session, sessions.Token)
if err != nil {
return nil, err
}
return &DataSessions{
Type: "Sessions",
Users: api.GetUserSessions(session, userID),
Sessions: sessions,
}, nil
case "session":
id, err := session.attestation.Decode(sessions.Token)
if err != nil {
return nil, NewDataError("bad_attestation", err.Error())
}
session, ok := api.GetSession(id)
if !ok {
return nil, NewDataError("no_such_session", "cannot retrieve session")
}
return &DataSessions{
Type: "Sessions",
Users: []*DataSession{session.Data()},
Sessions: sessions,
}, nil
default:
return nil, NewDataError("bad_request", "unknown sessions request type")
}
}
func (api *channellingAPI) HandleRoom(session *Session, room *DataRoom) (*DataRoom, error) {
room, err := api.UpdateRoom(session, room)
if err == nil {
session.Broadcast(room)
}
return room, err
}

56
src/app/spreed-webrtc-server/handler_image.go

@ -0,0 +1,56 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"net/http"
"strconv"
"time"
"github.com/strukturag/spreed-webrtc/go/channelling"
"github.com/gorilla/mux"
)
func makeImageHandler(buddyImages channelling.ImageCache, expires time.Duration) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
image := buddyImages.Get(vars["imageid"])
if image == nil {
http.Error(w, "Unknown image", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", image.MimeType())
w.Header().Set("ETag", image.LastChangeID())
age := time.Now().Sub(image.LastChange())
if age >= time.Second {
w.Header().Set("Age", strconv.Itoa(int(age.Seconds())))
}
if expires >= time.Second {
w.Header().Set("Expires", time.Now().Add(expires).Format(time.RFC1123))
w.Header().Set("Cache-Control", "public, no-transform, max-age="+strconv.Itoa(int(expires.Seconds())))
}
http.ServeContent(w, r, "", image.LastChange(), image.Reader())
}
}

30
src/app/spreed-webrtc-server/handler_main.go

@ -0,0 +1,30 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"net/http"
)
func mainHandler(w http.ResponseWriter, r *http.Request) {
handleRoomView("", w, r)
}

91
src/app/spreed-webrtc-server/handler_room.go

@ -0,0 +1,91 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"net/http"
"github.com/strukturag/spreed-webrtc/go/channelling"
"github.com/gorilla/mux"
)
func roomHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
handleRoomView(vars["room"], w, r)
}
func handleRoomView(room string, w http.ResponseWriter, r *http.Request) {
var err error
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
w.Header().Set("Expires", "-1")
w.Header().Set("Cache-Control", "private, max-age=0")
csp := false
if config.ContentSecurityPolicy != "" {
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
csp = true
}
if config.ContentSecurityPolicyReportOnly != "" {
w.Header().Set("Content-Security-Policy-Report-Only", config.ContentSecurityPolicyReportOnly)
csp = true
}
scheme := "http"
// Detect if the request was made with SSL.
ssl := r.TLS != nil
proto, ok := r.Header["X-Forwarded-Proto"]
if ok {
ssl = proto[0] == "https"
scheme = "https"
}
// Get languages from request.
langs := getRequestLanguages(r, []string{})
if len(langs) == 0 {
langs = append(langs, "en")
}
// Prepare context to deliver to HTML..
context := &channelling.Context{Cfg: config, App: "main", Host: r.Host, Scheme: scheme, Ssl: ssl, Csp: csp, Languages: langs, Room: room}
// Get URL parameters.
r.ParseForm()
// Check if incoming request is a crawler which supports AJAX crawling.
// See https://developers.google.com/webmasters/ajax-crawling/docs/getting-started for details.
if _, ok := r.Form["_escaped_fragment_"]; ok {
// Render crawlerPage template..
err = templates.ExecuteTemplate(w, "crawlerPage", &context)
} else {
// Render mainPage template.
err = templates.ExecuteTemplate(w, "mainPage", &context)
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

79
src/app/spreed-webrtc-server/handler_sandbox.go

@ -0,0 +1,79 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"fmt"
"net/http"
"net/url"
"github.com/strukturag/spreed-webrtc/go/channelling"
"github.com/gorilla/mux"
)
func sandboxHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// NOTE(longsleep): origin_scheme is window.location.protocol (eg. https:, http:).
originURL, err := url.Parse(fmt.Sprintf("%s//%s", vars["origin_scheme"], vars["origin_host"]))
if err != nil || originURL.Scheme == "" || originURL.Host == "" {
http.Error(w, "Invalid origin path", http.StatusBadRequest)
return
}
origin := fmt.Sprintf("%s://%s", originURL.Scheme, originURL.Host)
handleSandboxView(vars["sandbox"], origin, w, r)
}
func handleSandboxView(sandbox string, origin string, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
w.Header().Set("Expires", "-1")
w.Header().Set("Cache-Control", "private, max-age=0")
sandboxTemplateName := fmt.Sprintf("%s_sandbox.html", sandbox)
// Prepare context to deliver to HTML..
if t := templates.Lookup(sandboxTemplateName); t != nil {
// CSP support for sandboxes.
var csp string
switch sandbox {
case "odfcanvas":
csp = fmt.Sprintf("default-src 'none'; script-src %s; img-src data: blob:; style-src 'unsafe-inline'", origin)
case "pdfcanvas":
csp = fmt.Sprintf("default-src 'none'; script-src %s 'unsafe-eval'; img-src 'self' data: blob:; style-src 'unsafe-inline'", origin)
default:
csp = "default-src 'none'"
}
w.Header().Set("Content-Security-Policy", csp)
// Prepare context to deliver to HTML..
context := &channelling.Context{Cfg: config, Origin: origin, Csp: true}
err := t.Execute(w, &context)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
} else {
http.Error(w, "404 Unknown Sandbox", http.StatusNotFound)
}
}

59
src/app/spreed-webrtc-server/handler_wellknown.go

@ -0,0 +1,59 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"encoding/json"
"net/http"
"net/url"
"strings"
)
func wellKnownHandler(w http.ResponseWriter, r *http.Request) {
// Detect if the request was made with SSL.
ssl := r.TLS != nil
scheme := "http"
proto, ok := r.Header["X-Forwarded-Proto"]
if ok {
ssl = proto[0] == "https"
}
if ssl {
scheme = "https"
}
// Construct our URL.
url := url.URL{
Scheme: scheme,
Host: r.Host,
Path: strings.TrimSuffix(config.B, "/"),
}
doc := &map[string]string{
"spreed-webrtc_endpoint": url.String(),
}
data, err := json.MarshalIndent(doc, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}

27
src/app/spreed-webrtc-server/ws.go → src/app/spreed-webrtc-server/handler_ws.go

@ -25,6 +25,9 @@ import (
"log" "log"
"net/http" "net/http"
"github.com/strukturag/spreed-webrtc/go/channelling"
"github.com/strukturag/spreed-webrtc/go/channelling/server"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -50,7 +53,7 @@ var (
} }
) )
func makeWSHandler(connectionCounter ConnectionCounter, sessionManager SessionManager, codec Codec, channellingAPI ChannellingAPI) http.HandlerFunc { func makeWSHandler(connectionCounter channelling.ConnectionCounter, sessionManager channelling.SessionManager, codec channelling.Codec, channellingAPI channelling.ChannellingAPI, users *server.Users) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// Validate incoming request. // Validate incoming request.
if r.Method != "GET" { if r.Method != "GET" {
@ -67,13 +70,25 @@ func makeWSHandler(connectionCounter ConnectionCounter, sessionManager SessionMa
return return
} }
r.ParseForm()
token := r.FormValue("t")
st := sessionManager.DecodeSessionToken(token)
var userid string
if users != nil {
userid, _ = users.GetUserID(r)
if userid == "" {
userid = st.Userid
}
}
// Create a new connection instance. // Create a new connection instance.
session := sessionManager.CreateSession(r) session := sessionManager.CreateSession(st, userid)
client := NewClient(codec, channellingAPI, session) client := channelling.NewClient(codec, channellingAPI, session)
conn := NewConnection(connectionCounter.CountConnection(), ws, client) conn := channelling.NewConnection(connectionCounter.CountConnection(), ws, client)
// Start pumps (readPump blocks). // Start pumps (readPump blocks).
go conn.writePump() go conn.WritePump()
conn.readPump() conn.ReadPump()
} }
} }

272
src/app/spreed-webrtc-server/main.go

@ -22,197 +22,40 @@
package main package main
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"flag" "flag"
"fmt" "fmt"
"github.com/gorilla/mux"
"github.com/strukturag/goacceptlanguageparser"
"github.com/strukturag/httputils"
"github.com/strukturag/phoenix"
"github.com/strukturag/sloth"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"net/url"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
goruntime "runtime" goruntime "runtime"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/strukturag/spreed-webrtc/go/channelling"
"github.com/strukturag/spreed-webrtc/go/channelling/api"
"github.com/strukturag/spreed-webrtc/go/channelling/server"
"github.com/strukturag/spreed-webrtc/go/natsconnection"
"github.com/gorilla/mux"
"github.com/strukturag/httputils"
"github.com/strukturag/phoenix"
"github.com/strukturag/sloth"
) )
var version = "unreleased" var version = "unreleased"
var defaultConfig = "./server.conf" var defaultConfig = "./server.conf"
var templates *template.Template var templates *template.Template
var config *Config var config *channelling.Config
// Helper to retrieve languages from request.
func getRequestLanguages(r *http.Request, supportedLanguages []string) []string {
acceptLanguageHeader, ok := r.Header["Accept-Language"]
var langs []string
if ok {
langs = goacceptlanguageparser.ParseAcceptLanguage(acceptLanguageHeader[0], supportedLanguages)
}
return langs
}
func mainHandler(w http.ResponseWriter, r *http.Request) {
handleRoomView("", w, r)
}
func roomHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
handleRoomView(vars["room"], w, r)
}
func sandboxHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// NOTE(longsleep): origin_scheme is window.location.protocol (eg. https:, http:).
originURL, err := url.Parse(fmt.Sprintf("%s//%s", vars["origin_scheme"], vars["origin_host"]))
if err != nil || originURL.Scheme == "" || originURL.Host == "" {
http.Error(w, "Invalid origin path", http.StatusBadRequest)
return
}
origin := fmt.Sprintf("%s://%s", originURL.Scheme, originURL.Host)
handleSandboxView(vars["sandbox"], origin, w, r)
}
func makeImageHandler(buddyImages ImageCache, expires time.Duration) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
image := buddyImages.Get(vars["imageid"])
if image == nil {
http.Error(w, "Unknown image", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", image.mimetype)
w.Header().Set("ETag", image.lastChangeId)
age := time.Now().Sub(image.lastChange)
if age >= time.Second {
w.Header().Set("Age", strconv.Itoa(int(age.Seconds())))
}
if expires >= time.Second {
w.Header().Set("Expires", time.Now().Add(expires).Format(time.RFC1123))
w.Header().Set("Cache-Control", "public, no-transform, max-age="+strconv.Itoa(int(expires.Seconds())))
}
http.ServeContent(w, r, "", image.lastChange, bytes.NewReader(image.data))
}
}
func handleRoomView(room string, w http.ResponseWriter, r *http.Request) {
var err error
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
w.Header().Set("Expires", "-1")
w.Header().Set("Cache-Control", "private, max-age=0")
csp := false
if config.contentSecurityPolicy != "" {
w.Header().Set("Content-Security-Policy", config.contentSecurityPolicy)
csp = true
}
if config.contentSecurityPolicyReportOnly != "" {
w.Header().Set("Content-Security-Policy-Report-Only", config.contentSecurityPolicyReportOnly)
csp = true
}
scheme := "http"
// Detect if the request was made with SSL.
ssl := r.TLS != nil
proto, ok := r.Header["X-Forwarded-Proto"]
if ok {
ssl = proto[0] == "https"
scheme = "https"
}
// Get languages from request.
langs := getRequestLanguages(r, []string{})
if len(langs) == 0 {
langs = append(langs, "en")
}
// Prepare context to deliver to HTML..
context := &Context{Cfg: config, App: "main", Host: r.Host, Scheme: scheme, Ssl: ssl, Csp: csp, Languages: langs, Room: room}
// Get URL parameters.
r.ParseForm()
// Check if incoming request is a crawler which supports AJAX crawling.
// See https://developers.google.com/webmasters/ajax-crawling/docs/getting-started for details.
if _, ok := r.Form["_escaped_fragment_"]; ok {
// Render crawlerPage template..
err = templates.ExecuteTemplate(w, "crawlerPage", &context)
} else {
// Render mainPage template.
err = templates.ExecuteTemplate(w, "mainPage", &context)
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func handleSandboxView(sandbox string, origin string, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
w.Header().Set("Expires", "-1")
w.Header().Set("Cache-Control", "private, max-age=0")
sandboxTemplateName := fmt.Sprintf("%s_sandbox.html", sandbox)
// Prepare context to deliver to HTML..
if t := templates.Lookup(sandboxTemplateName); t != nil {
// CSP support for sandboxes.
var csp string
switch sandbox {
case "odfcanvas":
csp = fmt.Sprintf("default-src 'none'; script-src %s; img-src data: blob:; style-src 'unsafe-inline'", origin)
case "pdfcanvas":
csp = fmt.Sprintf("default-src 'none'; script-src %s 'unsafe-eval'; img-src 'self' data: blob:; style-src 'unsafe-inline'", origin)
default:
csp = "default-src 'none'"
}
w.Header().Set("Content-Security-Policy", csp)
// Prepare context to deliver to HTML..
context := &Context{Cfg: config, Origin: origin, Csp: true}
err := t.Execute(w, &context)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
} else {
http.Error(w, "404 Unknown Sandbox", http.StatusNotFound)
}
}
func runner(runtime phoenix.Runtime) error { func runner(runtime phoenix.Runtime) error {
log.SetFlags(log.LstdFlags | log.Lmicroseconds) log.SetFlags(log.LstdFlags | log.Lmicroseconds)
rootFolder, err := runtime.GetString("http", "root") rootFolder, err := runtime.GetString("http", "root")
@ -245,6 +88,11 @@ func runner(runtime phoenix.Runtime) error {
}() }()
} }
pipelinesEnabled, err := runtime.GetBool("app", "pipelinesEnabled")
if err != nil {
pipelinesEnabled = false
}
var sessionSecret []byte var sessionSecret []byte
sessionSecretString, err := runtime.GetString("app", "sessionSecret") sessionSecretString, err := runtime.GetString("app", "sessionSecret")
if err != nil { if err != nil {
@ -300,14 +148,29 @@ func runner(runtime phoenix.Runtime) error {
} }
} }
var tokenProvider TokenProvider var tokenProvider channelling.TokenProvider
if tokenFile != "" { if tokenFile != "" {
log.Printf("Using token authorization from %s\n", tokenFile) log.Printf("Using token authorization from %s\n", tokenFile)
tokenProvider = TokenFileProvider(tokenFile) tokenProvider = channelling.TokenFileProvider(tokenFile)
} }
// Nats pub/sub supports.
natsChannellingTrigger, _ := runtime.GetBool("nats", "channelling_trigger")
natsChannellingTriggerSubject, _ := runtime.GetString("nats", "channelling_trigger_subject")
if natsURL, err := runtime.GetString("nats", "url"); err == nil {
if natsURL != "" {
natsconnection.DefaultURL = natsURL
}
}
if natsEstablishTimeout, err := runtime.GetInt("nats", "establishTimeout"); err == nil {
if natsEstablishTimeout != 0 {
natsconnection.DefaultEstablishTimeout = time.Duration(natsEstablishTimeout) * time.Second
}
}
natsClientId, _ := runtime.GetString("nats", "client_id")
// Load remaining configuration items. // Load remaining configuration items.
config = NewConfig(runtime, tokenProvider != nil) config = server.NewConfig(runtime, tokenProvider != nil)
// Load templates. // Load templates.
templates = template.New("") templates = template.New("")
@ -400,14 +263,23 @@ func runner(runtime phoenix.Runtime) error {
} }
// Prepare services. // Prepare services.
buddyImages := NewImageCache() apiConsumer := channelling.NewChannellingAPIConsumer()
codec := NewCodec(incomingCodecLimit) buddyImages := channelling.NewImageCache()
roomManager := NewRoomManager(config, codec) codec := channelling.NewCodec(incomingCodecLimit)
hub := NewHub(config, sessionSecret, encryptionSecret, turnSecret, codec) roomManager := channelling.NewRoomManager(config, codec)
tickets := NewTickets(sessionSecret, encryptionSecret, computedRealm) hub := channelling.NewHub(config, sessionSecret, encryptionSecret, turnSecret, codec)
sessionManager := NewSessionManager(config, tickets, hub, roomManager, roomManager, buddyImages, sessionSecret) tickets := channelling.NewTickets(sessionSecret, encryptionSecret, computedRealm)
statsManager := NewStatsManager(hub, roomManager, sessionManager) sessionManager := channelling.NewSessionManager(config, tickets, hub, roomManager, roomManager, buddyImages, sessionSecret)
channellingAPI := NewChannellingAPI(config, roomManager, tickets, sessionManager, statsManager, hub, hub, hub) statsManager := channelling.NewStatsManager(hub, roomManager, sessionManager)
busManager := channelling.NewBusManager(apiConsumer, natsClientId, natsChannellingTrigger, natsChannellingTriggerSubject)
pipelineManager := channelling.NewPipelineManager(busManager, sessionManager, sessionManager, sessionManager)
// Create API.
channellingAPI := api.New(config, roomManager, tickets, sessionManager, statsManager, hub, hub, hub, busManager, pipelineManager)
apiConsumer.SetChannellingAPI(channellingAPI)
// Start bus.
busManager.Start()
// Add handlers. // Add handlers.
r.HandleFunc("/", httputils.MakeGzipHandler(mainHandler)) r.HandleFunc("/", httputils.MakeGzipHandler(mainHandler))
@ -415,32 +287,36 @@ func runner(runtime phoenix.Runtime) error {
r.Handle("/static/{path:.*}", http.StripPrefix(config.B, httputils.FileStaticServer(http.Dir(rootFolder)))) r.Handle("/static/{path:.*}", http.StripPrefix(config.B, httputils.FileStaticServer(http.Dir(rootFolder))))
r.Handle("/robots.txt", http.StripPrefix(config.B, http.FileServer(http.Dir(path.Join(rootFolder, "static"))))) r.Handle("/robots.txt", http.StripPrefix(config.B, http.FileServer(http.Dir(path.Join(rootFolder, "static")))))
r.Handle("/favicon.ico", http.StripPrefix(config.B, http.FileServer(http.Dir(path.Join(rootFolder, "static", "img"))))) r.Handle("/favicon.ico", http.StripPrefix(config.B, http.FileServer(http.Dir(path.Join(rootFolder, "static", "img")))))
r.Handle("/ws", makeWSHandler(statsManager, sessionManager, codec, channellingAPI)) r.HandleFunc("/.well-known/spreed-configuration", wellKnownHandler)
// Simple room handler.
r.HandleFunc("/{room}", httputils.MakeGzipHandler(roomHandler))
// Sandbox handler. // Sandbox handler.
r.HandleFunc("/sandbox/{origin_scheme}/{origin_host}/{sandbox}.html", httputils.MakeGzipHandler(sandboxHandler)) r.HandleFunc("/sandbox/{origin_scheme}/{origin_host}/{sandbox}.html", httputils.MakeGzipHandler(sandboxHandler))
// Add API end points. // Add RESTful API end points.
api := sloth.NewAPI() rest := sloth.NewAPI()
api.SetMux(r.PathPrefix("/api/v1/").Subrouter()) rest.SetMux(r.PathPrefix("/api/v1/").Subrouter())
api.AddResource(&Rooms{}, "/rooms") rest.AddResource(&server.Rooms{}, "/rooms")
api.AddResource(config, "/config") rest.AddResource(config, "/config")
api.AddResourceWithWrapper(&Tokens{tokenProvider}, httputils.MakeGzipHandler, "/tokens") rest.AddResourceWithWrapper(&server.Tokens{tokenProvider}, httputils.MakeGzipHandler, "/tokens")
var users *server.Users
if config.UsersEnabled { if config.UsersEnabled {
// Create Users handler. // Create Users handler.
users := NewUsers(hub, tickets, sessionManager, config.UsersMode, serverRealm, runtime) users = server.NewUsers(hub, tickets, sessionManager, config.UsersMode, serverRealm, runtime)
api.AddResource(&Sessions{tickets, hub, users}, "/sessions/{id}/") rest.AddResource(&server.Sessions{tickets, hub, users}, "/sessions/{id}/")
if config.UsersAllowRegistration { if config.UsersAllowRegistration {
api.AddResource(users, "/users") rest.AddResource(users, "/users")
} }
} }
if statsEnabled { if statsEnabled {
api.AddResourceWithWrapper(&Stats{statsManager}, httputils.MakeGzipHandler, "/stats") rest.AddResourceWithWrapper(&server.Stats{statsManager}, httputils.MakeGzipHandler, "/stats")
log.Println("Stats are enabled!") log.Println("Stats are enabled!")
} }
if pipelinesEnabled {
pipelineManager.Start()
rest.AddResource(&server.Pipelines{pipelineManager, channellingAPI}, "/pipelines/{id}")
log.Println("Pipelines API is enabled!")
}
// Add extra/static support if configured and exists. // Add extra/static support if configured and exists.
if extraFolder != "" { if extraFolder != "" {
@ -451,6 +327,12 @@ func runner(runtime phoenix.Runtime) error {
} }
} }
// Finally add websocket handler.
r.Handle("/ws", makeWSHandler(statsManager, sessionManager, codec, channellingAPI, users))
// Simple room handler.
r.HandleFunc("/{room}", httputils.MakeGzipHandler(roomHandler))
// Map everything else to a room when it is a GET. // Map everything else to a room when it is a GET.
rooms := r.PathPrefix("/").Methods("GET").Subrouter() rooms := r.PathPrefix("/").Methods("GET").Subrouter()
rooms.HandleFunc("/{room:.*}", httputils.MakeGzipHandler(roomHandler)) rooms.HandleFunc("/{room:.*}", httputils.MakeGzipHandler(roomHandler))
@ -459,7 +341,9 @@ func runner(runtime phoenix.Runtime) error {
} }
func boot() error { func boot() error {
defaultConfigPath := flag.String("dc", "", "Default configuration file.")
configPath := flag.String("c", defaultConfig, "Configuration file.") configPath := flag.String("c", defaultConfig, "Configuration file.")
overrideConfigPath := flag.String("oc", "", "Override configuration file.")
logPath := flag.String("l", "", "Log file, defaults to stderr.") logPath := flag.String("l", "", "Log file, defaults to stderr.")
showVersion := flag.Bool("v", false, "Display version number and exit.") showVersion := flag.Bool("v", false, "Display version number and exit.")
memprofile := flag.String("memprofile", "", "Write memory profile to this file.") memprofile := flag.String("memprofile", "", "Write memory profile to this file.")
@ -476,7 +360,9 @@ func boot() error {
} }
return phoenix.NewServer("server", version). return phoenix.NewServer("server", version).
DefaultConfig(defaultConfigPath).
Config(configPath). Config(configPath).
OverrideConfig(overrideConfigPath).
Log(logPath). Log(logPath).
CpuProfile(cpuprofile). CpuProfile(cpuprofile).
MemProfile(memprofile). MemProfile(memprofile).

17
src/app/spreed-webrtc-server/utils.go

@ -0,0 +1,17 @@
package main
import (
"net/http"
"github.com/strukturag/goacceptlanguageparser"
)
// Helper to retrieve languages from request.
func getRequestLanguages(r *http.Request, supportedLanguages []string) []string {
acceptLanguageHeader, ok := r.Header["Accept-Language"]
var langs []string
if ok {
langs = goacceptlanguageparser.ParseAcceptLanguage(acceptLanguageHeader[0], supportedLanguages)
}
return langs
}

12
static/js/controllers/uicontroller.js

@ -232,6 +232,7 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
var pickupTimeout = null; var pickupTimeout = null;
var autoAcceptTimeout = null; var autoAcceptTimeout = null;
var ringerTimeout = null;
$scope.updateAutoAccept = function(id, from) { $scope.updateAutoAccept = function(id, from) {
if (id) { if (id) {
@ -726,8 +727,19 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
$scope.$on("status", function(event, status) { $scope.$on("status", function(event, status) {
if (status === "connecting" && dialerEnabled) { if (status === "connecting" && dialerEnabled) {
dialer.start(); dialer.start();
// Start accept timeout.
ringerTimeout = $timeout(function() {
console.log("Ringer timeout reached.");
mediaStream.webrtc.doHangup("ringertimeout");
$scope.$emit("notification", "pickuptimeout", {
reason: 'pickuptimeout',
from: $scope.dialing
});
}, 35000);
} else { } else {
dialer.stop(); dialer.stop();
$timeout.cancel(ringerTimeout);
ringerTimeout = null;
} }
safeApply($scope, function(scope) { safeApply($scope, function(scope) {
var old = $scope.status; var old = $scope.status;

53
static/js/services/playpromise.js

@ -0,0 +1,53 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
"use strict";
define(["underscore"], function(_) {
function noopThen() {
// Automatic playback started.
}
function noopCatch(error) {
// Automatic playback failed.
}
// playPromise
return function() {
return function(elem, thenFunc, catchFunc) {
// Starting with Chome 50 play() returns a promise.
// https://developers.google.com/web/updates/2016/03/play-returns-promise
var playPromise = elem.play()
if (playPromise !== undefined) {
if (!thenFunc) {
thenFunc = noopThen;
}
if (!catchFunc) {
catchFunc = noopCatch;
}
playPromise.then(thenFunc).catch(catchFunc);
} else {
if (thenFunc) {
_.defer(thenFunc);
}
}
return playPromise;
}
};
});

9
static/js/services/services.js

@ -70,7 +70,8 @@ define([
'services/mediadevices', 'services/mediadevices',
'services/sandbox', 'services/sandbox',
'services/dummystream', 'services/dummystream',
'services/usermedia'], function(_, 'services/usermedia',
'services/playpromise'], function(_,
desktopNotify, desktopNotify,
playSound, playSound,
safeApply, safeApply,
@ -118,7 +119,8 @@ modules,
mediaDevices, mediaDevices,
sandbox, sandbox,
dummyStream, dummyStream,
userMedia) { userMedia,
playPromise) {
var services = { var services = {
desktopNotify: desktopNotify, desktopNotify: desktopNotify,
@ -168,7 +170,8 @@ userMedia) {
mediaDevices: mediaDevices, mediaDevices: mediaDevices,
sandbox: sandbox, sandbox: sandbox,
dummyStream: dummyStream, dummyStream: dummyStream,
userMedia: userMedia userMedia: userMedia,
playPromise: playPromise
}; };
var initialize = function(angModule) { var initialize = function(angModule) {

12
static/js/services/videolayout.js

@ -59,7 +59,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
var objectFitSupport = Modernizr["object-fit"] && true; var objectFitSupport = Modernizr["object-fit"] && true;
// videoLayout // videoLayout
return ["$window", function($window) { return ["$window", "playPromise", function($window, playPromise) {
// Invisible layout (essentially shows nothing). // Invisible layout (essentially shows nothing).
var Invisible = function(container, scope, controller) {}; var Invisible = function(container, scope, controller) {};
@ -189,7 +189,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
var $mini = $(scope.mini); var $mini = $(scope.mini);
this.miniParent = $mini.parent(); this.miniParent = $mini.parent();
$mini.prependTo(scope.remoteVideos); $mini.prependTo(scope.remoteVideos);
$mini.find("video")[0].play(); playPromise($mini.find("video")[0]);
this.countSelfAsRemote = true; this.countSelfAsRemote = true;
} }
Democrazy.prototype = Object.create(OnePeople.prototype); Democrazy.prototype = Object.create(OnePeople.prototype);
@ -199,7 +199,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
OnePeople.prototype.close.call(this, container, scope, controller); OnePeople.prototype.close.call(this, container, scope, controller);
var $mini = $(scope.mini); var $mini = $(scope.mini);
$mini.appendTo(this.miniParent); $mini.appendTo(this.miniParent);
$mini.find("video")[0].play(); playPromise($mini.find("video")[0]);
this.miniParent = null; this.miniParent = null;
}; };
@ -232,12 +232,12 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
if (this.big) { if (this.big) {
// Add old video back. // Add old video back.
this.big.insertAfter(remoteVideo); this.big.insertAfter(remoteVideo);
this.big.find("video")[0].play(); playPromise(this.big.find("video")[0]);
} }
this.big = remoteVideo; this.big = remoteVideo;
remoteVideo.appendTo(this.bigVideo); remoteVideo.appendTo(this.bigVideo);
remoteVideo.find("video")[0].play(); playPromise(remoteVideo.find("video")[0]);
}; };
@ -292,7 +292,7 @@ define(["jquery", "underscore", "modernizr", "injectCSS"], function($, _, Modern
this.closed = true; this.closed = true;
if (this.big) { if (this.big) {
this.remoteVideos.append(this.big); this.remoteVideos.append(this.big);
this.big.find("video")[0].play(); playPromise(this.big.find("video")[0]);
} }
this.big = null; this.big = null;
this.bigVideo.remove() this.bigVideo.remove()

Loading…
Cancel
Save