Compare commits

..

20 Commits

Author SHA1 Message Date
vel 370e056a2b
(doc): readme changes 2022-02-02 16:13:08 -08:00
vel 55e5136e88
Merge pull request #1 from QPixel/refactor/main
Refactor main so it works now
2022-02-02 16:04:12 -08:00
vel 2586e60f0c
finally fixed player 2022-02-01 17:07:56 -08:00
vel 7a7e99337f
websocket is more stable 2022-01-31 12:29:06 -08:00
vel f52b9a9c53
player is super broken 2022-01-31 11:21:04 -08:00
vel 9350346b4c
state stuff in backend 2022-01-31 00:30:29 -08:00
vel 728f2d67d0
feat(ws): barebones SetPlayhead event 2022-01-27 22:48:21 -08:00
vel 5f81ed1756
more stuff 2022-01-27 14:29:54 -08:00
vel 7ba50976cf
even more websocket changes 2022-01-26 23:45:57 -08:00
vel e8e5b7e409
better websocket error/close handling 2022-01-26 23:45:39 -08:00
vel cad2d85ce4
websocket stuff 2022-01-26 22:30:05 -08:00
vel 013e6c5b01
small changes to the hero 2022-01-24 20:17:18 -08:00
vel e4d7906cb5
color mode stuff 2022-01-24 19:45:47 -08:00
vel 0fd974266c
some build/deploy stuff 2022-01-24 19:39:51 -08:00
vel 521b3eef20
ping event 2022-01-23 23:37:48 -08:00
vel ef4e0b4ac2
ws works now 2022-01-23 01:22:21 -08:00
vel 15a2fe7420
more stuff 2022-01-22 23:49:01 -08:00
vel ef022499e7
added api routes 2022-01-22 23:02:47 -08:00
vel fd536075c5
tons of backend changes 2022-01-22 22:57:25 -08:00
vel 996bfea07c
header and login 2022-01-22 19:16:30 -08:00
59 changed files with 1455 additions and 600 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
*/.env */.env
test test
*/.DS_STORE
.DS_STORE

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2
}

23
README.md Normal file
View File

@ -0,0 +1,23 @@
# watchtogether
This was a project that I created over 7 days of working while having covid :)
I probably won't do much with the codebase, but enjoy how I basically figured out how to sync video streams with multiple clients
## The Stack
- frontend
- next js
- websockets
- chakra-ui
- react-player
- backend
- go
- gorilla/websockets
## how 2 deploy?
you don't

1
backend/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
cmd/watchtogether/watchtogether

View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

100
backend/.idea/workspace.xml Normal file
View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="ALL" />
</component>
<component name="ChangeListManager">
<list default="true" id="8a64704d-5500-41a6-aa4c-e275933fc58c" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Go File" />
</list>
</option>
</component>
<component name="GOROOT" url="file:///usr/lib/go" />
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="GitSEFilterConfiguration">
<file-type-list>
<filtered-out-file-type name="LOCAL_BRANCH" />
<filtered-out-file-type name="REMOTE_BRANCH" />
<filtered-out-file-type name="TAG" />
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
</file-type-list>
</component>
<component name="GoLibraries">
<option name="indexEntireGoPath" value="false" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2456l5ltkI1NptLGqpacBxKAWdj" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="ASKED_ADD_EXTERNAL_FILES" value="true" />
<property name="DefaultGoTemplateProperty" value="Go File" />
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="go.format.on.save.advertiser.fired" value="true" />
<property name="go.formatter.settings.were.checked" value="true" />
<property name="go.import.settings.migrated" value="true" />
<property name="go.modules.go.list.on.any.changes.was.set" value="true" />
<property name="go.sdk.automatically.set" value="true" />
<property name="go.watchers.conflict.with.on.save.actions.check.performed" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
</component>
<component name="RunManager">
<configuration name="go build github.com/qpixel/watchtogether/cmd/watchtogether" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
<module name="backend" />
<working_directory value="$PROJECT_DIR$" />
<kind value="PACKAGE" />
<package value="github.com/qpixel/watchtogether/cmd/watchtogether" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$/cmd/watchtogether/main.go" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Go Build.go build github.com/qpixel/watchtogether/cmd/watchtogether" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
</component>
<component name="VgoProject">
<integration-enabled>true</integration-enabled>
</component>
</project>

4
backend/Caddyfile Normal file
View File

@ -0,0 +1,4 @@
localhost:3001
file_server
reverse_proxy 127.0.0.1:8080

View File

1
backend/README.md Normal file
View File

@ -0,0 +1 @@
# backend

View File

@ -0,0 +1,12 @@
#!/bin/bash
VERSION=v1.0.0
COMMIT_HASH=$(git rev-parse --short HEAD)
ENVIRONMENT=$ENV
BUILDSTRING="$VERSION-$COMMIT_HASH-$ENVIRONMENT"
echo The environment is "$ENVIRONMENT"
echo Building watch together backend "$VERSION-$COMMIT_HASH"
if [[ "$ENVIRONMENT" == "dev" ]]; then
go build -gcflags="all=-N -l" -ldflags "-X github.com/qpixel/watchtogether/cmd/watchtogether/main.VERSION=${BUILDSTRING} -X github.com/qpixel/watchtogether/cmd/watchtogether/main.ENVIRONMENT=${ENVIRONMENT}";
else
go build -ldflags "-X github.com/qpixel/watchtogether/cmd/watchtogether/main.VERSION=${BUILDSTRING} -X github.com/ubergeek77/uberbot/v2/core.ENVIRONMENT=${ENVIRONMENT}";
fi

View File

@ -1,72 +1,62 @@
package main package main
import ( import (
"flag" "encoding/json"
"fmt" "github.com/gorilla/mux"
"github.com/peterbourgon/ff/v3" "github.com/qpixel/watchtogether/internal/ws"
"github.com/qpixel/watchtogether/internal/logger" tlog "github.com/ubergeek77/tinylog"
"github.com/qpixel/watchtogether/internal/server" "net/http"
"github.com/ubergeek77/tinylog"
"os"
"runtime"
) )
type flags struct { var log = tlog.NewTaggedLogger("Logger", tlog.NewColor("38;5;111"))
loglvl int
port int
}
func newFlags(args []string) (flgs flags, err error) {
fs := flag.NewFlagSet(args[0], flag.ContinueOnError)
var ( var (
loglvl = fs.Int("loglvl", tinylog.TraceLevel, "sets the log level (also via LOG_LEVEL)") VERSION string
port = fs.Int("port", 4000, "sets the port to use (also via PORT)") ENVIRONMENT string
) )
// Parse the command line flags from above
err = ff.Parse(fs, args[1:], ff.WithEnvVarNoPrefix())
if err != nil {
return flgs, err
}
return flags{
loglvl: *loglvl,
port: *port,
}, nil
}
func main() { func main() {
if err := run(os.Args); err != nil { r := mux.NewRouter()
tinylog.DefaultLogger().Errorf("error from main.run(): %s\n", err) hub := ws.NewHub()
os.Exit(1) go hub.Run()
}
} r.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
ws.ServeWs(hub, w, r)
func run(args []string) error { })
r.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
flgs, err := newFlags(args) w.Header().Set("Content-Type", "application/json")
resp := make(map[string]interface{})
resp["status"] = "ok"
resp["users"] = len(hub.Clients)
jsonResp, err := json.Marshal(resp)
if err != nil { if err != nil {
return err log.Fatalf("error in marshaling json. err: %s", err.Error())
} }
// setup logger with defaults _, err = w.Write(jsonResp)
lgr := logger.NewLogger(flgs.loglvl, "WatchTogether")
lgr.Infof("go runtime ver: %s", runtime.Version())
lgr.Infof("Logging level has been set to %d", lgr.LogLevel)
mr := server.NewMuxRouter()
serverDriver := server.NewDriver()
params := server.NewServerParams(lgr, serverDriver)
s, err := server.NewServer(mr, params)
if err != nil { if err != nil {
lgr.Errorf("err: %s", err) return
lgr.Fatal("error in server.NewServer") }
return
})
r.Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
resp := make(map[string]interface{})
resp["status"] = "ok"
jsonResp, err := json.Marshal(resp)
if err != nil {
log.Fatalf("error in marshaling json. err: %s", err.Error())
}
_, err = w.Write(jsonResp)
if err != nil {
return
}
return
})
log.Infof("starting backend")
log.Infof("running version %s. environment: %s", VERSION, ENVIRONMENT)
err := http.ListenAndServe(":8080", r)
if err != nil {
log.Errorf("%s", err.Error())
log.Fatalf("unable to serve on port 8080")
} }
s.Addr = fmt.Sprintf(":%d", flgs.port)
return s.ListenAndServe()
} }

