Merge branch 'develop' into localization

This commit is contained in:
Gabe Kangas 2024-04-22 18:34:46 -07:00
commit e2b9db1e66
No known key found for this signature in database
GPG Key ID: 4345B2060657F330
92 changed files with 11973 additions and 13165 deletions

View File

@ -40,7 +40,7 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.21' go-version: '1.22'
cache: true cache: true
- name: Install Google Chrome - name: Install Google Chrome

View File

@ -9,16 +9,16 @@
# the `language` matrix defined below to confirm you have the correct set of # the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages. # supported CodeQL languages.
# #
name: "CodeQL" name: 'CodeQL'
on: on:
push: push:
branches: [ develop ] branches: [develop]
paths-ignore: paths-ignore:
- 'static/**' - 'static/**'
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ develop ] branches: [develop]
paths-ignore: paths-ignore:
- 'static/**' - 'static/**'
@ -30,41 +30,46 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'go', 'javascript' ] language: ['go', 'javascript']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more: # Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. - uses: actions/setup-go@v5
- name: Initialize CodeQL with:
uses: github/codeql-action/init@v3 go-version: '1.22'
with: cache: true
languages: ${{ matrix.language }}
config-file: ./.github/codeql/${{ matrix.language }}.yml
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Initializes the CodeQL tools for scanning.
# If this step fails, then you should remove it and run the build manually (see below) - name: Initialize CodeQL
- name: Autobuild uses: github/codeql-action/init@v3
uses: github/codeql-action/autobuild@v3 with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/${{ matrix.language }}.yml
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Command-line programs to run using the OS shell. # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# 📚 https://git.io/JvXDl # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # Command-line programs to run using the OS shell.
# and modify them (or add more) to build your code if your project # 📚 https://git.io/JvXDl
# uses a compiled language
#- run: | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# make bootstrap # and modify them (or add more) to build your code if your project
# make release # uses a compiled language
- name: Perform CodeQL Analysis #- run: |
uses: github/codeql-action/analyze@v3 # make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View File

@ -28,7 +28,7 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.21' go-version: '1.22'
cache: true cache: true
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: golangci-lint - name: golangci-lint

View File

@ -12,7 +12,7 @@ jobs:
test: test:
strategy: strategy:
matrix: matrix:
go-version: [1.20.x, 1.21.x] go-version: [1.21.x, 1.22.x]
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:

View File

@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.21' go-version: '1.22'
cache: true cache: true
- name: Cache node modules - name: Cache node modules

View File

@ -72,6 +72,9 @@ jobs:
if: steps.changed-files-yaml.outputs.src_any_changed == 'true' if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
run: npx prettier --write ${{ steps.changed-files-yaml.outputs.src_all_changed_files }} run: npx prettier --write ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
- name: Debug changed files output
run: 'pwd && echo "Changed files: ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}"'
- name: Commit changes - name: Commit changes
if: steps.changed-files-yaml.outputs.src_any_changed == 'true' if: steps.changed-files-yaml.outputs.src_any_changed == 'true'
uses: EndBug/add-and-commit@v9 uses: EndBug/add-and-commit@v9
@ -80,6 +83,7 @@ jobs:
author_email: owncast@owncast.online author_email: owncast@owncast.online
message: 'Javascript formatting autofixes' message: 'Javascript formatting autofixes'
add: ${{ steps.changed-files-yaml.outputs.src_all_changed_files }} add: ${{ steps.changed-files-yaml.outputs.src_all_changed_files }}
cwd: './web' # Ensure this is the correct relative directory
pull: '--rebase --autostash' pull: '--rebase --autostash'
unused-code: unused-code:

View File

@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.21' go-version: '1.22'
cache: true cache: true
- name: Cache node modules - name: Cache node modules

View File

