MeteorPlugin.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Plugin to do "real time" updates using Meteor
  18. *
  19. * @category Plugin
  20. * @package GNUsocial
  21. * @author Evan Prodromou <evan@status.net>
  22. * @copyright 2010-2019 Free Software Foundation, Inc http://www.fsf.org
  23. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  24. */
  25. defined('GNUSOCIAL') || die();
  26. require_once INSTALLDIR . DIRECTORY_SEPARATOR . 'lib/modules/Realtime/RealtimePlugin.php';
  27. /**
  28. * Plugin to do realtime updates using Meteor
  29. *
  30. * @category Plugin
  31. * @package GNUsocial
  32. * @author Evan Prodromou <evan@status.net>
  33. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  34. */
  35. class MeteorPlugin extends RealtimePlugin
  36. {
  37. const PLUGIN_VERSION = '2.0.0';
  38. public $webserver = null;
  39. public $webport = null;
  40. public $controlport = null;
  41. public $controlserver = null;
  42. public $channelbase = null;
  43. public $protocol = null;
  44. public $persistent = true;
  45. protected $_socket = null;
  46. public function __construct(
  47. ?string $webserver = null,
  48. int $webport = 4670,
  49. int $controlport = 4671,
  50. ?string $controlserver = null,
  51. string $channelbase = '',
  52. string $protocol = 'http'
  53. ) {
  54. global $config;
  55. $this->webserver = (empty($webserver)) ? $config['site']['server'] : $webserver;
  56. $this->webport = $webport;
  57. $this->controlport = $controlport;
  58. $this->controlserver = (empty($controlserver)) ? $webserver : $controlserver;
  59. $this->channelbase = $channelbase;
  60. $this->protocol = $protocol;
  61. parent::__construct();
  62. }
  63. /**
  64. * Pull settings from config file/database if set.
  65. */
  66. public function initialize()
  67. {
  68. $settings = [
  69. 'webserver',
  70. 'webport',
  71. 'controlport',
  72. 'controlserver',
  73. 'channelbase',
  74. 'protocol',
  75. ];
  76. foreach ($settings as $name) {
  77. $val = common_config('meteor', $name);
  78. if ($val !== false) {
  79. $this->$name = $val;
  80. }
  81. }
  82. return parent::initialize();
  83. }
  84. public function _getScripts()
  85. {
  86. $scripts = parent::_getScripts();
  87. if ($this->protocol == 'https') {
  88. $scripts[] = 'https://' . $this->webserver . (($this->webport == 443) ? '' : ':' . $this->webport) . '/meteor.js';
  89. } else {
  90. $scripts[] = 'http://' . $this->webserver . (($this->webport == 80) ? '' : ':' . $this->webport) . '/meteor.js';
  91. }
  92. $scripts[] = $this->path('js/meteorupdater.js');
  93. return $scripts;
  94. }
  95. public function _updateInitialize($timeline, int $user_id)
  96. {
  97. $script = parent::_updateInitialize($timeline, $user_id);
  98. $ours = sprintf(
  99. "MeteorUpdater.init(%s, %s, %s, %s);",
  100. json_encode($this->webserver),
  101. json_encode($this->webport),
  102. json_encode($this->protocol),
  103. json_encode($timeline)
  104. );
  105. return $script." ".$ours;
  106. }
  107. public function _connect()
  108. {
  109. $controlserver = (empty($this->controlserver)) ? $this->webserver : $this->controlserver;
  110. $errno = $errstr = null;
  111. $timeout = 5;
  112. $flags = STREAM_CLIENT_CONNECT;
  113. if ($this->persistent) {
  114. $flags |= STREAM_CLIENT_PERSISTENT;
  115. }
  116. // May throw an exception.
  117. $this->_socket = stream_socket_client(
  118. "tcp://{$controlserver}:{$this->controlport}",
  119. $errno,
  120. $errstr,
  121. $timeout,
  122. $flags
  123. );
  124. if (!$this->_socket) {
  125. // TRANS: Exception. %1$s is the control server, %2$s is the control port.
  126. throw new Exception(sprintf(_m('Could not connect to %1$s on %2$s.'), $controlserver, $this->controlport));
  127. }
  128. }
  129. public function _publish($channel, $message)
  130. {
  131. $message = json_encode($message);
  132. $message = addslashes($message);
  133. $cmd = "ADDMESSAGE $channel $message\n";
  134. $cnt = fwrite($this->_socket, $cmd);
  135. $result = fgets($this->_socket);
  136. if (preg_match('/^ERR (.*)$/', $result, $matches)) {
  137. // TRANS: Exception. %s is the Meteor message that could not be added.
  138. throw new Exception(sprintf(_m('Error adding meteor message "%s".'), $matches[1]));
  139. }
  140. // TODO: parse and deal with result
  141. }
  142. public function _disconnect()
  143. {
  144. if (!$this->persistent) {
  145. $cnt = fwrite($this->_socket, "QUIT\n");
  146. @fclose($this->_socket);
  147. }
  148. }
  149. // Meteord flips out with default '/' separator
  150. public function _pathToChannel(array $path): string
  151. {
  152. if (!empty($this->channelbase)) {
  153. array_unshift($path, $this->channelbase);
  154. }
  155. return implode('-', $path);
  156. }
  157. public function onPluginVersion(array &$versions): bool
  158. {
  159. $versions[] = [
  160. 'name' => 'Meteor',
  161. 'version' => self::PLUGIN_VERSION,
  162. 'author' => 'Evan Prodromou',
  163. 'homepage' => GNUSOCIAL_ENGINE_REPO_URL . 'tree/master/plugins/Meteor',
  164. 'rawdescription' =>
  165. // TRANS: Plugin description.
  166. _m('Plugin to do "real time" updates using Meteor.')
  167. ];
  168. return true;
  169. }
  170. }