View File

@ -0,0 +1,3 @@
version: "3.9"
services:
caddy:

View File

@ -2,16 +2,8 @@ module github.com/qpixel/watchtogether
go 1.17 go 1.17
replace (
github.com/qpixel/tloghttp => ../../tloghttp
github.com/qpixel/tlogbuilder => ../../tlogbuilder
)
require ( require (
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0 // indirect
github.com/justinas/alice v1.2.0 github.com/gorilla/websocket v1.4.2 // indirect
github.com/peterbourgon/ff/v3 v3.1.2 github.com/ubergeek77/tinylog v1.0.0 // indirect
github.com/qpixel/tloghttp v0.0.0-20211222065322-cd8d1a945a36
github.com/qpixel/tlogbuilder v0.0.0
github.com/ubergeek77/tinylog v1.0.0
) )

View File

@ -1,13 +1,6 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM=
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
github.com/ubergeek77/tinylog v1.0.0 h1:gsq98mbig3LDWhsizOe2tid12wHUz/mrkDlmgJ0MZG4= github.com/ubergeek77/tinylog v1.0.0 h1:gsq98mbig3LDWhsizOe2tid12wHUz/mrkDlmgJ0MZG4=
github.com/ubergeek77/tinylog v1.0.0/go.mod h1:NzUi4PkRG2hACL4cGgmW7db6EaKjAeqrqlVQnJdw78Q= github.com/ubergeek77/tinylog v1.0.0/go.mod h1:NzUi4PkRG2hACL4cGgmW7db6EaKjAeqrqlVQnJdw78Q=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,45 +0,0 @@
// Copyright 2018 The Go Cloud Development Kit Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// @gilcrest - removed TLSServer interface
// Package driver defines an interface for custom HTTP listeners.
// Application code should use package server.
package driver
import (
"context"
"net/http"
)
// Server dispatches requests to an http.Handler.
type Server interface {
// ListenAndServe listens on the TCP network address addr and then
// calls Serve with handler to handle requests on incoming connections.
// The addr argument will be a non-empty string specifying "host:port".
// The http.Handler will always be non-nil.
// Drivers must block until serving is done (or
// return an error if serving can't occur for some reason), serve
// requests to the given http.Handler, and be interruptable by Shutdown.
// Drivers should use the given address if they serve using TCP directly.
ListenAndServe(addr string, h http.Handler) error
// Shutdown gracefully shuts down the server without interrupting
// any active connections. If the provided context expires before
// the shutdown is complete, Shutdown returns the context's error,
// otherwise it returns any error returned from closing the Server's
// underlying Listener(s).
Shutdown(ctx context.Context) error
}

View File

@ -1,17 +0,0 @@
package logger
import (
"github.com/ubergeek77/tinylog"
)
func NewLogger(lvl int, tag string) *tinylog.Logger {
// cfg contains our custom config for tinylog
cfg := tinylog.NewConfig()
cfg.LogLevel = lvl
cfg.LogPrefix = tinylog.GenerateTag(tag, tinylog.NewColor("38;5;133"), cfg)
//initialize the instance of tinylog
lgr := tinylog.NewLogger(cfg)
return lgr
}

View File

@ -1,59 +0,0 @@
package server
import (
"github.com/justinas/alice"
"github.com/qpixel/tlogbuilder"
"github.com/qpixel/tloghttp"
"net/http"
"time"
)
// MIT License
//
//Copyright (c) 2017 Dan Gillis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
// LoggerChain returns a middleware chain (via alice.Chain)
// initialized with all the standard middleware handlers for logging. The logger
// will be added to the request context for subsequent use with pre-populated
// fields, including the request method, url, status, size, duration, remote IP,
// user agent, referer. A unique Request ID is also added to the logger, context
// and response headers.
func (s *Server) loggerChain() alice.Chain {
ac := alice.New(tloghttp.NewHandler(s.logger),
tloghttp.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
tlogbuilder.CreateBuilder(tloghttp.FromRequest(r)).
Info().
Str("method", r.Method).
Stringer("url", r.URL).
Int("status", status).
Int("size", size).
Dur("duration", duration).
Msg("request logged").
Build()
}),
tloghttp.RemoteAddrHandler("remote_ip"),
tloghttp.UserAgentHandler("user_agent"),
tloghttp.RefererHandler("referer"),
//tloghttp.RequestIDHandler("request_id", "Request-Id"),
)
return ac
}

View File

@ -1,14 +0,0 @@
package server
import "net/http"
const (
contentTypeHeaderKey string = "Content-Type"
appJSONContentTypeHeaderVal string = "application/json",
)
func (s *Server) routes() {
s.router.Handle("/", func(http.ResponseWriter, *http.Response) {
})
}

View File