@ -5,7 +5,7 @@ run:
# Define the Go version limit. # Define the Go version limit.
# Mainly related to generics support in go1.18. # Mainly related to generics support in go1.18.
# Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.18 # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.18
go: '1.21' go: '1.22'
issues: issues:
# The linter has a default list of ignorable errors. Turning this on will enable that list. # The linter has a default list of ignorable errors. Turning this on will enable that list.
@ -69,7 +69,7 @@ linters-settings:
gosimple: gosimple:
# Select the Go version to target. The default is '1.13'. # Select the Go version to target. The default is '1.13'.
go: '1.21' go: '1.22'
# https://staticcheck.io/docs/options#checks # https://staticcheck.io/docs/options#checks
checks: ['all'] checks: ['all']

View File

@ -95,7 +95,7 @@ The Owncast backend is a service written in Go.
1. Ensure you have prerequisites installed. 1. Ensure you have prerequisites installed.
- C compiler, such as [GCC compiler](https://gcc.gnu.org/install/download.html) or a [Musl-compatible compiler](https://musl.libc.org/) - C compiler, such as [GCC compiler](https://gcc.gnu.org/install/download.html) or a [Musl-compatible compiler](https://musl.libc.org/)
- [ffmpeg](https://ffmpeg.org/download.html) - [ffmpeg](https://ffmpeg.org/download.html)
1. Install the [Go toolchain](https://golang.org/dl/) (1.21 or above). 1. Install the [Go toolchain](https://golang.org/dl/) (1.22 or above).
1. Clone the repo. `git clone https://github.com/owncast/owncast` 1. Clone the repo. `git clone https://github.com/owncast/owncast`
1. `go run main.go` will run from the source. 1. `go run main.go` will run from the source.
1. Visit `http://yourserver:8080` to access the web interface or `http://yourserver:8080/admin` to access the admin. 1. Visit `http://yourserver:8080` to access the web interface or `http://yourserver:8080/admin` to access the admin.

6
go.mod
View File

@ -1,9 +1,9 @@
module github.com/owncast/owncast module github.com/owncast/owncast
go 1.21 go 1.22
require ( require (
github.com/aws/aws-sdk-go v1.51.17 github.com/aws/aws-sdk-go v1.51.23
github.com/go-fed/activity v1.0.1-0.20210803212804-d866ba75dd0f github.com/go-fed/activity v1.0.1-0.20210803212804-d866ba75dd0f
github.com/go-fed/httpsig v1.1.0 github.com/go-fed/httpsig v1.1.0
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
@ -59,7 +59,6 @@ require (
require github.com/SherClockHolmes/webpush-go v1.3.0 require github.com/SherClockHolmes/webpush-go v1.3.0
require ( require (
github.com/TwiN/go-away v1.6.13 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
@ -76,6 +75,7 @@ require (
require ( require (
github.com/CAFxX/httpcompression v0.0.9 github.com/CAFxX/httpcompression v0.0.9
github.com/TwiN/go-away v1.6.13
github.com/andybalholm/cascadia v1.3.2 github.com/andybalholm/cascadia v1.3.2
github.com/jellydator/ttlcache/v3 v3.2.0 github.com/jellydator/ttlcache/v3 v3.2.0
github.com/mssola/user_agent v0.6.0 github.com/mssola/user_agent v0.6.0

2
go.sum
View File

@ -12,6 +12,8 @@ github.com/aws/aws-sdk-go v1.50.33 h1:/SKPJ7ZVPCFOYZyTKo5YdjeUEeOn2J2M0qfDTXWAoE
github.com/aws/aws-sdk-go v1.50.33/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go v1.50.33/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.51.17 h1:Cfa40lCdjv9OxC3X1Ks3a6O1Tu3gOANSyKHOSw/zuWU= github.com/aws/aws-sdk-go v1.51.17 h1:Cfa40lCdjv9OxC3X1Ks3a6O1Tu3gOANSyKHOSw/zuWU=
github.com/aws/aws-sdk-go v1.51.17/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go v1.51.17/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.51.23 h1:/3TEdsEE/aHmdKGw2NrOp7Sdea76zfffGkTTSXTsDxY=
github.com/aws/aws-sdk-go v1.51.23/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

6
static/web/404.html vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9262],{70918:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/upgrade",function(){return n(65282)}])},65282:function(e,t,n){"use strict";n.r(t);var a=n(85893),r=n(67294),l=n(12426),s=n(73745),d=n(89062),u=n(27478),i=n(27674);let{Title:c}=s.default,o=e=>{let t=Object.values(e);return(0,a.jsx)(d.Z,{dataSource:t,columns:[{title:"Name",dataIndex:"name",key:"name",render:(e,t)=>(0,a.jsx)("a",{href:t.browser_download_url,children:e})},{title:"Size",dataIndex:"size",key:"size",render:e=>"".concat((e/1024/1024).toFixed(2)," MB")}],rowKey:e=>e.id,size:"large",pagination:!1})},_=()=>{let[e,t]=(0,r.useState)({html_url:"",name:"",created_at:null,body:"",assets:[]}),n=async()=>{try{let e=await (0,u.Kt)();t(e)}catch(e){console.log("==== error",e)}};return((0,r.useEffect)(()=>{n()},[]),e)?(0,a.jsxs)("div",{className:"upgrade-page",children:[(0,a.jsx)(c,{level:2,children:(0,a.jsx)("a",{href:e.html_url,children:e.name})}),(0,a.jsx)(c,{level:5,children:new Date(e.created_at).toDateString()}),(0,a.jsx)(l.U,{children:e.body}),(0,a.jsx)("h3",{children:"Downloads"}),(0,a.jsx)(o,{...e.assets})]}):null};_.getLayout=function(e){return(0,a.jsx)(i.l,{page:e})},t.default=_}},function(e){e.O(0,[2016,4992,9831,6113,9505,4970,3745,6919,6880,6655,8810,247,9062,3520,7674,2888,9774,179],function(){return e(e.s=70918)}),_N_E=e.O()}]); (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9262],{70918:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/upgrade",function(){return n(65282)}])},65282:function(e,t,n){"use strict";n.r(t);var a=n(85893),r=n(67294),l=n(39205),s=n(73745),d=n(89062),u=n(27478),i=n(27674);let{Title:c}=s.default,o=e=>{let t=Object.values(e);return(0,a.jsx)(d.Z,{dataSource:t,columns:[{title:"Name",dataIndex:"name",key:"name",render:(e,t)=>(0,a.jsx)("a",{href:t.browser_download_url,children:e})},{title:"Size",dataIndex:"size",key:"size",render:e=>"".concat((e/1024/1024).toFixed(2)," MB")}],rowKey:e=>e.id,size:"large",pagination:!1})},_=()=>{let[e,t]=(0,r.useState)({html_url:"",name:"",created_at:null,body:"",assets:[]}),n=async()=>{try{let e=await (0,u.Kt)();t(e)}catch(e){console.log("==== error",e)}};return((0,r.useEffect)(()=>{n()},[]),e)?(0,a.jsxs)("div",{className:"upgrade-page",children:[(0,a.jsx)(c,{level:2,children:(0,a.jsx)("a",{href:e.html_url,children:e.name})}),(0,a.jsx)(c,{level:5,children:new Date(e.created_at).toDateString()}),(0,a.jsx)(l.U,{children:e.body}),(0,a.jsx)("h3",{children:"Downloads"}),(0,a.jsx)(o,{...e.assets})]}):null};_.getLayout=function(e){return(0,a.jsx)(i.l,{page:e})},t.default=_}},function(e){e.O(0,[2016,4992,9831,6113,9505,4970,3745,6919,6880,6655,8810,247,9062,8262,7674,2888,9774,179],function(){return e(e.s=70918)}),_N_E=e.O()}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
static/web/sw.js vendored

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,195 @@
const { test } = require('@jest/globals');
var request = require('supertest');
request = request('http://127.0.0.1:8080');
const WebSocket = require('ws');
const fs = require('fs');
const registerChat = require('./lib/chat').registerChat;
const sendChatMessage = require('./lib/chat').sendChatMessage;
const sendAdminRequest = require('./lib/admin').sendAdminRequest;
const sendAdminPayload = require('./lib/admin').sendAdminPayload;
const getAdminResponse = require('./lib/admin').getAdminResponse;
const randomNumber = require('./lib/rand').randomNumber;
const localIPAddressV4 = '127.0.0.1';
const localIPAddressV6 = '::1';
const testVisibilityMessage = {
body: 'message ' + randomNumber(100),
type: 'CHAT',
};
var userId;
var accessToken;
test('register a user', async (done) => {
const registration = await registerChat();
userId = registration.id;
accessToken = registration.accessToken;
done();
});
test('send a chat message', async (done) => {
sendChatMessage(testVisibilityMessage, accessToken, done);
});
test('set the user as moderator', async (done) => {
const res = await sendAdminPayload('chat/users/setmoderator', {
userId: userId,
isModerator: true,
});
done();
});
test('verify user is a moderator', async (done) => {
const response = await getAdminResponse('chat/users/moderators');
const tokenCheck = response.body.filter((user) => user.id === userId);
expect(tokenCheck).toHaveLength(1);
done();
});
test('verify user list is populated', async (done) => {
const ws = new WebSocket(
`ws://localhost:8080/ws?accessToken=${accessToken}`,
{
origin: 'http://localhost:8080',
}
);
ws.on('open', async function open() {
const response = await getAdminResponse('chat/clients');
expect(response.body.length).toBeGreaterThan(0);
// Optionally, if GeoIP is configured, check the location property.
if (fs.existsSync('../../../data/GeoLite2-City.mmdb')) {
expect(response.body[0].geo.regionName).toBe('Localhost');
}
ws.close();
});
ws.on('error', function incoming(data) {
console.error(data);
ws.close();
});
ws.on('close', function incoming(data) {
done();
});
});
test('disable a user by admin', async (done) => {
// To allow for visually being able to see the test hiding the
// message add a short delay.
await new Promise((r) => setTimeout(r, 1500));
const ws = new WebSocket(
`ws://localhost:8080/ws?accessToken=${accessToken}`,
{
origin: 'http://localhost:8080',
}
);
const res = await sendAdminPayload('chat/users/setenabled', {
userId: userId,
enabled: false,
});
await new Promise((r) => setTimeout(r, 1500));
done();
});
test('verify user is disabled', async (done) => {
const response = await getAdminResponse('chat/users/disabled');
const tokenCheck = response.body.filter((user) => user.id === userId);
expect(tokenCheck).toHaveLength(1);
done();
});
test('verify messages from user are hidden', async (done) => {
const response = await getAdminResponse('chat/messages');
const message = response.body.filter((obj) => {
return obj.user.id === userId;
});
expect(message[0].user.disabledAt).toBeTruthy();
done();
});
test('re-enable a user by admin', async (done) => {
const res = await sendAdminPayload('chat/users/setenabled', {
userId: userId,
enabled: true,
});
done();
});
test('verify user is enabled', async (done) => {
const response = await getAdminResponse('chat/users/disabled');
const tokenCheck = response.body.filter((user) => user.id === userId);
expect(tokenCheck).toHaveLength(0);
done();
});
test('ban an ip address by admin', async (done) => {
const resIPv4 = await sendAdminRequest(
'chat/users/ipbans/create',
localIPAddressV4
);
const resIPv6 = await sendAdminRequest(
'chat/users/ipbans/create',
localIPAddressV6
);
done();
});
test('verify IP address is blocked from the ban', async (done) => {
const response = await getAdminResponse('chat/users/ipbans');
expect(response.body).toHaveLength(2);
expect(onlyLocalIPAddress(response.body)).toBe(true);
done();
});
test('verify access is denied', async (done) => {
await request.get(`/api/chat?accessToken=${accessToken}`).expect(401);
done();
});
test('remove an ip address ban by admin', async (done) => {
const resIPv4 = await sendAdminRequest(
'chat/users/ipbans/remove',
localIPAddressV4
);
const resIPv6 = await sendAdminRequest(
'chat/users/ipbans/remove',
localIPAddressV6
);
done();
});
test('verify IP address is no longer banned', async (done) => {
const response = await getAdminResponse('chat/users/ipbans');
expect(response.body).toHaveLength(0);
done();
});
test('verify access is allowed after unban', async (done) => {
await request.get(`/api/chat?accessToken=${accessToken}`).expect(200);
done();
});
// This function expects the local address to be localIPAddressV4 & localIPAddressV6
function onlyLocalIPAddress(banInfo) {
for (let i = 0; i < banInfo.length; i++) {
if (
banInfo[i].ipAddress != localIPAddressV4 &&
banInfo[i].ipAddress != localIPAddressV6
) {
return false;
}
}
return true;
}

View File

@ -56,7 +56,10 @@ test('verify admin can make API call to mark message as hidden', async (done) =>
const message = res.body[0]; const message = res.body[0];
messageId = message.id; messageId = message.id;
await sendAdminPayload('chat/messagevisibility', { idArray: [messageId], visible: false }); await sendAdminPayload('chat/messagevisibility', {
idArray: [messageId],
visible: false,
});
}); });
test('verify message has become hidden', async (done) => { test('verify message has become hidden', async (done) => {

View File

@ -4,10 +4,8 @@ request = request('http://127.0.0.1:8080');
const getAdminResponse = require('./lib/admin').getAdminResponse; const getAdminResponse = require('./lib/admin').getAdminResponse;
test('correct number of log entries exist', (done) => { test('correct number of log entries exist', (done) => {
getAdminResponse('logs') getAdminResponse('logs').then((res) => {
.then((res) => { // expect(res.body).toHaveLength(8);
// expect(res.body).toHaveLength(8); done();
done(); });
});
}); });

View File

@ -1,172 +0,0 @@
const { test } = require('@jest/globals');
var request = require('supertest');
request = request('http://127.0.0.1:8080');
const WebSocket = require('ws');
const fs = require('fs');
const registerChat = require('./lib/chat').registerChat;
const sendChatMessage = require('./lib/chat').sendChatMessage;
const sendAdminRequest = require('./lib/admin').sendAdminRequest;
const sendAdminPayload = require('./lib/admin').sendAdminPayload;
const getAdminResponse = require('./lib/admin').getAdminResponse;
const randomNumber = require('./lib/rand').randomNumber;
const localIPAddressV4 = '127.0.0.1';
const localIPAddressV6 = '::1';
const testVisibilityMessage = {
body: 'message ' + randomNumber(100),
type: 'CHAT',
};
var userId;
var accessToken;
test('register a user', async (done) => {
const registration = await registerChat();
userId = registration.id;
accessToken = registration.accessToken;
done();
});
test('send a chat message', async (done) => {
sendChatMessage(testVisibilityMessage, accessToken, done);
});
test('set the user as moderator', async (done) => {
const res = await sendAdminPayload('chat/users/setmoderator', { userId: userId, isModerator: true });
done();
});
test('verify user is a moderator', async (done) => {
const response = await getAdminResponse('chat/users/moderators');
const tokenCheck = response.body.filter((user) => user.id === userId);
expect(tokenCheck).toHaveLength(1);
done();
});
test('verify user list is populated', async (done) => {
const ws = new WebSocket(
`ws://localhost:8080/ws?accessToken=${accessToken}`,
{
origin: 'http://localhost:8080',
}
);
ws.on('open', async function open() {
const response = await getAdminResponse('chat/clients');
expect(response.body.length).toBeGreaterThan(0);
// Optionally, if GeoIP is configured, check the location property.
if (fs.existsSync('../../../data/GeoLite2-City.mmdb')) {
expect(response.body[0].geo.regionName).toBe('Localhost');
}
ws.close();
});
ws.on('error', function incoming(data) {
console.error(data);
ws.close();
});
ws.on('close', function incoming(data) {
done();
});
});
test('disable a user by admin', async (done) => {
// To allow for visually being able to see the test hiding the
// message add a short delay.
await new Promise((r) => setTimeout(r, 1500));
const ws = new WebSocket(
`ws://localhost:8080/ws?accessToken=${accessToken}`,
{
origin: 'http://localhost:8080',
}
);
const res = await sendAdminPayload('chat/users/setenabled', { userId: userId, enabled: false });
await new Promise((r) => setTimeout(r, 1500));
done();
});
test('verify user is disabled', async (done) => {
const response = await getAdminResponse('chat/users/disabled');
const tokenCheck = response.body.filter((user) => user.id === userId);
expect(tokenCheck).toHaveLength(1);
done();
});
test('verify messages from user are hidden', async (done) => {
const response = await getAdminResponse('chat/messages');
const message = response.body.filter((obj) => {
return obj.user.id === userId;
});
expect(message[0].user.disabledAt).toBeTruthy();
done();
});
test('re-enable a user by admin', async (done) => {
const res = await sendAdminPayload('chat/users/setenabled', { userId: userId, enabled: true });
done();
});
test('verify user is enabled', async (done) => {
const response = await getAdminResponse('chat/users/disabled');
const tokenCheck = response.body.filter((user) => user.id === userId);
expect(tokenCheck).toHaveLength(0);
done();
});
test('ban an ip address by admin', async (done) => {
const resIPv4 = await sendAdminRequest('chat/users/ipbans/create', localIPAddressV4);
const resIPv6 = await sendAdminRequest('chat/users/ipbans/create', localIPAddressV6);
done();
});
test('verify IP address is blocked from the ban', async (done) => {
const response = await getAdminResponse('chat/users/ipbans');
expect(response.body).toHaveLength(2);
expect(onlyLocalIPAddress(response.body)).toBe(true);
done();
});
test('verify access is denied', async (done) => {
await request.get(`/api/chat?accessToken=${accessToken}`).expect(401);
done();
});
test('remove an ip address ban by admin', async (done) => {
const resIPv4 = await sendAdminRequest('chat/users/ipbans/remove', localIPAddressV4);
const resIPv6 = await sendAdminRequest('chat/users/ipbans/remove', localIPAddressV6);
done();
});
test('verify IP address is no longer banned', async (done) => {
const response = await getAdminResponse('chat/users/ipbans');
expect(response.body).toHaveLength(0);
done();
});
test('verify access is allowed after unban', async (done) => {
await request.get(`/api/chat?accessToken=${accessToken}`).expect(200);
done();
});
// This function expects the local address to be localIPAddressV4 & localIPAddressV6
function onlyLocalIPAddress(banInfo) {
for (let i = 0; i < banInfo.length; i++) {
if ((banInfo[i].ipAddress != localIPAddressV4) && (banInfo[i].ipAddress != localIPAddressV6)) {
return false
}
}
return true
}

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,26 @@
{ {
"name": "owncast-test-automation", "name": "owncast-test-automation",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "jest" "test": "jest --runInBand *.test.js"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"supertest": "^6.3.2", "supertest": "^6.3.2",
"websocket": "^1.0.32", "websocket": "^1.0.32",
"ajv": "^8.11.0", "ajv": "^8.11.0",
"ajv-draft-04" : "^1.0.0", "ajv-draft-04": "^1.0.0",
"jsonfile": "^6.1.0", "jsonfile": "^6.1.0",
"crypto-random": "^2.0.1" "crypto-random": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"jest": "^26.6.3" "jest": "^26.6.3"
} },
"jest": {
"verbose": true,
"maxWorkers": 1
}
} }

View File

@ -14,4 +14,5 @@ module.exports = defineConfig({
}); });
}, },
}, },
retries: 3,
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 186 KiB

