diff options
author | Chocobozzz <me@florianbigard.com> | 2022-10-11 11:07:40 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-10-11 11:11:04 +0200 |
commit | 9d4c60dccc8e7e777ad139a82e9f61feda9d21fc (patch) | |
tree | 2931338f340b398d36c43575fea95cf1fbbfeb4c | |
parent | 9866921cbf3f8f0925f7ffb3a231d5dfe2d30953 (diff) | |
download | PeerTube-9d4c60dccc8e7e777ad139a82e9f61feda9d21fc.tar.gz PeerTube-9d4c60dccc8e7e777ad139a82e9f61feda9d21fc.tar.zst PeerTube-9d4c60dccc8e7e777ad139a82e9f61feda9d21fc.zip |
Add ability for plugins to register ws routes
-rw-r--r-- | client/src/app/core/plugins/plugin.service.ts | 5 | ||||
-rw-r--r-- | client/src/standalone/videos/shared/peertube-plugin.ts | 1 | ||||
-rw-r--r-- | client/src/types/register-client-option.model.ts | 3 | ||||
-rw-r--r-- | server.ts | 4 | ||||
-rw-r--r-- | server/lib/plugins/plugin-helpers-builder.ts | 13 | ||||
-rw-r--r-- | server/lib/plugins/plugin-manager.ts | 31 | ||||
-rw-r--r-- | server/lib/plugins/register-helpers.ts | 21 | ||||
-rw-r--r-- | server/tests/fixtures/peertube-plugin-test-websocket/main.js | 36 | ||||
-rw-r--r-- | server/tests/fixtures/peertube-plugin-test-websocket/package.json | 20 | ||||
-rw-r--r-- | server/tests/plugins/index.ts | 1 | ||||
-rw-r--r-- | server/tests/plugins/plugin-websocket.ts | 70 | ||||
-rw-r--r-- | server/types/plugins/index.ts | 1 | ||||
-rw-r--r-- | server/types/plugins/register-server-option.model.ts | 14 | ||||
-rw-r--r-- | server/types/plugins/register-server-websocket-route.model.ts | 8 | ||||
-rw-r--r-- | support/doc/plugins/guide.md | 36 | ||||
-rw-r--r-- | support/nginx/peertube | 5 |
16 files changed, 262 insertions, 7 deletions
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index dadc2a41d..1e79cbf79 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -202,6 +202,11 @@ export class PluginService implements ClientHook { | |||
202 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` | 202 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` |
203 | }, | 203 | }, |
204 | 204 | ||
205 | getBaseWebSocketRoute: () => { | ||
206 | const pathPrefix = PluginsManager.getPluginPathPrefix(pluginInfo.isTheme) | ||
207 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/ws` | ||
208 | }, | ||
209 | |||
205 | getBasePluginClientPath: () => { | 210 | getBasePluginClientPath: () => { |
206 | return '/p' | 211 | return '/p' |
207 | }, | 212 | }, |
diff --git a/client/src/standalone/videos/shared/peertube-plugin.ts b/client/src/standalone/videos/shared/peertube-plugin.ts index 968854ce8..daf6f2b03 100644 --- a/client/src/standalone/videos/shared/peertube-plugin.ts +++ b/client/src/standalone/videos/shared/peertube-plugin.ts | |||
@@ -43,6 +43,7 @@ export class PeerTubePlugin { | |||
43 | return { | 43 | return { |
44 | getBaseStaticRoute: unimplemented, | 44 | getBaseStaticRoute: unimplemented, |
45 | getBaseRouterRoute: unimplemented, | 45 | getBaseRouterRoute: unimplemented, |
46 | getBaseWebSocketRoute: unimplemented, | ||
46 | getBasePluginClientPath: unimplemented, | 47 | getBasePluginClientPath: unimplemented, |
47 | 48 | ||
48 | getSettings: () => { | 49 | getSettings: () => { |
diff --git a/client/src/types/register-client-option.model.ts b/client/src/types/register-client-option.model.ts index 2460a7499..2c09f15a7 100644 --- a/client/src/types/register-client-option.model.ts +++ b/client/src/types/register-client-option.model.ts | |||
@@ -24,6 +24,9 @@ export type RegisterClientHelpers = { | |||
24 | 24 | ||
25 | getBaseRouterRoute: () => string | 25 | getBaseRouterRoute: () => string |
26 | 26 | ||
27 | // PeerTube >= 5.0 | ||
28 | getBaseWebSocketRoute: () => string | ||
29 | |||
27 | getBasePluginClientPath: () => string | 30 | getBasePluginClientPath: () => string |
28 | 31 | ||
29 | isLoggedIn: () => boolean | 32 | isLoggedIn: () => boolean |
@@ -328,6 +328,10 @@ async function startApplication () { | |||
328 | GeoIPUpdateScheduler.Instance.enable() | 328 | GeoIPUpdateScheduler.Instance.enable() |
329 | OpenTelemetryMetrics.Instance.registerMetrics() | 329 | OpenTelemetryMetrics.Instance.registerMetrics() |
330 | 330 | ||
331 | PluginManager.Instance.init(server) | ||
332 | // Before PeerTubeSocket init | ||
333 | PluginManager.Instance.registerWebSocketRouter() | ||
334 | |||
331 | PeerTubeSocket.Instance.init(server) | 335 | PeerTubeSocket.Instance.init(server) |
332 | VideoViewsManager.Instance.init() | 336 | VideoViewsManager.Instance.init() |
333 | 337 | ||
diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts index 35945422c..7b1def6e3 100644 --- a/server/lib/plugins/plugin-helpers-builder.ts +++ b/server/lib/plugins/plugin-helpers-builder.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { Server } from 'http' | ||
2 | import { join } from 'path' | 3 | import { join } from 'path' |
3 | import { ffprobePromise } from '@server/helpers/ffmpeg/ffprobe-utils' | 4 | import { ffprobePromise } from '@server/helpers/ffmpeg/ffprobe-utils' |
4 | import { buildLogger } from '@server/helpers/logger' | 5 | import { buildLogger } from '@server/helpers/logger' |
@@ -17,12 +18,12 @@ import { MPlugin, MVideo, UserNotificationModelForApi } from '@server/types/mode | |||
17 | import { PeerTubeHelpers } from '@server/types/plugins' | 18 | import { PeerTubeHelpers } from '@server/types/plugins' |
18 | import { VideoBlacklistCreate, VideoStorage } from '@shared/models' | 19 | import { VideoBlacklistCreate, VideoStorage } from '@shared/models' |
19 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist' | 20 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist' |
21 | import { PeerTubeSocket } from '../peertube-socket' | ||
20 | import { ServerConfigManager } from '../server-config-manager' | 22 | import { ServerConfigManager } from '../server-config-manager' |
21 | import { blacklistVideo, unblacklistVideo } from '../video-blacklist' | 23 | import { blacklistVideo, unblacklistVideo } from '../video-blacklist' |
22 | import { VideoPathManager } from '../video-path-manager' | 24 | import { VideoPathManager } from '../video-path-manager' |
23 | import { PeerTubeSocket } from '../peertube-socket' | ||
24 | 25 | ||
25 | function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers { | 26 | function buildPluginHelpers (httpServer: Server, pluginModel: MPlugin, npmName: string): PeerTubeHelpers { |
26 | const logger = buildPluginLogger(npmName) | 27 | const logger = buildPluginLogger(npmName) |
27 | 28 | ||
28 | const database = buildDatabaseHelpers() | 29 | const database = buildDatabaseHelpers() |
@@ -30,7 +31,7 @@ function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHel | |||
30 | 31 | ||
31 | const config = buildConfigHelpers() | 32 | const config = buildConfigHelpers() |
32 | 33 | ||
33 | const server = buildServerHelpers() | 34 | const server = buildServerHelpers(httpServer) |
34 | 35 | ||
35 | const moderation = buildModerationHelpers() | 36 | const moderation = buildModerationHelpers() |
36 | 37 | ||
@@ -69,8 +70,10 @@ function buildDatabaseHelpers () { | |||
69 | } | 70 | } |
70 | } | 71 | } |
71 | 72 | ||
72 | function buildServerHelpers () { | 73 | function buildServerHelpers (httpServer: Server) { |
73 | return { | 74 | return { |
75 | getHTTPServer: () => httpServer, | ||
76 | |||
74 | getServerActor: () => getServerActor() | 77 | getServerActor: () => getServerActor() |
75 | } | 78 | } |
76 | } | 79 | } |
@@ -218,6 +221,8 @@ function buildPluginRelatedHelpers (plugin: MPlugin, npmName: string) { | |||
218 | 221 | ||
219 | getBaseRouterRoute: () => `/plugins/${plugin.name}/${plugin.version}/router/`, | 222 | getBaseRouterRoute: () => `/plugins/${plugin.name}/${plugin.version}/router/`, |
220 | 223 | ||
224 | getBaseWebSocketRoute: () => `/plugins/${plugin.name}/${plugin.version}/ws/`, | ||
225 | |||
221 | getDataDirectoryPath: () => join(CONFIG.STORAGE.PLUGINS_DIR, 'data', npmName) | 226 | getDataDirectoryPath: () => join(CONFIG.STORAGE.PLUGINS_DIR, 'data', npmName) |
222 | } | 227 | } |
223 | } | 228 | } |
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index a46b97fa4..c4d9b6574 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { createReadStream, createWriteStream } from 'fs' | 2 | import { createReadStream, createWriteStream } from 'fs' |
3 | import { ensureDir, outputFile, readJSON } from 'fs-extra' | 3 | import { ensureDir, outputFile, readJSON } from 'fs-extra' |
4 | import { Server } from 'http' | ||
4 | import { basename, join } from 'path' | 5 | import { basename, join } from 'path' |
5 | import { decachePlugin } from '@server/helpers/decache' | 6 | import { decachePlugin } from '@server/helpers/decache' |
6 | import { ApplicationModel } from '@server/models/application/application' | 7 | import { ApplicationModel } from '@server/models/application/application' |
@@ -67,9 +68,37 @@ export class PluginManager implements ServerHook { | |||
67 | private hooks: { [name: string]: HookInformationValue[] } = {} | 68 | private hooks: { [name: string]: HookInformationValue[] } = {} |
68 | private translations: PluginLocalesTranslations = {} | 69 | private translations: PluginLocalesTranslations = {} |
69 | 70 | ||
71 | private server: Server | ||
72 | |||
70 | private constructor () { | 73 | private constructor () { |
71 | } | 74 | } |
72 | 75 | ||
76 | init (server: Server) { | ||
77 | this.server = server | ||
78 | } | ||
79 | |||
80 | registerWebSocketRouter () { | ||
81 | this.server.on('upgrade', (request, socket, head) => { | ||
82 | const url = request.url | ||
83 | |||
84 | const matched = url.match(`/plugins/([^/]+)/([^/]+/)?ws(/.*)`) | ||
85 | if (!matched) return | ||
86 | |||
87 | const npmName = PluginModel.buildNpmName(matched[1], PluginType.PLUGIN) | ||
88 | const subRoute = matched[3] | ||
89 | |||
90 | const result = this.getRegisteredPluginOrTheme(npmName) | ||
91 | if (!result) return | ||
92 | |||
93 | const routes = result.registerHelpers.getWebSocketRoutes() | ||
94 | |||
95 | const wss = routes.find(r => r.route.startsWith(subRoute)) | ||
96 | if (!wss) return | ||
97 | |||
98 | wss.handler(request, socket, head) | ||
99 | }) | ||
100 | } | ||
101 | |||
73 | // ###################### Getters ###################### | 102 | // ###################### Getters ###################### |
74 | 103 | ||
75 | isRegistered (npmName: string) { | 104 | isRegistered (npmName: string) { |
@@ -581,7 +610,7 @@ export class PluginManager implements ServerHook { | |||
581 | }) | 610 | }) |
582 | } | 611 | } |
583 | 612 | ||
584 | const registerHelpers = new RegisterHelpers(npmName, plugin, onHookAdded.bind(this)) | 613 | const registerHelpers = new RegisterHelpers(npmName, plugin, this.server, onHookAdded.bind(this)) |
585 | 614 | ||
586 | return { | 615 | return { |
587 | registerStore: registerHelpers, | 616 | registerStore: registerHelpers, |
diff --git a/server/lib/plugins/register-helpers.ts b/server/lib/plugins/register-helpers.ts index f4d405676..1aaef3606 100644 --- a/server/lib/plugins/register-helpers.ts +++ b/server/lib/plugins/register-helpers.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { Server } from 'http' | ||
2 | import { logger } from '@server/helpers/logger' | 3 | import { logger } from '@server/helpers/logger' |
3 | import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth' | 4 | import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth' |
4 | import { VideoConstantManagerFactory } from '@server/lib/plugins/video-constant-manager-factory' | 5 | import { VideoConstantManagerFactory } from '@server/lib/plugins/video-constant-manager-factory' |
@@ -8,7 +9,8 @@ import { | |||
8 | RegisterServerAuthExternalResult, | 9 | RegisterServerAuthExternalResult, |
9 | RegisterServerAuthPassOptions, | 10 | RegisterServerAuthPassOptions, |
10 | RegisterServerExternalAuthenticatedResult, | 11 | RegisterServerExternalAuthenticatedResult, |
11 | RegisterServerOptions | 12 | RegisterServerOptions, |
13 | RegisterServerWebSocketRouteOptions | ||
12 | } from '@server/types/plugins' | 14 | } from '@server/types/plugins' |
13 | import { | 15 | import { |
14 | EncoderOptionsBuilder, | 16 | EncoderOptionsBuilder, |
@@ -49,12 +51,15 @@ export class RegisterHelpers { | |||
49 | 51 | ||
50 | private readonly onSettingsChangeCallbacks: SettingsChangeCallback[] = [] | 52 | private readonly onSettingsChangeCallbacks: SettingsChangeCallback[] = [] |
51 | 53 | ||
54 | private readonly webSocketRoutes: RegisterServerWebSocketRouteOptions[] = [] | ||
55 | |||
52 | private readonly router: express.Router | 56 | private readonly router: express.Router |
53 | private readonly videoConstantManagerFactory: VideoConstantManagerFactory | 57 | private readonly videoConstantManagerFactory: VideoConstantManagerFactory |
54 | 58 | ||
55 | constructor ( | 59 | constructor ( |
56 | private readonly npmName: string, | 60 | private readonly npmName: string, |
57 | private readonly plugin: PluginModel, | 61 | private readonly plugin: PluginModel, |
62 | private readonly server: Server, | ||
58 | private readonly onHookAdded: (options: RegisterServerHookOptions) => void | 63 | private readonly onHookAdded: (options: RegisterServerHookOptions) => void |
59 | ) { | 64 | ) { |
60 | this.router = express.Router() | 65 | this.router = express.Router() |
@@ -66,6 +71,7 @@ export class RegisterHelpers { | |||
66 | const registerSetting = this.buildRegisterSetting() | 71 | const registerSetting = this.buildRegisterSetting() |
67 | 72 | ||
68 | const getRouter = this.buildGetRouter() | 73 | const getRouter = this.buildGetRouter() |
74 | const registerWebSocketRoute = this.buildRegisterWebSocketRoute() | ||
69 | 75 | ||
70 | const settingsManager = this.buildSettingsManager() | 76 | const settingsManager = this.buildSettingsManager() |
71 | const storageManager = this.buildStorageManager() | 77 | const storageManager = this.buildStorageManager() |
@@ -85,13 +91,14 @@ export class RegisterHelpers { | |||
85 | const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth() | 91 | const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth() |
86 | const unregisterExternalAuth = this.buildUnregisterExternalAuth() | 92 | const unregisterExternalAuth = this.buildUnregisterExternalAuth() |
87 | 93 | ||
88 | const peertubeHelpers = buildPluginHelpers(this.plugin, this.npmName) | 94 | const peertubeHelpers = buildPluginHelpers(this.server, this.plugin, this.npmName) |
89 | 95 | ||
90 | return { | 96 | return { |
91 | registerHook, | 97 | registerHook, |
92 | registerSetting, | 98 | registerSetting, |
93 | 99 | ||
94 | getRouter, | 100 | getRouter, |
101 | registerWebSocketRoute, | ||
95 | 102 | ||
96 | settingsManager, | 103 | settingsManager, |
97 | storageManager, | 104 | storageManager, |
@@ -180,10 +187,20 @@ export class RegisterHelpers { | |||
180 | return this.onSettingsChangeCallbacks | 187 | return this.onSettingsChangeCallbacks |
181 | } | 188 | } |
182 | 189 | ||
190 | getWebSocketRoutes () { | ||
191 | return this.webSocketRoutes | ||
192 | } | ||
193 | |||
183 | private buildGetRouter () { | 194 | private buildGetRouter () { |
184 | return () => this.router | 195 | return () => this.router |
185 | } | 196 | } |
186 | 197 | ||
198 | private buildRegisterWebSocketRoute () { | ||
199 | return (options: RegisterServerWebSocketRouteOptions) => { | ||
200 | this.webSocketRoutes.push(options) | ||
201 | } | ||
202 | } | ||
203 | |||
187 | private buildRegisterSetting () { | 204 | private buildRegisterSetting () { |
188 | return (options: RegisterServerSettingOptions) => { | 205 | return (options: RegisterServerSettingOptions) => { |
189 | this.settings.push(options) | 206 | this.settings.push(options) |
diff --git a/server/tests/fixtures/peertube-plugin-test-websocket/main.js b/server/tests/fixtures/peertube-plugin-test-websocket/main.js new file mode 100644 index 000000000..3fde76cfe --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-websocket/main.js | |||
@@ -0,0 +1,36 @@ | |||
1 | const WebSocketServer = require('ws').WebSocketServer | ||
2 | |||
3 | async function register ({ | ||
4 | registerWebSocketRoute | ||
5 | }) { | ||
6 | const wss = new WebSocketServer({ noServer: true }) | ||
7 | |||
8 | wss.on('connection', function connection(ws) { | ||
9 | ws.on('message', function message(data) { | ||
10 | if (data.toString() === 'ping') { | ||
11 | ws.send('pong') | ||
12 | } | ||
13 | }) | ||
14 | }) | ||
15 | |||
16 | registerWebSocketRoute({ | ||
17 | route: '/toto', | ||
18 | |||
19 | handler: (request, socket, head) => { | ||
20 | wss.handleUpgrade(request, socket, head, ws => { | ||
21 | wss.emit('connection', ws, request) | ||
22 | }) | ||
23 | } | ||
24 | }) | ||
25 | } | ||
26 | |||
27 | async function unregister () { | ||
28 | return | ||
29 | } | ||
30 | |||
31 | module.exports = { | ||
32 | register, | ||
33 | unregister | ||
34 | } | ||
35 | |||
36 | // ########################################################################### | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-websocket/package.json b/server/tests/fixtures/peertube-plugin-test-websocket/package.json new file mode 100644 index 000000000..89c8baa04 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-websocket/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-websocket", | ||
3 | "version": "0.0.1", | ||
4 | "description": "Plugin test websocket", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/plugins/index.ts b/server/tests/plugins/index.ts index 4534120fd..210af7236 100644 --- a/server/tests/plugins/index.ts +++ b/server/tests/plugins/index.ts | |||
@@ -8,5 +8,6 @@ import './plugin-router' | |||
8 | import './plugin-storage' | 8 | import './plugin-storage' |
9 | import './plugin-transcoding' | 9 | import './plugin-transcoding' |
10 | import './plugin-unloading' | 10 | import './plugin-unloading' |
11 | import './plugin-websocket' | ||
11 | import './translations' | 12 | import './translations' |
12 | import './video-constants' | 13 | import './video-constants' |
diff --git a/server/tests/plugins/plugin-websocket.ts b/server/tests/plugins/plugin-websocket.ts new file mode 100644 index 000000000..adaa28b1d --- /dev/null +++ b/server/tests/plugins/plugin-websocket.ts | |||
@@ -0,0 +1,70 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import WebSocket from 'ws' | ||
4 | import { cleanupTests, createSingleServer, PeerTubeServer, PluginsCommand, setAccessTokensToServers } from '@shared/server-commands' | ||
5 | |||
6 | function buildWebSocket (server: PeerTubeServer, path: string) { | ||
7 | return new WebSocket('ws://' + server.host + path) | ||
8 | } | ||
9 | |||
10 | function expectErrorOrTimeout (server: PeerTubeServer, path: string, expectedTimeout: number) { | ||
11 | return new Promise<void>((res, rej) => { | ||
12 | const ws = buildWebSocket(server, path) | ||
13 | ws.on('error', () => res()) | ||
14 | |||
15 | const timeout = setTimeout(() => res(), expectedTimeout) | ||
16 | |||
17 | ws.on('open', () => { | ||
18 | clearTimeout(timeout) | ||
19 | |||
20 | return rej(new Error('Connect did not timeout')) | ||
21 | }) | ||
22 | }) | ||
23 | } | ||
24 | |||
25 | describe('Test plugin websocket', function () { | ||
26 | let server: PeerTubeServer | ||
27 | const basePaths = [ | ||
28 | '/plugins/test-websocket/ws/', | ||
29 | '/plugins/test-websocket/0.0.1/ws/' | ||
30 | ] | ||
31 | |||
32 | before(async function () { | ||
33 | this.timeout(30000) | ||
34 | |||
35 | server = await createSingleServer(1) | ||
36 | await setAccessTokensToServers([ server ]) | ||
37 | |||
38 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-websocket') }) | ||
39 | }) | ||
40 | |||
41 | it('Should not connect to the websocket without the appropriate path', async function () { | ||
42 | const paths = [ | ||
43 | '/plugins/unknown/ws/', | ||
44 | '/plugins/unknown/0.0.1/ws/' | ||
45 | ] | ||
46 | |||
47 | for (const path of paths) { | ||
48 | await expectErrorOrTimeout(server, path, 1000) | ||
49 | } | ||
50 | }) | ||
51 | |||
52 | it('Should not connect to the websocket without the appropriate sub path', async function () { | ||
53 | for (const path of basePaths) { | ||
54 | await expectErrorOrTimeout(server, path + '/unknown', 1000) | ||
55 | } | ||
56 | }) | ||
57 | |||
58 | it('Should connect to the websocket and receive pong', function (done) { | ||
59 | const ws = buildWebSocket(server, basePaths[0]) | ||
60 | |||
61 | ws.on('open', () => ws.send('ping')) | ||
62 | ws.on('message', data => { | ||
63 | if (data.toString() === 'pong') return done() | ||
64 | }) | ||
65 | }) | ||
66 | |||
67 | after(async function () { | ||
68 | await cleanupTests([ server ]) | ||
69 | }) | ||
70 | }) | ||
diff --git a/server/types/plugins/index.ts b/server/types/plugins/index.ts index de30ff2ab..bf9c35d49 100644 --- a/server/types/plugins/index.ts +++ b/server/types/plugins/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './plugin-library.model' | 1 | export * from './plugin-library.model' |
2 | export * from './register-server-auth.model' | 2 | export * from './register-server-auth.model' |
3 | export * from './register-server-option.model' | 3 | export * from './register-server-option.model' |
4 | export * from './register-server-websocket-route.model' | ||
diff --git a/server/types/plugins/register-server-option.model.ts b/server/types/plugins/register-server-option.model.ts index a8b804b63..1e2bd830e 100644 --- a/server/types/plugins/register-server-option.model.ts +++ b/server/types/plugins/register-server-option.model.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Response, Router } from 'express' | 1 | import { Response, Router } from 'express' |
2 | import { Server } from 'http' | ||
2 | import { Logger } from 'winston' | 3 | import { Logger } from 'winston' |
3 | import { ActorModel } from '@server/models/actor/actor' | 4 | import { ActorModel } from '@server/models/actor/actor' |
4 | import { | 5 | import { |
@@ -22,6 +23,7 @@ import { | |||
22 | RegisterServerAuthExternalResult, | 23 | RegisterServerAuthExternalResult, |
23 | RegisterServerAuthPassOptions | 24 | RegisterServerAuthPassOptions |
24 | } from './register-server-auth.model' | 25 | } from './register-server-auth.model' |
26 | import { RegisterServerWebSocketRouteOptions } from './register-server-websocket-route.model' | ||
25 | 27 | ||
26 | export type PeerTubeHelpers = { | 28 | export type PeerTubeHelpers = { |
27 | logger: Logger | 29 | logger: Logger |
@@ -83,6 +85,9 @@ export type PeerTubeHelpers = { | |||
83 | } | 85 | } |
84 | 86 | ||
85 | server: { | 87 | server: { |
88 | // PeerTube >= 5.0 | ||
89 | getHTTPServer: () => Server | ||
90 | |||
86 | getServerActor: () => Promise<ActorModel> | 91 | getServerActor: () => Promise<ActorModel> |
87 | } | 92 | } |
88 | 93 | ||
@@ -97,6 +102,8 @@ export type PeerTubeHelpers = { | |||
97 | 102 | ||
98 | // PeerTube >= 3.2 | 103 | // PeerTube >= 3.2 |
99 | getBaseRouterRoute: () => string | 104 | getBaseRouterRoute: () => string |
105 | // PeerTube >= 5.0 | ||
106 | getBaseWebSocketRoute: () => string | ||
100 | 107 | ||
101 | // PeerTube >= 3.2 | 108 | // PeerTube >= 3.2 |
102 | getDataDirectoryPath: () => string | 109 | getDataDirectoryPath: () => string |
@@ -140,5 +147,12 @@ export type RegisterServerOptions = { | |||
140 | // * /plugins/:pluginName/router/... | 147 | // * /plugins/:pluginName/router/... |
141 | getRouter(): Router | 148 | getRouter(): Router |
142 | 149 | ||
150 | // PeerTube >= 5.0 | ||
151 | // Register WebSocket route | ||
152 | // Base routes of the WebSocket router are | ||
153 | // * /plugins/:pluginName/:pluginVersion/ws/... | ||
154 | // * /plugins/:pluginName/ws/... | ||
155 | registerWebSocketRoute: (options: RegisterServerWebSocketRouteOptions) => void | ||
156 | |||
143 | peertubeHelpers: PeerTubeHelpers | 157 | peertubeHelpers: PeerTubeHelpers |
144 | } | 158 | } |
diff --git a/server/types/plugins/register-server-websocket-route.model.ts b/server/types/plugins/register-server-websocket-route.model.ts new file mode 100644 index 000000000..edf64f66b --- /dev/null +++ b/server/types/plugins/register-server-websocket-route.model.ts | |||
@@ -0,0 +1,8 @@ | |||
1 | import { IncomingMessage } from 'http' | ||
2 | import { Duplex } from 'stream' | ||
3 | |||
4 | export type RegisterServerWebSocketRouteOptions = { | ||
5 | route: string | ||
6 | |||
7 | handler: (request: IncomingMessage, socket: Duplex, head: Buffer) => any | ||
8 | } | ||
diff --git a/support/doc/plugins/guide.md b/support/doc/plugins/guide.md index 431d5332f..1c809258a 100644 --- a/support/doc/plugins/guide.md +++ b/support/doc/plugins/guide.md | |||
@@ -12,6 +12,7 @@ | |||
12 | - [Storage](#storage) | 12 | - [Storage](#storage) |
13 | - [Update video constants](#update-video-constants) | 13 | - [Update video constants](#update-video-constants) |
14 | - [Add custom routes](#add-custom-routes) | 14 | - [Add custom routes](#add-custom-routes) |
15 | - [Add custom WebSocket handlers](#add-custom-websocket-handlers) | ||
15 | - [Add external auth methods](#add-external-auth-methods) | 16 | - [Add external auth methods](#add-external-auth-methods) |
16 | - [Add new transcoding profiles](#add-new-transcoding-profiles) | 17 | - [Add new transcoding profiles](#add-new-transcoding-profiles) |
17 | - [Server helpers](#server-helpers) | 18 | - [Server helpers](#server-helpers) |
@@ -317,6 +318,41 @@ The `ping` route can be accessed using: | |||
317 | * Or `/plugins/:pluginName/router/ping` | 318 | * Or `/plugins/:pluginName/router/ping` |
318 | 319 | ||
319 | 320 | ||
321 | #### Add custom WebSocket handlers | ||
322 | |||
323 | You can create custom WebSocket servers (like [ws](https://github.com/websockets/ws) for example) using `registerWebSocketRoute`: | ||
324 | |||
325 | ```js | ||
326 | function register ({ | ||
327 | registerWebSocketRoute, | ||
328 | peertubeHelpers | ||
329 | }) { | ||
330 | const wss = new WebSocketServer({ noServer: true }) | ||
331 | |||
332 | wss.on('connection', function connection(ws) { | ||
333 | peertubeHelpers.logger.info('WebSocket connected!') | ||
334 | |||
335 | setInterval(() => { | ||
336 | ws.send('WebSocket message sent by server'); | ||
337 | }, 1000) | ||
338 | }) | ||
339 | |||
340 | registerWebSocketRoute({ | ||
341 | route: '/my-websocket-route', | ||
342 | |||
343 | handler: (request, socket, head) => { | ||
344 | wss.handleUpgrade(request, socket, head, ws => { | ||
345 | wss.emit('connection', ws, request) | ||
346 | }) | ||
347 | } | ||
348 | }) | ||
349 | } | ||
350 | ``` | ||
351 | |||
352 | The `my-websocket-route` route can be accessed using: | ||
353 | * `/plugins/:pluginName/:pluginVersion/ws/my-websocket-route` | ||
354 | * Or `/plugins/:pluginName/ws/my-websocket-route` | ||
355 | |||
320 | #### Add external auth methods | 356 | #### Add external auth methods |
321 | 357 | ||
322 | If you want to add a classic username/email and password auth method (like [LDAP](https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-ldap) for example): | 358 | If you want to add a classic username/email and password auth method (like [LDAP](https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-ldap) for example): |
diff --git a/support/nginx/peertube b/support/nginx/peertube index abb83d5c4..f6f754b58 100644 --- a/support/nginx/peertube +++ b/support/nginx/peertube | |||
@@ -132,6 +132,11 @@ server { | |||
132 | try_files /dev/null @api_websocket; | 132 | try_files /dev/null @api_websocket; |
133 | } | 133 | } |
134 | 134 | ||
135 | # Plugin websocket routes | ||
136 | location ~ ^/plugins/[^/]+(/[^/]+)?/ws/ { | ||
137 | try_files /dev/null @api_websocket; | ||
138 | } | ||
139 | |||
135 | ## | 140 | ## |
136 | # Performance optimizations | 141 | # Performance optimizations |
137 | # For extra performance please refer to https://github.com/denji/nginx-tuning | 142 | # For extra performance please refer to https://github.com/denji/nginx-tuning |