Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 16 additions & 82 deletions core/src/OC/eventsource.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,88 +20,31 @@ function OCEventSource(src, data) {
let joinChar
this.typelessListeners = []
this.closed = false
this.listeners = {}
if (data) {
for (name in data) {
dataStr += name + '=' + encodeURIComponent(data[name]) + '&'
}
}
dataStr += 'requesttoken=' + encodeURIComponent(getRequestToken())
if (!this.useFallBack && typeof EventSource !== 'undefined') {
joinChar = '&'
if (src.indexOf('?') === -1) {
joinChar = '?'
}
this.source = new EventSource(src + joinChar + dataStr)
this.source.onmessage = function(e) {
for (let i = 0; i < this.typelessListeners.length; i++) {
this.typelessListeners[i](JSON.parse(e.data))
}
}.bind(this)
} else {
const iframeId = 'oc_eventsource_iframe_' + OCEventSource.iframeCount
OCEventSource.fallBackSources[OCEventSource.iframeCount] = this
const iframe = document.createElement('iframe')
iframe.id = iframeId
iframe.style.display = 'none'

joinChar = '&'
if (src.indexOf('?') === -1) {
joinChar = '?'
}
iframe.src = src + joinChar + 'fallback=true&fallback_id=' + OCEventSource.iframeCount + '&' + dataStr

this.iframe = iframe
document.body.appendChild(this.iframe)
this.useFallBack = true
OCEventSource.iframeCount++
joinChar = '&'
if (src.indexOf('?') === -1) {
joinChar = '?'
}
this.source = new EventSource(src + joinChar + dataStr)
this.source.onmessage = function(e) {
for (let i = 0; i < this.typelessListeners.length; i++) {
this.typelessListeners[i](JSON.parse(e.data))
}
}.bind(this)
// add close listener
this.listen('__internal__', function(data) {
if (data === 'close') {
this.close()
}
}.bind(this))
}
OCEventSource.fallBackSources = []
OCEventSource.iframeCount = 0// number of fallback iframes
OCEventSource.fallBackCallBack = function(id, type, data) {
OCEventSource.fallBackSources[id].fallBackCallBack(type, data)
}
OCEventSource.prototype = {
typelessListeners: [],
iframe: null,
listeners: {}, // only for fallback
useFallBack: false,
/**
* Fallback callback for browsers that don't have the
* native EventSource object.
*
* Calls the registered listeners.
*
* @private
* @param {string} type event type
* @param {object} data received data
*/
fallBackCallBack: function(type, data) {
let i
// ignore messages that might appear after closing
if (this.closed) {
return
}
if (type) {
if (typeof this.listeners.done !== 'undefined') {
for (i = 0; i < this.listeners[type].length; i++) {
this.listeners[type][i](data)
}
}
} else {
for (i = 0; i < this.typelessListeners.length; i++) {
this.typelessListeners[i](data)
}
}
},
lastLength: 0, // for fallback
/**
* Listen to a given type of events.
*
Expand All @@ -111,20 +54,13 @@ OCEventSource.prototype = {
listen: function(type, callback) {
if (callback && callback.call) {
if (type) {
if (this.useFallBack) {
if (!this.listeners[type]) {
this.listeners[type] = []
this.source.addEventListener(type, function(e) {
if (typeof e.data !== 'undefined') {
callback(JSON.parse(e.data))
} else {
callback('')
}
this.listeners[type].push(callback)
} else {
this.source.addEventListener(type, function(e) {
if (typeof e.data !== 'undefined') {
callback(JSON.parse(e.data))
} else {
callback('')
}
}, false)
}
}, false)
} else {
this.typelessListeners.push(callback)
}
Expand All @@ -135,9 +71,7 @@ OCEventSource.prototype = {
*/
close: function() {
this.closed = true
if (typeof this.source !== 'undefined') {
this.source.close()
}
this.source.close()
},
}

Expand Down
4 changes: 2 additions & 2 deletions dist/9396-9396.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/9396-9396.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/core-login.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/core-login.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/core-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/core-main.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/core-update.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/core-update.js.map

Large diffs are not rendered by default.

49 changes: 7 additions & 42 deletions lib/private/EventSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2020-2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
Expand All @@ -12,8 +12,6 @@
use OCP\IRequest;

class EventSource implements IEventSource {
private bool $fallback = false;
private int $fallBackId = 0;
private bool $started = false;

public function __construct(
Expand All @@ -31,25 +29,7 @@ protected function init(): void {
\OC_Util::obEnd();
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');
$this->fallback = isset($_GET['fallback']) && $_GET['fallback'] == 'true';
if ($this->fallback) {
$this->fallBackId = (int)$_GET['fallback_id'];
/**
* FIXME: The default content-security-policy of ownCloud forbids inline
* JavaScript for security reasons. IE starting on Windows 10 will
* however also obey the CSP which will break the event source fallback.
*
* As a workaround thus we set a custom policy which allows the execution
* of inline JavaScript.
*
* @link https://github.com/owncloud/core/issues/14286
*/
header("Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'");
header('Content-Type: text/html');
echo str_repeat('<span></span>' . PHP_EOL, 10); //dummy data to keep IE happy
} else {
header('Content-Type: text/event-stream');
}
header('Content-Type: text/event-stream');
if (!$this->request->passesStrictCookieCheck()) {
header('Location: ' . \OC::$WEBROOT);
exit();
Expand All @@ -63,16 +43,10 @@ protected function init(): void {
}

/**
* send a message to the client
*
* @param string $type
* @param mixed $data
*
* @throws \BadMethodCallException
* if only one parameter is given, a typeless message will be send with that parameter as data
* @suppress PhanDeprecatedFunction
*/
public function send($type, $data = null) {
public function send(string $type, mixed $data = null): void {
if ($data && !preg_match('/^[A-Za-z0-9_]+$/', $type)) {
throw new \BadMethodCallException('Type needs to be alphanumeric (' . $type . ')');
}
Expand All @@ -81,24 +55,15 @@ public function send($type, $data = null) {
$data = $type;
$type = null;
}
if ($this->fallback) {
$response = '<script type="text/javascript">window.parent.OC.EventSource.fallBackCallBack('
. $this->fallBackId . ',"' . ($type ?? '') . '",' . json_encode($data, JSON_HEX_TAG) . ')</script>' . PHP_EOL;
echo $response;
} else {
if ($type) {
echo 'event: ' . $type . PHP_EOL;
}
echo 'data: ' . json_encode($data, JSON_HEX_TAG) . PHP_EOL;
if ($type) {
echo 'event: ' . $type . PHP_EOL;
}
echo 'data: ' . json_encode($data, JSON_HEX_TAG) . PHP_EOL;
echo PHP_EOL;
flush();
}

/**
* close the connection of the event source
*/
public function close() {
public function close(): void {
$this->send('__internal__', 'close'); //server side closing can be an issue, let the client do it
}
}
27 changes: 14 additions & 13 deletions lib/public/IEventSource.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
Expand All @@ -8,31 +9,31 @@
namespace OCP;

/**
* wrapper for server side events (http://en.wikipedia.org/wiki/Server-sent_events)
* includes a fallback for older browsers and IE
* Wrapper for Server-Sent Events (SSE).
*
* use server side events with caution, to many open requests can hang the server
* Use SSE with caution: too many concurrent open requests can overload or stall the server.
*
* The event source will initialize the connection to the client when the first data is sent
* The connection is opened lazily when the first event is sent.
*
* @see https://developer.mozilla.org/docs/Web/API/Server-sent_events
* @since 8.0.0
*/
interface IEventSource {
/**
* send a message to the client
* Sends an event to the client.
*
* @param string $type One of success, notice, error, failure and done. Used in core/js/update.js
* @param mixed $data
* @return void
* @param string $type Event type/name.
* @param mixed $data Event payload.
*
* if only one parameter is given, a typeless message will be send with that parameter as data
* If only one argument is provided, it is sent as a typeless payload (legacy behavior).
* @since 8.0.0
*/
public function send($type, $data = null);
public function send(string $type, mixed $data = null): void;

/**
* close the connection of the event source
* @return void
* Closes the SSE connection.
*
* @since 8.0.0
*/
public function close();
public function close(): void;
}
Loading