View File

@ -21,7 +21,7 @@ export const TitleNotifier: FC<TitleNotifierProps> = ({ name }) => {
const [backgrounded, setBackgrounded] = useState(false); const [backgrounded, setBackgrounded] = useState(false);
const [title, setTitle] = useState(name); const [title, setTitle] = useState(name);
const { online } = serverStatus; const { online, streamTitle } = serverStatus;
const onBlur = () => { const onBlur = () => {
setBackgrounded(true); setBackgrounded(true);
@ -66,6 +66,17 @@ export const TitleNotifier: FC<TitleNotifierProps> = ({ name }) => {
setTitle(`💬 :: ${name}`); setTitle(`💬 :: ${name}`);
}, [chatMessages, name]); }, [chatMessages, name]);
useEffect(() => {
if (navigator.mediaSession === undefined) {
return;
}
navigator.mediaSession.metadata = new MediaMetadata({
title: streamTitle,
artist: name,
artwork: [{ src: '/logo' }],
});
}, [name, streamTitle]);
useEffect(() => { useEffect(() => {
if (!backgrounded) { if (!backgrounded) {
return; return;

1489
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
"@codemirror/lang-html": "^6.4.2", "@codemirror/lang-html": "^6.4.2",
"@codemirror/lang-javascript": "^6.1.2", "@codemirror/lang-javascript": "^6.1.2",
"@codemirror/lang-markdown": "6.2.5", "@codemirror/lang-markdown": "6.2.5",
"@codemirror/language-data": "6.5.0", "@codemirror/language-data": "6.5.1",
"@fontsource/inter": "^5.0.0", "@fontsource/inter": "^5.0.0",
"@fontsource/poppins": "5.0.13", "@fontsource/poppins": "5.0.13",
"@next/bundle-analyzer": "^14.0.0", "@next/bundle-analyzer": "^14.0.0",
@ -38,7 +38,7 @@
"interweave-autolink": "^5.1.0", "interweave-autolink": "^5.1.0",
"lodash": "4.17.21", "lodash": "4.17.21",
"next-export-i18n": "^2.1.0", "next-export-i18n": "^2.1.0",
"next": "14.2.1", "next": "14.2.2",
"next-pwa": "^5.6.0", "next-pwa": "^5.6.0",
"next-with-less": "3.0.1", "next-with-less": "3.0.1",
"picmo": "5.8.5", "picmo": "5.8.5",
@ -50,7 +50,7 @@
"react-hotkeys-hook": "4.5.0", "react-hotkeys-hook": "4.5.0",
"react-linkify": "1.0.0-alpha", "react-linkify": "1.0.0-alpha",
"react-markdown": "9.0.1", "react-markdown": "9.0.1",
"react-virtuoso": "4.7.8", "react-virtuoso": "4.7.9",
"recoil": "0.7.7", "recoil": "0.7.7",
"regexpu-core": "^5.3.2", "regexpu-core": "^5.3.2",
"sanitize-html": "^2.11.0", "sanitize-html": "^2.11.0",
@ -87,20 +87,20 @@
"@types/markdown-it": "14.0.1", "@types/markdown-it": "14.0.1",
"@types/node": "20.12.7", "@types/node": "20.12.7",
"@types/prop-types": "15.7.12", "@types/prop-types": "15.7.12",
"@types/react": "18.2.78", "@types/react": "18.2.79",
"@types/react-linkify": "1.0.4", "@types/react-linkify": "1.0.4",
"@types/sanitize-html": "^2.9.0", "@types/sanitize-html": "^2.9.0",
"@types/ua-parser-js": "0.7.39", "@types/ua-parser-js": "0.7.39",
"@types/video.js": "^7.3.50", "@types/video.js": "^7.3.50",
"@typescript-eslint/eslint-plugin": "7.6.0", "@typescript-eslint/eslint-plugin": "7.7.0",
"@typescript-eslint/parser": "7.6.0", "@typescript-eslint/parser": "7.7.0",
"babel-loader": "9.1.3", "babel-loader": "9.1.3",
"chromatic": "11.3.0", "chromatic": "11.3.0",
"css-loader": "7.1.1", "css-loader": "7.1.1",
"cypress": "^13.2.0", "cypress": "^13.2.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-next": "14.2.1", "eslint-config-next": "14.2.2",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "6.8.0", "eslint-plugin-jsx-a11y": "6.8.0",
@ -127,7 +127,7 @@
"storybook-addon-fetch-mock": "2.0.0", "storybook-addon-fetch-mock": "2.0.0",
"storybook-preset-less": "1.1.3", "storybook-preset-less": "1.1.3",
"style-dictionary": "3.9.2", "style-dictionary": "3.9.2",
"style-loader": "3.3.4", "style-loader": "4.0.0",
"stylelint": "^15.10.1", "stylelint": "^15.10.1",
"stylelint-config-standard": "^34.0.0", "stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^11.0.0", "stylelint-config-standard-scss": "^11.0.0",