aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-10-11 11:07:40 +0200
committerChocobozzz <me@florianbigard.com>2022-10-11 11:11:04 +0200
commit9d4c60dccc8e7e777ad139a82e9f61feda9d21fc (patch)
tree2931338f340b398d36c43575fea95cf1fbbfeb4c
parent9866921cbf3f8f0925f7ffb3a231d5dfe2d30953 (diff)
downloadPeerTube-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.ts5
-rw-r--r--client/src/standalone/videos/shared/peertube-plugin.ts1
-rw-r--r--client/src/types/register-client-option.model.ts3
-rw-r--r--server.ts4
-rw-r--r--server/lib/plugins/plugin-helpers-builder.ts13
-rw-r--r--server/lib/plugins/plugin-manager.ts31
-rw-r--r--server/lib/plugins/register-helpers.ts21
-rw-r--r--server/tests/fixtures/peertube-plugin-test-websocket/main.js36
-rw-r--r--server/tests/fixtures/peertube-plugin-test-websocket/package.json20
-rw-r--r--server/tests/plugins/index.ts1
-rw-r--r--server/tests/plugins/plugin-websocket.ts70
-rw-r--r--server/types/plugins/index.ts1
-rw-r--r--server/types/plugins/register-server-option.model.ts14
-rw-r--r--server/types/plugins/register-server-websocket-route.model.ts8
-rw-r--r--support/doc/plugins/guide.md36
-rw-r--r--support/nginx/peertube5
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
diff --git a/server.ts b/server.ts
index 417387a4f..a29b5e408 100644
--- a/server.ts
+++ b/server.ts
@@ -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 @@
1import express from 'express' 1import express from 'express'
2import { Server } from 'http'
2import { join } from 'path' 3import { join } from 'path'
3import { ffprobePromise } from '@server/helpers/ffmpeg/ffprobe-utils' 4import { ffprobePromise } from '@server/helpers/ffmpeg/ffprobe-utils'
4import { buildLogger } from '@server/helpers/logger' 5import { buildLogger } from '@server/helpers/logger'
@@ -17,12 +18,12 @@ import { MPlugin, MVideo, UserNotificationModelForApi } from '@server/types/mode
17import { PeerTubeHelpers } from '@server/types/plugins' 18import { PeerTubeHelpers } from '@server/types/plugins'
18import { VideoBlacklistCreate, VideoStorage } from '@shared/models' 19import { VideoBlacklistCreate, VideoStorage } from '@shared/models'
19import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist' 20import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist'
21import { PeerTubeSocket } from '../peertube-socket'
20import { ServerConfigManager } from '../server-config-manager' 22import { ServerConfigManager } from '../server-config-manager'
21import { blacklistVideo, unblacklistVideo } from '../video-blacklist' 23import { blacklistVideo, unblacklistVideo } from '../video-blacklist'
22import { VideoPathManager } from '../video-path-manager' 24import { VideoPathManager } from '../video-path-manager'
23import { PeerTubeSocket } from '../peertube-socket'
24 25
25function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers { 26function 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
72function buildServerHelpers () { 73function 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 @@
1import express from 'express' 1import express from 'express'
2import { createReadStream, createWriteStream } from 'fs' 2import { createReadStream, createWriteStream } from 'fs'
3import { ensureDir, outputFile, readJSON } from 'fs-extra' 3import { ensureDir, outputFile, readJSON } from 'fs-extra'
4import { Server } from 'http'
4import { basename, join } from 'path' 5import { basename, join } from 'path'
5import { decachePlugin } from '@server/helpers/decache' 6import { decachePlugin } from '@server/helpers/decache'
6import { ApplicationModel } from '@server/models/application/application' 7import { 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 @@
1import express from 'express' 1import express from 'express'
2import { Server } from 'http'
2import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
3import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth' 4import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth'
4import { VideoConstantManagerFactory } from '@server/lib/plugins/video-constant-manager-factory' 5import { 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'
13import { 15import {
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 @@
1const WebSocketServer = require('ws').WebSocketServer
2
3async 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
27async function unregister () {
28 return
29}
30
31module.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'
8import './plugin-storage' 8import './plugin-storage'
9import './plugin-transcoding' 9import './plugin-transcoding'
10import './plugin-unloading' 10import './plugin-unloading'
11import './plugin-websocket'
11import './translations' 12import './translations'
12import './video-constants' 13import './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
3import WebSocket from 'ws'
4import { cleanupTests, createSingleServer, PeerTubeServer, PluginsCommand, setAccessTokensToServers } from '@shared/server-commands'
5
6function buildWebSocket (server: PeerTubeServer, path: string) {
7 return new WebSocket('ws://' + server.host + path)
8}
9
10function 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
25describe('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 @@
1export * from './plugin-library.model' 1export * from './plugin-library.model'
2export * from './register-server-auth.model' 2export * from './register-server-auth.model'
3export * from './register-server-option.model' 3export * from './register-server-option.model'
4export * 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 @@
1import { Response, Router } from 'express' 1import { Response, Router } from 'express'
2import { Server } from 'http'
2import { Logger } from 'winston' 3import { Logger } from 'winston'
3import { ActorModel } from '@server/models/actor/actor' 4import { ActorModel } from '@server/models/actor/actor'
4import { 5import {
@@ -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'
26import { RegisterServerWebSocketRouteOptions } from './register-server-websocket-route.model'
25 27
26export type PeerTubeHelpers = { 28export 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 @@
1import { IncomingMessage } from 'http'
2import { Duplex } from 'stream'
3
4export 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
323You can create custom WebSocket servers (like [ws](https://github.com/websockets/ws) for example) using `registerWebSocketRoute`:
324
325```js
326function 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
352The `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
322If 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): 358If 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