@ -1,159 +0,0 @@
// Copyright 2018 The Go Cloud Development Kit Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// @QPixel edits - I have made a copy of the go-api-basic server code and made
// the following changes:
//
// - removed zerolog dependency
package server
import (
"context"
"errors"
"github.com/gorilla/mux"
"github.com/qpixel/watchtogether/internal/driver"
"github.com/ubergeek77/tinylog"
"io"
"net/http"
"time"
)
const pathPrefix = "/api"
// Server represents an HTTP server
type Server struct {
router *mux.Router
driver driver.Server
logger *tinylog.Logger
Addr string
// Authorization
// Services
}
//type LoggerService interface {
// Read() logger.LoggerResponse
//
//}
// SvrParams is the set of configuration parameters for a Server
type SvrParams struct {
// Logger is used for the server logging
Logger *tinylog.Logger
// Driver serves HTTP requests
Driver driver.Server
}
// NewServerParams is an initializer for ServerParams
func NewServerParams(lgr *tinylog.Logger, d driver.Server) *SvrParams {
options := &SvrParams{
Logger: lgr,
Driver: d,
}
return options
}
// NewServer initializes a new Server and registers
// routes to the router mux
// todo add error checking
func NewServer(r *mux.Router, params *SvrParams) (*Server, error) {
s := &Server{router: r}
s.logger = params.Logger
s.driver = params.Driver
return s, nil
}
func (s *Server) ListenAndServe() error {
if s.Addr == "" {
return errors.New("server Addr is empty")
}
if s.router == nil {
return errors.New("server router is nil")
}
if s.driver == nil {
return errors.New("server driver is nil")
}
s.logger.Infof("server is listening on %s", s.Addr)
return s.driver.ListenAndServe(s.Addr, s.router)
}
// Shutdown will gracefully shut down the server without interrupting any active connections
func (s *Server) Shutdown(ctx context.Context) error {
if s.driver == nil {
return nil
}
return s.driver.Shutdown(ctx)
}
// Driver implements the driver.Server interface. The zero value is a valid http.Server
type Driver struct {
Server http.Server
}
// NewDriver intializes a Driver with http.Server using default timeouts
func NewDriver() *Driver {
return &Driver{
Server: http.Server{
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
},
}
}
// ListenAndServe sets the address and handler on Driver's http.Server
func (d *Driver) ListenAndServe(addr string, h http.Handler) error {
d.Server.Addr = addr
d.Server.Handler = h
return d.Server.ListenAndServe()
}
// Shutdown gracefully shuts down the server without interrupting any active connections,
// by calling Shutdown on Driver's http.Server
func (d *Driver) Shutdown(ctx context.Context) error {
return d.Server.Shutdown(ctx)
}
// NewMuxRouter initializes a gorilla/mux router and
// adds the /api subroute to it
func NewMuxRouter() *mux.Router {
// initializer gorilla/mux router
r := mux.NewRouter()
// send Router through PathPrefix method to validate any standard
// subroutes you may want for your APIs. e.g. I always want to be
// sure that every request has "/api" as part of its path prefix
// without having to put it into every handle path in my various
// routing functions
s := r.PathPrefix(pathPrefix).Subrouter()
return s
}
func decoderErr(err error) error {
switch {
case err == io.EOF:
return errors.New("request body cannot be empty")
case err == io.ErrUnexpectedEOF:
return errors.New("malformed json")
case err != nil:
return err
}
return nil
}

View File

@ -0,0 +1,129 @@
package ws
import (
"bytes"
"github.com/gorilla/websocket"
"net/http"
"time"
)
const (
// time allowed to write a message to the peer.
writeWait = 10 * time.Second
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
maxMessageSize = 512
)
var (
newline = []byte{'\n'}
space = []byte{' '}
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
type Client struct {
hub *Hub
conn *websocket.Conn
send chan []byte
}
// readPump pumps messages from the websocket connection to the hub.
//
// The application runs readPump in a per-connection goroutine. The application
// ensures that there is at most one reader on a connection by executing all
// reads from this goroutine.
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
c.conn.SetReadLimit(maxMessageSize)
c.conn.SetReadDeadline(time.Now().Add(pongWait))
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Infof("error: %v", err)
}
break
}
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
c.hub.broadcast <- RawMessage{
Client: c,
Data: message,
}
}
}
// writePump pumps messages from the hub to the websocket connection.
//
// A goroutine running writePump is started for each connection. The
// application ensures that there is at most one writer to a connection by
// executing all writes from this goroutine.
func (c *Client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
// The hub closed the channel.
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := c.conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
w.Write(message)
// Add queued chat messages to the current websocket message.
n := len(c.send)
for i := 0; i < n; i++ {
w.Write(newline)
w.Write(<-c.send)
}
if err := w.Close(); err != nil {
return
}
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
// ServeWs handles websocket requests from the peer.
func ServeWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
upgrader.CheckOrigin = func(r *http.Request) bool {
return true
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error(err.Error())
return
}
client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
client.hub.register <- client
// Allow collection of memory referenced by the caller by doing all work in
// new goroutines.
go client.writePump()
go client.readPump()
}

View File

@ -0,0 +1,76 @@
package ws
//todo better data deserialization
type IdentityData struct {
ClientID string `json:"clientID"`
User User `json:"user"`
}
func handleIdentifyEvent(message *Message) {
d := message.Data.(map[string]interface{})
if id, ok := d["clientID"]; ok {
log.Infof("Client %s has sent identify event", id.(string))
}
user := d["user"].(map[string]interface{})
userId := user["id"].(string)
playhead := message.hub.State.playhead
paused := message.hub.State.paused
m := Message{
MessageData: MessageData{
Type: Identify,
Data: map[string]interface{}{
"admin": message.hub.State.IsAdmin(userId),
"playlist": "http://localhost:8081/BelleOpening.m3u8",
"hasController": message.hub.State.IsController(userId),
"playhead": playhead,
"paused": paused,
"user": d["user"],
},
},
}
message.send <- m.SerializeMessage().Data
}
func handlePingEvent(message *Message) {
m := Message{
message.Client,
MessageData{
Type: Pong,
Data: nil,
},
}
message.send <- m.SerializeMessage().Data
}
func handleGetPlayhead(message *Message) {
}
func handleSetPlayhead(message *Message) {
d := message.Data.(map[string]interface{})
m := Message{
MessageData: MessageData{
Type: SetPlayhead,
Data: map[string]interface{}{
"playhead": d["playhead"],
"paused": d["paused"],
},
},
}
log.Infof("Received SetPlayhead event. playhead is at %s", d["playhead"])
err := message.hub.State.setPlayhead(d["playhead"].(float64))
if err != nil {
log.Errorf("unable to set playhead. %s", err)
}
err = message.hub.State.setPaused(d["paused"].(bool))
if err != nil {
log.Errorf("unable to set paused. %s", err)
}
for client := range message.Client.hub.Clients {
if client == message.Client {
continue
}
client.send <- m.SerializeMessage().Data
}
}

View File

@ -0,0 +1,69 @@
package ws
type Hub struct {
// Registered Clients
Clients map[*Client]bool
// State
State *State
// Inbound messages from the Clients
broadcast chan RawMessage
// Register requests from the Clients
register chan *Client
// Unregister requests from Clients
unregister chan *Client
}
func NewHub() *Hub {
return &Hub{
broadcast: make(chan RawMessage),
register: make(chan *Client),
unregister: make(chan *Client),
Clients: make(map[*Client]bool),
State: NewState(),
}
}
func (h *Hub) handleMessage(rm RawMessage) {
m := rm.UnserializeData()
switch m.Type {
case Identify:
handleIdentifyEvent(&m)
case Ping:
handlePingEvent(&m)
case GetPlayhead:
handleGetPlayhead(&m)
case SetPlayhead:
handleSetPlayhead(&m)
default:
return
}
return
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.Clients[client] = true
case client := <-h.unregister:
if _, ok := h.Clients[client]; ok {
delete(h.Clients, client)
close(client.send)
}
case message := <-h.broadcast:
go h.handleMessage(message)
//for client := range h.Clients {
// select {
// case client.send <- message:
// default:
// close(client.send)
// delete(h.Clients, client)
// }
//}
}
}
}

View File

@ -0,0 +1,71 @@
package ws
import (
"encoding/json"
tlog "github.com/ubergeek77/tinylog"
)
var log = tlog.NewTaggedLogger("WS", tlog.NewColor("38;5;111"))
type MessageTypes float64
// todo rewrite to use an event handler system
const (
Ping MessageTypes = iota
Pong
Identify
GetPlayhead
SetPlayhead
)
type MessageData struct {
Type MessageTypes `json:"type"`
RawData json.RawMessage `json:"data,omitempty"`
Data interface{} `json:"-"`
}
type Message struct {
*Client
MessageData
}
type RawMessage struct {
Client *Client
Data []byte
}
func (rm RawMessage) UnserializeData() Message {
var md MessageData
if err := json.Unmarshal(rm.Data, &md); err != nil {
log.Errorf("error unmarshalling message, %s", err.Error())
}
if md.RawData != nil && len(md.RawData) > 0 {
if err := json.Unmarshal(md.RawData, &md.Data); err != nil {
// handle error
log.Errorf("error unmarshalling data, %s", err.Error())
}
}
m := Message{
Client: rm.Client,
MessageData: md,
}
return m
}
func (m Message) SerializeMessage() RawMessage {
var err error
if m.Data != nil {
m.RawData, err = json.Marshal(m.Data)
if err != nil {
log.Errorf("unable to marshal data, %s", err.Error())
}
}
data, err := json.Marshal(m.MessageData)
if err != nil {
log.Errorf("unable to marshal message, %s", err.Error())
}
return RawMessage{
Data: data,
}
}

