Merge branch 'develop' into localization
2
.github/workflows/browser-testing.yml
vendored
@ -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
|
||||||
|
|||||||
65
.github/workflows/codeql-analysis.yml
vendored
@ -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
|
||||||
|
|||||||
2
.github/workflows/go-lint.yml
vendored
@ -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
|
||||||
|
|||||||
2
.github/workflows/go-tests.yaml
vendored
@ -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:
|
||||||
|
|||||||
2
.github/workflows/hls-tests.yml
vendored
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
2
.github/workflows/screenshots.yml
vendored
@ -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
|
||||||
|
|||||||
@ -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']
|
||||||
|
|
||||||
|
|||||||
@ -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
@ -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
@ -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
6
static/web/404/index.html
vendored
1
static/web/_next/static/chunks/310.9490b8d5fed1165d.js
vendored
Normal file
1
static/web/_next/static/chunks/4ad82c5e-6cc2d4ea01908bbd.js
vendored
Normal file
1
static/web/_next/static/chunks/6896-e687f392c06d3163.js
vendored
Normal file
1
static/web/_next/static/chunks/8262-6aa4acee34c12634.js
vendored
Normal 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()}]);
|
||||||
40
static/web/_next/static/css/6dddd42a88f781e5.css
vendored
40
static/web/_next/static/css/82ca0e3bd2bd92a7.css
vendored
Normal file
BIN
static/web/_next/static/media/inter-greek-300-normal.d8faf285.woff
vendored
Normal file
6
static/web/admin/access-tokens/index.html
vendored
6
static/web/admin/actions/index.html
vendored
6
static/web/admin/chat/emojis/index.html
vendored
6
static/web/admin/chat/messages/index.html
vendored
6
static/web/admin/chat/users/index.html
vendored
6
static/web/admin/config-chat/index.html
vendored
6
static/web/admin/config-notify/index.html
vendored
6
static/web/admin/config-video/index.html
vendored
6
static/web/admin/config/general/index.html
vendored
6
static/web/admin/config/server/index.html
vendored
6
static/web/admin/hardware-info/index.html
vendored
6
static/web/admin/help/index.html
vendored
6
static/web/admin/index.html
vendored
6
static/web/admin/logs/index.html
vendored
6
static/web/admin/stream-health/index.html
vendored
6
static/web/admin/upgrade/index.html
vendored
6
static/web/admin/viewer-info/index.html
vendored
6
static/web/admin/webhooks/index.html
vendored
8
static/web/embed/chat/readonly/index.html
vendored
8
static/web/embed/chat/readwrite/index.html
vendored
8
static/web/embed/video/index.html
vendored
8
static/web/index.html
vendored
2
static/web/sw.js
vendored
195
test/automated/api/004_chatusers.test.js
Normal 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;
|
||||||
|
}
|
||||||
@ -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) => {
|
||||||
@ -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();
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
22814
test/automated/api/package-lock.json
generated
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -14,4 +14,5 @@ module.exports = defineConfig({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
retries: 3,
|
||||||
});
|
});
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 186 KiB |
@ -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
@ -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",
|
||||||
|
|||||||