View File

@ -0,0 +1,54 @@
package ws
import (
"errors"
"sync"
)
type State struct {
sync.RWMutex
playhead float64
controllerUserId string
adminUserId string
paused bool
}
func NewState() *State {
return &State{
playhead: 0,
controllerUserId: "218072060923084802",
adminUserId: "218072060923084802",
}
}
func (s *State) setPlayhead(playhead float64) error {
if s == nil {
return errors.New("unable to find state")
}
s.Lock()
defer s.Unlock()
s.playhead = playhead
return nil
}
func (s *State) setPaused(paused bool) error {
if s == nil {
return errors.New("unable to find state")
}
s.Lock()
defer s.Unlock()
s.paused = paused
return nil
}
func (s *State) IsController(userID string) bool {
if userID == s.controllerUserId {
return true
}
return false
}
func (s *State) IsAdmin(userID string) bool {
if userID == s.adminUserId {
return true
}
return false
}

View File

@ -0,0 +1,6 @@
package ws
type User struct {
ID string `json:"id"`
Name string `json:"name"`
}

View File

@ -1,13 +0,0 @@
package services
import "github.com/ubergeek77/tinylog"
// LoggerService reads and updates the logger state
type LoggerService struct {
Logger tinylog.Logger
}
// NewLoggerService creates a new instance of the logging service
func NewLoggerService(logger tinylog.Logger) *LoggerService {
return &LoggerService{Logger: logger}
}

View File

@ -0,0 +1,6 @@
DISCORD_ID=
DISCORD_SECRET=
SECRET=
NEXT_PUBLIC_CLIENT_ID=
NEXTAUTH_URL=
NEXT_PUBLIC_WS_URI=

2
frontend/.gitignore vendored
View File

@ -32,3 +32,5 @@ yarn-error.log*
# vercel # vercel
.vercel .vercel

View File

@ -1,39 +1 @@
# Example app with [chakra-ui](https://github.com/chakra-ui/chakra-ui) and TypeScript # frontend
This example features how to use [chakra-ui](https://github.com/chakra-ui/chakra-ui) as the component library within a Next.js app with TypeScript.
Next.js and chakra-ui have built-in TypeScript declarations, so we'll get autocompletion for their modules straight away.
We are connecting the Next.js `_app.js` with `chakra-ui`'s Provider and theme so the pages can have app-wide dark/light mode. We are also creating some components which shows the usage of `chakra-ui`'s style props.
## Preview
Preview the example live on [StackBlitz](http://stackblitz.com/):
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-chakra-ui-typescript)
## Deploy your own
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-chakra-ui-typescript&project-name=with-chakra-ui-typescript&repository-name=with-chakra-ui-typescript)
## How to use
### Using `create-next-app`
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
```bash
npx create-next-app --example with-chakra-ui-typescript with-chakra-ui-typescript-app
# or
yarn create next-app --example with-chakra-ui-typescript with-chakra-ui-typescript-app
```
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
## Notes
Chakra has supported Gradients and RTL in `v1.1`. To utilize RTL, [add RTL direction and swap](https://chakra-ui.com/docs/features/rtl-support).
If you don't have multi-direction app, you should make `<Html lang="ar" dir="rtl">` inside `_document.ts`.

5
frontend/next.config.js Normal file
View File

@ -0,0 +1,5 @@
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
module.exports = withBundleAnalyzer({})

View File

@ -1,9 +1,13 @@
{ {
"private": true, "private": true,
"name": "frontend",
"scripts": { "scripts": {
"dev": "next", "dev": "next",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start",
"analyze": "cross-env ANALYZE=true next build",
"analyze:server": "cross-env BUNDLE_ANALYZE=server next build",
"analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/icons": "^1.0.5", "@chakra-ui/icons": "^1.0.5",
@ -11,19 +15,30 @@
"@chakra-ui/theme-tools": "1.1.2", "@chakra-ui/theme-tools": "1.1.2",
"@emotion/react": "11.1.5", "@emotion/react": "11.1.5",
"@emotion/styled": "11.1.5", "@emotion/styled": "11.1.5",
"consola": "^2.15.3",
"events": "^3.3.0",
"framer-motion": "^4.0.3", "framer-motion": "^4.0.3",
"next": "latest", "next": "latest",
"next-auth": "^4.1.2", "next-auth": "^4.1.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-player": "^2.9.0", "react-player": "^2.9.0",
"websocket": "^1.0.34" "uuid": "^8.3.2",
"ws": "^8.4.2"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^12.0.8",
"@types/events": "^3.0.0",
"@types/node": "^14.6.0", "@types/node": "^14.6.0",
"@types/react": "^17.0.3", "@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3", "@types/react-dom": "^17.0.3",
"@types/uuid": "^8.3.4",
"@types/websocket": "^1.0.4", "@types/websocket": "^1.0.4",
"@types/ws": "^8.2.2",
"cross-env": "^7.0.3",
"typescript": "4.3.2" "typescript": "4.3.2"
},
"volta": {
"node": "16.13.2"
} }
} }

View File

@ -1,19 +1,23 @@
import { Flex, useColorMode, FlexProps } from '@chakra-ui/react' import React, { FC } from "react";
import { Flex, useColorMode, FlexProps } from "@chakra-ui/react";
export const Container = (props: FlexProps) => { export const Container: FC<FlexProps> = (props: FlexProps) => {
const { colorMode } = useColorMode() const { colorMode } = useColorMode();
const bgColor = { light: 'gray.50', dark: 'gray.900' } const bgColor = { light: "gray.50", dark: "gray.900" };
const color = { light: 'black', dark: 'white' } const color = { light: "black", dark: "white" };
return ( return (
<Flex <Flex
direction="column" direction="column"
alignItems="center"
justifyContent="flex-start" justifyContent="flex-start"
bg={bgColor[colorMode]} bg={bgColor[colorMode]}
color={color[colorMode]} color={color[colorMode]}
{...props} {...props}
/> />
) );
} };
Container.defaultProps = {
alignItems: "center",
};

View File

@ -0,0 +1,37 @@
import { Box, Flex, Heading } from "@chakra-ui/layout";
import { Text } from "@chakra-ui/react";
import React, { FC } from "react";
const Header: FC = ({ children }) => {
return (
<Flex
as="header"
position="fixed"
w="100%"
justifyContent="space-between"
outline="1"
padding="1"
>
<Flex
alignSelf="flex-start"
bgGradient="linear(to-l, #7928CA, #FF0080)"
bgClip="text"
height="100%"
>
<Heading fontSize="2rem">Watch Together</Heading>
<Text
fontStyle="italic"
ml="2"
mt="1"
fontSize="2xl"
fontWeight="semibold"
>
admin
</Text>
</Flex>
{children}
</Flex>
);
};
export default Header;

View File

@ -1,12 +1,23 @@
import { Flex, Heading } from "@chakra-ui/react"; import { Flex, Heading, keyframes } from "@chakra-ui/react";
import { css } from "@emotion/react";
const gradient = keyframes`
0% { background-position: 0% 0%; }
100% { background-position: 100% 0%; }
}
`;
export const Hero = ({ title }: { title: string }) => ( export const Hero = ({ title }: { title: string }) => (
<Flex <Flex
justifyContent="center" justifyContent="center"
alignItems="center" alignItems="center"
height="100vh" height="100vh"
bgGradient="linear(to-l, #7928CA, #FF0080)" backgroundImage="linear-gradient(90deg,#cf5c5c,#c19b4a,#def2a0,#c6ee4a,#42eca6,#64b3d9,#208ea2,#498ada,#5b73df,#897ed3,#cf5c5c,#c19b4a)"
backgroundSize="1100% 100%"
bgClip="text" bgClip="text"
css={css`
animation: ${gradient} 14s linear infinite;
`}
> >
<Heading fontSize="6vw">{title}</Heading> <Heading fontSize="6vw">{title}</Heading>
</Flex> </Flex>

View File

@ -1,19 +1,83 @@
import React, { FC } from "react"; import { Box } from "@chakra-ui/react";
import ReactPlayer, { ReactPlayerProps } from "react-player"; import React, { forwardRef } from "react";
import ReactPlayer, { Config, ReactPlayerProps } from "react-player";
type PlayerProps = { id: string } & ReactPlayerProps; const Player = forwardRef<ReactPlayer, ReactPlayerProps>((props, ref) => {
const config: Config = {
const Player: FC<PlayerProps> = ({ id, config }) => {
return <ReactPlayer url={id} config={config} />;
};
Player.defaultProps = {
id: "",
config: {
file: { file: {
forceHLS: true, forceHLS: true,
}, },
},
}; };
// useEffect(() => {
// if (playerRef.current && typeof props.identity !== "undefined") {
// console.log(props.identity.playhead);
// playerRef.current.seekTo(props.identity.playhead);
// setPaused(props.identity.paused);
// }
// }, []);
// socket.emitter.once(SocketEvents.SetPlayhead, (e) => {
// console.log(e);
// playerRef.current.seekTo(e.playhead);
// setPaused(e.paused);
// });
// const onSeek = (playedSeconds: number) => {
// if (!props.identity.admin) return;
// if (paused) {
// socket?.send(
// MessageUtil.encode(
// new Message(MessageTypes.SetPlayhead, {
// playhead: playedSeconds,
// paused: true,
// })
// )
// );
// return;
// }
// socket?.send(
// MessageUtil.encode(
// new Message(MessageTypes.SetPlayhead, {
// playhead: playedSeconds,
// paused: false,
// })
// )
// );
// };
// const onPause = () => {
// if (!props.identity.admin) return;
// setPaused(true);
// socket?.send(
// MessageUtil.encode(
// new Message(MessageTypes.SetPlayhead, {
// playhead: playerRef.current.getCurrentTime(),
// paused: true,
// })
// )
// );
// };
// const onPlay = () => {
// if (!props.identity.admin) return;
// setPaused(false);
// socket?.send(
// MessageUtil.encode(
// new Message(MessageTypes.SetPlayhead, {
// playhead: playerRef.current.getCurrentTime(),
// paused: false,
// })
// )
// );
// };
return (
<Box height="100vh" width="100vw">
<ReactPlayer
url={props.url}
width="100%"
height="100%"
config={config}
ref={ref}
{...props}
/>
</Box>
);
});
export default Player; export default Player;

View File

@ -0,0 +1,21 @@
import { Avatar } from "@chakra-ui/avatar";
import { Box, Flex, Text } from "@chakra-ui/layout";
import { Session } from "next-auth";
import React, { FC } from "react";
interface UserDataProps {
user: Session["user"];
}
const UserData: FC<UserDataProps> = ({ user: { name, image } }) => {
return (
<Flex alignSelf="flex-end" mr="2" mt="1">
<Avatar src={image} />
<Box ml="3" mt="2">
<Text fontWeight="bold">{name}</Text>
</Box>
</Flex>
);
};
export default UserData;

View File

@ -0,0 +1,32 @@
import { User } from "next-auth";
import { useEffect, useState } from "react";
import PlayerSocket from "../ws/websocket";
interface useWSProps {
user: User;
}
// todo write websocket reconnector
const useWS = ({ user }: useWSProps): PlayerSocket | null => {
if (typeof window === "undefined") {
return;
}
// todo checkout usecallback
const [socket, setSocket] = useState<PlayerSocket>(null);
useEffect(() => {
if (socket !== null) {
return;
}
let internalSocket = new PlayerSocket(user);
setSocket(internalSocket);
return () => {
if (internalSocket.readyState !== WebSocket.OPEN) {
return internalSocket.close();
}
};
}, []);
return socket;
};
export default useWS;

View File

@ -0,0 +1,14 @@
export enum MessageTypes {
Ping,
Pong,
Identify,
GetPlayhead,
SetPlayhead,
}
interface IMessage {
type: MessageTypes;
data?: unknown;
}
export default IMessage;

View File

@ -0,0 +1,6 @@
interface IUser {
id: string;
name: string;
}
export default IUser;

View File

@ -0,0 +1,13 @@
import IUser from "./IUser";
interface IdentityData {
admin?: boolean;
hasController?: boolean;
clientID?: string;
playlist?: string;
playhead?: number;
paused?: boolean;
user: IUser;
}
export default IdentityData;

View File

@ -0,0 +1,4 @@
export default interface SetPlayheadEvent {
playhead: number;
paused: boolean;
}

View File

@ -0,0 +1,7 @@
enum SocketEvents {
Identify = "Identify",
SetPlayhead = "SetPlayhead",
GetPlayhead = "GetPlayhead",
}
export default SocketEvents;

View File

@ -1,5 +1,6 @@
import NextDocument, { Html, Head, Main, NextScript } from 'next/document' import NextDocument, { Html, Head, Main, NextScript } from "next/document";
import { ColorModeScript } from '@chakra-ui/react' import { ColorModeScript } from "@chakra-ui/react";
import theme from "../theme";
export default class Document extends NextDocument { export default class Document extends NextDocument {
render() { render() {
@ -8,11 +9,11 @@ export default class Document extends NextDocument {
<Head /> <Head />
<body> <body>
{/* Make Color mode to persists when you refresh the page. */} {/* Make Color mode to persists when you refresh the page. */}
<ColorModeScript /> <ColorModeScript initialColorMode={theme.config.initialColorMode} />
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>
</Html> </Html>
) );
} }
} }

View File

@ -0,0 +1,69 @@
import { Text } from "@chakra-ui/layout";
import { Flex as Box } from "@chakra-ui/react";
import { GetServerSideProps, NextPage } from "next";
import { Session } from "next-auth";
import { getSession } from "next-auth/react";
import React from "react";
import { Container } from "../../components/Container";
import Header from "../../components/Header";
import UserData from "../../components/UserData";
interface AdminPageProps {
isAuthed: boolean;
isAdmin: boolean;
session?: Session;
}
const AdminPage: NextPage<AdminPageProps> = ({
isAuthed,
isAdmin,
session,
}) => {
return (
<>
<Container height="100vh">
<Header>
<UserData user={session.user} />
</Header>
<Container
height="30vh"
mt="5rem"
backgroundColor="gray.700"
width="100vw"
>
<Text>Hello</Text>
</Container>
</Container>
</>
);
};
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context);
if (session) {
const isAdmin = session.user.name.includes("qpixel");
if (!isAdmin) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {
isAuthed: true,
isAdmin: isAdmin,
session: session,
} as AdminPageProps,
};
}
return {
props: {
isAdmin: false,
isAuthed: false,
} as AdminPageProps,
};
};
export default AdminPage;

View File

@ -0,0 +1,54 @@
import { GetServerSideProps, NextPage } from "next";
import React, { useEffect } from "react";
import { Container } from "../../components/Container";
import { Text } from "@chakra-ui/react";
import { useRouter } from "next/router";
import {
getSession,
signOut,
SignOutResponse,
useSession,
} from "next-auth/react";
const SignOutPage: NextPage = () => {
const router = useRouter();
const session = useSession();
let data: SignOutResponse | null;
const signout = async () => {
data = await signOut({ redirect: false, callbackUrl: "/" });
};
if (session) {
useEffect(() => {
signout();
setTimeout(() => {
if (data) {
router.push(data.url);
} else {
router.push("/");
}
}, 600);
}, []);
}
return (
<Container height="100vh">
<Text>Signing out!</Text>
</Container>
);
};
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {},
};
};
export default SignOutPage;

View File

@ -12,4 +12,10 @@ export default NextAuth({
jwt: { jwt: {
secret: process.env.SECRET, secret: process.env.SECRET,
}, },
callbacks: {
async session({ session, token }) {
session.user.id = token.sub;
return session;
},
},
}); });

View File

@ -1,14 +1,21 @@
import { Button, Text } from "@chakra-ui/react"; import { Button, Link as ChakraLink, Text } from "@chakra-ui/react";
import { GetServerSideProps, NextPage } from "next"; import { GetServerSideProps, NextPage } from "next";
import { getSession, signIn } from "next-auth/react"; import { getSession, signIn } from "next-auth/react";
import Head from "next/head";
import Link from "next/link";
import React from "react"; import React from "react";
import { Container } from "../components/Container"; import { Container } from "../components/Container";
import { Footer } from "../components/Footer"; import { Footer } from "../components/Footer";
import { Hero } from "../components/Hero"; import { Hero } from "../components/Hero";
import { Main } from "../components/Main"; import { Main } from "../components/Main";
import { isDev } from "../util";
const Index: NextPage = () => { const Index: NextPage = () => {
return ( return (
<>
<Head>
<title>Watch Together</title>
</Head>
<Container height="100vh"> <Container height="100vh">
<Hero /> <Hero />
<Main> <Main>
@ -16,20 +23,27 @@ const Index: NextPage = () => {
maxWidth="200" maxWidth="200"
alignSelf="center" alignSelf="center"
onClick={() => signIn("discord")} onClick={() => signIn("discord")}
disabled={!isDev()}
> >
Login With Discord Login With Discord
</Button> </Button>
</Main> </Main>
<Footer> <Footer>
<ChakraLink>
<Link href="https://velvox.dev">
<Text>&copy;2022 Velvox</Text> <Text>&copy;2022 Velvox</Text>
</Link>
</ChakraLink>
</Footer> </Footer>
</Container> </Container>
</>
); );
}; };
export const getServerSideProps: GetServerSideProps = async (context) => { export const getServerSideProps: GetServerSideProps = async (context) => {
const session = getSession(context); const session = await getSession(context);
if (session) { const isDev = true;
if (session && !isDev) {
return { return {
redirect: { redirect: {
destination: "/player", destination: "/player",

View File

@ -1,16 +1,118 @@
import { NextPage } from "next"; import { GetServerSideProps, NextPage } from "next";
import dynamic from "next/dynamic"; import { User } from "next-auth";
import React from "react"; import { getSession } from "next-auth/react";
import Head from "next/head";
import React, { useEffect, useRef, useState } from "react";
import ReactPlayer from "react-player";
import { Container } from "../components/Container"; import { Container } from "../components/Container";
import Player from "../components/Player";
import useWS from "../hooks/useWS";
import IdentityData from "../interfaces/Identity";
import { MessageTypes } from "../interfaces/IMessage";
import SetPlayheadEvent from "../interfaces/Playhead";
import SocketEvents from "../interfaces/SocketEvents";
import { isBrowser } from "../util";
import Message from "../util/Message";
import MessageUtil from "../util/MessageUtil";
const Player = dynamic(() => import("../components/Player")); interface PlayerPageProps {
user: User;
}
// types for the function
const PlayerPage: NextPage = () => { const PlayerPage: NextPage<PlayerPageProps> = ({ user }) => {
const socket = useWS({ user });
const playerRef = useRef<ReactPlayer>();
const [id, setID] = useState<string>("");
const [identity, setIdentity] = useState<IdentityData>();
const [paused, setPaused] = useState<boolean>(true);
useEffect(() => {
if (isBrowser() && typeof socket !== "undefined") {
socket?.emitter.once(SocketEvents.Identify, (e: IdentityData) => {
setID(e.playlist);
setIdentity(e);
playerRef?.current.seekTo(e.playhead);
setPaused(e.paused);
});
socket?.emitter.on(SocketEvents.SetPlayhead, (e: SetPlayheadEvent) => {
console.log(e.paused);
setPaused(e.paused);
playerRef.current.seekTo(e.playhead);
});
}
}, [socket]);
const onPlay = () => {
if (!identity.admin) return;
setPaused(false);
socket?.send(
MessageUtil.encode(
new Message(MessageTypes.SetPlayhead, {
playhead: playerRef.current.getCurrentTime(),
paused: false,
})
)
);
};
const onSeek = (playedSeconds: number) => {
if (!identity.admin) return;
socket.send(
MessageUtil.encode(
new Message(MessageTypes.SetPlayhead, {
playhead: playedSeconds,
paused: paused,
})
)
);
};
const onPause = () => {
console.log("running now");
if (!identity.admin) return;
setPaused(true);
socket?.send(
MessageUtil.encode(
new Message(MessageTypes.SetPlayhead, {
playhead: playerRef.current.getCurrentTime(),
paused: true,
})
)
);
};
return ( return (
<Container height="100vh"> <>
<Player /> <Head>
<title>Watch Together</title>
</Head>
<Container height="100vh" background={"#000"}>
<Player
url={id}
onPlay={onPlay}
onPause={onPause}
onSeek={onSeek}
controls={identity?.hasController}
playing={!paused}
ref={playerRef}
/>
</Container> </Container>
</>
); );
}; };
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {
user: session.user,
},
};
};
export default PlayerPage; export default PlayerPage;

View File

@ -0,0 +1,12 @@
import IMessage, { MessageTypes } from "../interfaces/IMessage";
export default class Message implements IMessage {
constructor(public type: MessageTypes, public data?: unknown) {}
toJSON(): Record<string, unknown> {
return {
type: this.type,
data: this.data,
};
}
}

View File

@ -0,0 +1,16 @@
// inspired by aether/aero
import Message from "./Message";
export default class MessageUtil {
static encode(message: Message): string {
return JSON.stringify(message);
}
static decode(message: string): Message | null {
const parsed = JSON.parse(message);
if (typeof parsed.type !== "number") {
return null;
}
return new Message(parsed.type, parsed.data);
}
}

View File

@ -1,3 +0,0 @@
const useAPI = () => {};
export default useAPI;

View File

@ -0,0 +1,6 @@
export function isDev() {
return process.env.NODE_ENV === "development";
}
export function isBrowser() {
return typeof window !== "undefined";
}

View File

@ -0,0 +1,77 @@
import { User } from "next-auth";
import EventEmitter from "events";
import IdentityData from "../interfaces/Identity";
import { MessageTypes } from "../interfaces/IMessage";
import Message from "../util/Message";
import MessageUtil from "../util/MessageUtil";
// browser socket
// todo: write a shim for this
let Websocket: typeof WebSocket;
if (typeof window !== "undefined") {
Websocket = window.WebSocket;
} else {
Websocket = require("ws");
}
export default class PlayerSocket extends Websocket {
private clientID: string;
public emitter: EventEmitter;
constructor(private user: User) {
super(process.env.NEXT_PUBLIC_WS_URI);
this.emitter = new EventEmitter();
this.clientID = process.env.NEXT_PUBLIC_CLIENT_ID;
this.onopen = this.onOpen;
this.onmessage = this.onMessage;
this.onclose = this.onClose;
}
close(code?: number, reason?: string): void {
this.emitter.removeAllListeners();
super.close(code, reason);
}
onMessage(evt: MessageEvent<any>) {
let message = MessageUtil.decode(evt.data);
if (message.type === MessageTypes["Ping"]) {
return;
}
this.emitter.emit(MessageTypes[message.type], message.data);
}
onOpen() {
this.send(
MessageUtil.encode(
new Message(MessageTypes.Identify, {
clientID: this.clientID,
user: {
id: this.user.id,
name: this.user.name,
},
} as IdentityData)
)
);
this.emitter.emit("open");
this.pingEvent();
}
pingEvent() {
let interval = setInterval(() => {
if (!this.open) {
clearInterval(interval);
return;
}
console.log("[WS] running ping event");
this.send(
MessageUtil.encode(
new Message(MessageTypes.Ping, {
clientID: this.clientID,
})
)
);
}, 20000);
}
onClose(event: CloseEvent) {
console.log("[WS] socket connection closed");
this.emitter.emit("closed");
}
get open() {
return this.readyState === this.OPEN;
}
}

View File

@ -12,8 +12,9 @@
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve" "jsx": "preserve",
"typeRoots": ["types/"]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["types/*.d.ts", "**/*.ts", "**/*.tsx", "next-seo.config.js"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

View File

@ -2,7 +2,8 @@ declare namespace NodeJS {
export interface ProcessEnv { export interface ProcessEnv {
DISCORD_ID: string; DISCORD_ID: string;
DISCORD_SECRET: string; DISCORD_SECRET: string;
API_PORT: string;
SECRET: string; SECRET: string;
NEXT_PUBLIC_CLIENT_ID: string;
NEXT_PUBLIC_WS_URI: string;
} }
} }

10
frontend/types/next-auth.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import NextAuth, { Session as NextSession, User as NextUser } from "next-auth";
declare module "next-auth" {
interface User extends NextUser {
id: string;
}
interface Session extends NextSession {
user: User;
}
}

View File

@ -818,6 +818,13 @@
resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c" resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c"
integrity sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA== integrity sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==
"@next/bundle-analyzer@^12.0.8":
version "12.0.8"
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-12.0.8.tgz#a4fef1b14f8a4a87c09d97a3d909deddc88d12f8"
integrity sha512-tRwFyAkJA0h+rwt4exq31T59qo4qwp7vPoR3yC8gIpK/E5NAwafyk40aNpk4OWhiQ2IvJMFutukMzY3xl79NXA==
dependencies:
webpack-bundle-analyzer "4.3.0"
"@next/env@12.0.7": "@next/env@12.0.7":
version "12.0.7" version "12.0.7"
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.0.7.tgz#316f7bd1b6b69f554d2676cfc91a16bc7e32ee79" resolved "https://registry.yarnpkg.com/@next/env/-/env-12.0.7.tgz#316f7bd1b6b69f554d2676cfc91a16bc7e32ee79"
@ -910,6 +917,11 @@
resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.0.1.tgz#ed0da773bd5f794d0603f5a5b5cee6d2354e5660" resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.0.1.tgz#ed0da773bd5f794d0603f5a5b5cee6d2354e5660"
integrity sha512-mMyQ9vjpuFqePkfe5bZVIf/H3Dmk6wA8Kjxff9RcO4kqzJo+Ek9pGKwZHpeMr7Eku0QhLXMCd7fNCSnEnRMubg== integrity sha512-mMyQ9vjpuFqePkfe5bZVIf/H3Dmk6wA8Kjxff9RcO4kqzJo+Ek9pGKwZHpeMr7Eku0QhLXMCd7fNCSnEnRMubg==
"@polka/url@^1.0.0-next.20":
version "1.0.0-next.21"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
"@popperjs/core@^2.9.3": "@popperjs/core@^2.9.3":
version "2.11.0" version "2.11.0"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7"
@ -942,6 +954,11 @@
prop-types "^15.7.2" prop-types "^15.7.2"
tslib "^2.1.0" tslib "^2.1.0"
"@types/events@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
"@types/lodash.mergewith@4.6.6": "@types/lodash.mergewith@4.6.6":
version "4.6.6" version "4.6.6"
resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10" resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10"
@ -1000,6 +1017,11 @@
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf" resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf"
integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw== integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==
"@types/uuid@^8.3.4":
version "8.3.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
"@types/warning@^3.0.0": "@types/warning@^3.0.0":
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
@ -1012,11 +1034,28 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/ws@^8.2.2":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21"
integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==
dependencies:
"@types/node" "*"
acorn-walk@^8.0.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@8.5.0: acorn@8.5.0:
version "8.5.0" version "8.5.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==
acorn@^8.0.4:
version "8.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
anser@1.4.9: anser@1.4.9:
version "1.4.9" version "1.4.9"
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760" resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760"
@ -1212,13 +1251,6 @@ buffer@5.6.0:
base64-js "^1.0.2" base64-js "^1.0.2"
ieee754 "^1.1.4" ieee754 "^1.1.4"
bufferutil@^4.0.1:
version "4.0.6"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433"
integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==
dependencies:
node-gyp-build "^4.3.0"
builtin-status-codes@^3.0.0: builtin-status-codes@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@ -1264,6 +1296,14 @@ chalk@4.0.0:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
chalk@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chokidar@3.5.1: chokidar@3.5.1:
version "3.5.1" version "3.5.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
@ -1321,6 +1361,11 @@ colorette@^1.2.2:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
commander@^6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
commondir@^1.0.1: commondir@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -1331,6 +1376,11 @@ compute-scroll-into-view@1.0.14:
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz#80e3ebb25d6aa89f42e533956cb4b16a04cfe759" resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz#80e3ebb25d6aa89f42e533956cb4b16a04cfe759"
integrity sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ== integrity sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ==
consola@^2.15.3:
version "2.15.3"
resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550"
integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==
constants-browserify@1.0.0: constants-browserify@1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@ -1404,6 +1454,22 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
cross-env@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
dependencies:
cross-spawn "^7.0.1"
cross-spawn@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
which "^2.0.1"
crypto-browserify@3.12.0: crypto-browserify@3.12.0:
version "3.12.0" version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@ -1452,20 +1518,12 @@ csstype@^3.0.2, csstype@^3.0.9:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
d@1, d@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
dependencies:
es5-ext "^0.10.50"
type "^1.0.1"
data-uri-to-buffer@3.0.1: data-uri-to-buffer@3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636"
integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==
debug@2, debug@^2.2.0: debug@2:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@ -1516,6 +1574,11 @@ domain-browser@4.19.0:
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.19.0.tgz#1093e17c0a17dbd521182fe90d49ac1370054af1" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.19.0.tgz#1093e17c0a17dbd521182fe90d49ac1370054af1"
integrity sha512-fRA+BaAWOR/yr/t7T9E9GJztHPeFjj8U35ajyAjCDtAAnTn1Rc1f6W6VGPJrO1tkQv9zWu+JRof7z6oQtiYVFQ== integrity sha512-fRA+BaAWOR/yr/t7T9E9GJztHPeFjj8U35ajyAjCDtAAnTn1Rc1f6W6VGPJrO1tkQv9zWu+JRof7z6oQtiYVFQ==
duplexer@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
electron-to-chromium@^1.3.723: electron-to-chromium@^1.3.723:
version "1.4.26" version "1.4.26"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.26.tgz#d71b9da220543cf10614a576c3d5ebbe43d96efb" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.26.tgz#d71b9da220543cf10614a576c3d5ebbe43d96efb"
@ -1588,37 +1651,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1" is-date-object "^1.0.1"
is-symbol "^1.0.2" is-symbol "^1.0.2"
es5-ext@^0.10.35, es5-ext@^0.10.50:
version "0.10.53"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
dependencies:
es6-iterator "~2.0.3"
es6-symbol "~3.1.3"
next-tick "~1.0.0"
es6-iterator@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
dependencies:
d "1"
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-object-assign@^1.1.0: es6-object-assign@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=
es6-symbol@^3.1.1, es6-symbol@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
dependencies:
d "^1.0.1"
ext "^1.1.2"
escalade@^3.1.1: escalade@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@ -1639,7 +1676,7 @@ etag@1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
events@3.3.0: events@3.3.0, events@^3.3.0:
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
@ -1652,13 +1689,6 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4" md5.js "^1.3.4"
safe-buffer "^5.1.1" safe-buffer "^5.1.1"
ext@^1.1.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52"
integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==
dependencies:
type "^2.5.0"
fill-range@^7.0.1: fill-range@^7.0.1:
version "7.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@ -1776,6 +1806,13 @@ graceful-fs@^4.1.2:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
gzip-size@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==
dependencies:
duplexer "^0.1.2"
has-bigints@^1.0.1: has-bigints@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
@ -2060,11 +2097,6 @@ is-typed-array@^1.1.3, is-typed-array@^1.1.7:
foreach "^2.0.5" foreach "^2.0.5"
has-tostringtag "^1.0.0" has-tostringtag "^1.0.0"
is-typedarray@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
is-weakref@^1.0.1: is-weakref@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
@ -2072,6 +2104,11 @@ is-weakref@^1.0.1:
dependencies: dependencies:
call-bind "^1.0.2" call-bind "^1.0.2"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
jest-worker@27.0.0-next.5: jest-worker@27.0.0-next.5:
version "27.0.0-next.5" version "27.0.0-next.5"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.0-next.5.tgz#5985ee29b12a4e191f4aae4bb73b97971d86ec28" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.0-next.5.tgz#5985ee29b12a4e191f4aae4bb73b97971d86ec28"
@ -2139,6 +2176,11 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
lodash@^4.17.20:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@ -2202,6 +2244,11 @@ minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mrmime@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b"
integrity sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==
ms@2.0.0: ms@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -2227,11 +2274,6 @@ next-auth@^4.1.2:
preact-render-to-string "^5.1.19" preact-render-to-string "^5.1.19"
uuid "^8.3.2" uuid "^8.3.2"
next-tick@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
next@latest: next@latest:
version "12.0.7" version "12.0.7"
resolved "https://registry.yarnpkg.com/next/-/next-12.0.7.tgz#33ebf229b81b06e583ab5ae7613cffe1ca2103fc" resolved "https://registry.yarnpkg.com/next/-/next-12.0.7.tgz#33ebf229b81b06e583ab5ae7613cffe1ca2103fc"
@ -2304,11 +2346,6 @@ node-fetch@2.6.1:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-gyp-build@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==
node-html-parser@1.4.9: node-html-parser@1.4.9:
version "1.4.9" version "1.4.9"
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.4.9.tgz#3c8f6cac46479fae5800725edb532e9ae8fd816c" resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.4.9.tgz#3c8f6cac46479fae5800725edb532e9ae8fd816c"
@ -2374,6 +2411,11 @@ oidc-token-hash@^5.0.1:
resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6" resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6"
integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ== integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==
opener@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
openid-client@^5.1.0: openid-client@^5.1.0:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.1.2.tgz#a80cc6a7d8d7159ad97c51781338f5a954396bba" resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.1.2.tgz#a80cc6a7d8d7159ad97c51781338f5a954396bba"
@ -2458,6 +2500,11 @@ path-exists@^4.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-parse@^1.0.6: path-parse@^1.0.6:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
@ -2788,6 +2835,18 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1" inherits "^2.0.1"
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shell-quote@1.7.3: shell-quote@1.7.3:
version "1.7.3" version "1.7.3"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123"
@ -2802,6 +2861,15 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2" get-intrinsic "^1.0.2"
object-inspect "^1.9.0" object-inspect "^1.9.0"
sirv@^1.0.7:
version "1.0.19"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49"
integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==
dependencies:
"@polka/url" "^1.0.0-next.20"
mrmime "^1.0.0"
totalist "^1.0.0"
source-map@0.7.3: source-map@0.7.3:
version "0.7.3" version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
@ -2993,6 +3061,11 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
totalist@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
tr46@^1.0.1: tr46@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@ -3020,23 +3093,6 @@ type-fest@^0.7.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
type@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
type@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d"
integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
dependencies:
is-typedarray "^1.0.0"
typescript@4.3.2: typescript@4.3.2:
version "4.3.2" version "4.3.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
@ -3077,13 +3133,6 @@ use-subscription@1.5.1:
dependencies: dependencies:
object-assign "^4.1.1" object-assign "^4.1.1"
utf-8-validate@^5.0.2:
version "5.0.8"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.8.tgz#4a735a61661dbb1c59a0868c397d2fe263f14e58"
integrity sha512-k4dW/Qja1BYDl2qD4tOMB9PFVha/UJtxTc1cXYOe3WwA/2m0Yn4qB7wLMpJyLJ/7DR0XnTut3HsCSzDT4ZvKgA==
dependencies:
node-gyp-build "^4.3.0"
util-deprecate@^1.0.1: util-deprecate@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@ -3131,17 +3180,20 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
websocket@^1.0.34: webpack-bundle-analyzer@4.3.0:
version "1.0.34" version "4.3.0"
resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz#2f3c0ca9041d5ee47fa418693cf56b4a518b578b"
integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== integrity sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==
dependencies: dependencies:
bufferutil "^4.0.1" acorn "^8.0.4"
debug "^2.2.0" acorn-walk "^8.0.0"
es5-ext "^0.10.50" chalk "^4.1.0"
typedarray-to-buffer "^3.1.5" commander "^6.2.0"
utf-8-validate "^5.0.2" gzip-size "^6.0.0"
yaeti "^0.0.6" lodash "^4.17.20"
opener "^1.5.2"
sirv "^1.0.7"
ws "^7.3.1"
whatwg-url@^7.0.0: whatwg-url@^7.0.0:
version "7.1.0" version "7.1.0"
@ -3175,16 +3227,28 @@ which-typed-array@^1.1.2:
has-tostringtag "^1.0.0" has-tostringtag "^1.0.0"
is-typed-array "^1.1.7" is-typed-array "^1.1.7"
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
ws@^7.3.1:
version "7.5.6"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
ws@^8.4.2:
version "8.4.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==
xtend@^4.0.2: xtend@^4.0.2:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yaeti@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=
yallist@^4.0.0: yallist@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"