-custom: ["https://joinpeertube.org/roadmap", "https://soutenir.framasoft.org/en/"]
+custom: ["https://soutenir.framasoft.org/en/"]
branches:
- develop
- ci
+ - next
pull_request:
types: [synchronize, opened]
- develop
- master
- ci
+ - next
pull_request:
types: [synchronize, opened]
schedule:
<div class="row">
<h1 class="sr-only" i18n>Follows</h1>
<div class="col-xl-6 col-md-12">
- <h2 i18n class="subtitle">Followers instances ({{ followersPagination.totalItems }})</h2>
+ <h2 i18n class="subtitle">Follower instances ({{ followersPagination.totalItems }})</h2>
<div i18n class="no-results" *ngIf="followersPagination.totalItems === 0">This instance does not have instances followers.</div>
fragment="business-model"
#anchorLink
(click)="onClickCopyLink(anchorLink)">
- <h3 i18n class="section-title">How we will pay for this instance</h3>
+ <h3 i18n class="section-title">How we will pay for keeping our instance running</h3>
</a>
<div [innerHTML]="html.businessModel"></div>
<button [cdkCopyToClipboard]="account.nameWithHostForced" (click)="activateCopiedMessage()"
class="btn btn-outline-secondary btn-sm copy-button"
>
- <span class="glyphicon glyphicon-copy"></span>
+ <span class="glyphicon glyphicon-duplicate"></span>
</button>
</div>
<span *ngIf="accountUser?.blocked" [ngbTooltip]="accountUser.blockedReason" class="badge badge-danger" i18n>Banned</span>
</div>
<div class="search-bar">
- <input type="text" (input)="onSearchChange($event)" i18n-placeholder placeholder="Search..."/>
+ <input type="text" (input)="onSearchChange($event)" i18n-placeholder placeholder="Search..." autofocus />
</div>
<div class="alert alert-info" i18n *ngIf="pluginInstalled">
<my-global-icon iconName="search"></my-global-icon>
<ng-container i18n>
- {{ pagination.totalItems }} {pagination.totalItems, plural, =1 {result} other {results}} for "{{ search }}"
- </ng-container>
+ {{ pagination.totalItems }} {pagination.totalItems, plural, =1 {result} other {results}} for {{ search }}"
+ </ng-container>
</ng-container>
</div>
<label i18n for="username">User</label>
<input
type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1"
- formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput
+ formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" autofocus
>
</div>
import { ActivatedRoute } from '@angular/router'
import { AuthService, Notifier, RedirectService, UserService } from '@app/core'
import { HooksService } from '@app/core/plugins/hooks.service'
-import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
+import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models'
})
export class LoginComponent extends FormReactive implements OnInit, AfterViewInit {
- @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef
@ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef
accordion: NgbAccordion
}
ngAfterViewInit () {
- if (this.usernameInput) {
- this.usernameInput.nativeElement.focus()
- }
-
this.hooks.runAction('action:login.init', 'login')
}
newInstanceFollower: $localize`Your instance has a new follower`,
autoInstanceFollowing: $localize`Your instance automatically followed another instance`,
abuseNewMessage: $localize`An abuse report received a new message`,
- abuseStateChange: $localize`One of your abuse reports has been accepted or rejected by moderators`
+ abuseStateChange: $localize`One of your abuse reports has been accepted or rejected by moderators`,
+ newPeerTubeVersion: $localize`A new PeerTube version is available`,
+ newPluginVersion: $localize`One of your plugin/theme has a new available version`
}
this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[]
videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST,
newUserRegistration: UserRight.MANAGE_USERS,
newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW,
- autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION
+ autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION,
+ newPeerTubeVersion: UserRight.MANAGE_DEBUG,
+ newPluginVersion: UserRight.MANAGE_DEBUG
}
}
<div class="results-header">
<div class="first-line">
<div class="results-counter" *ngIf="pagination.totalItems">
- <span i18n>{{ pagination.totalItems | myNumberFormatter }} {pagination.totalItems, plural, =1 {result} other {results}} </span>
+ <span class="mr-1" i18n>{{ pagination.totalItems | myNumberFormatter }} {pagination.totalItems, plural, =1 {result} other {results}}</span>
- <span i18n *ngIf="advancedSearch.searchTarget === 'local'">on this instance</span>
- <span i18n *ngIf="advancedSearch.searchTarget === 'search-index'">on the vidiverse</span>
+ <span class="mr-1" i18n *ngIf="advancedSearch.searchTarget === 'local'">on this instance</span>
+ <span class="mr-1" i18n *ngIf="advancedSearch.searchTarget === 'search-index'">on the vidiverse</span>
- <span *ngIf="currentSearch" i18n>
- for <span class="search-value">{{ currentSearch }}</span>
- </span>
+ <span *ngIf="currentSearch" i18n>for <span class="search-value">{{ currentSearch }}</span></span>
</div>
<div
<div class="margin-content">
<div i18n class="title-page title-page-single">
- Created {{ pagination.totalItems }} playlists
+ Created {pagination.totalItems, plural, =1 {1 playlist} other {{{ pagination.totalItems }} playlists}}
</div>
<div i18n class="no-results" *ngIf="pagination.totalItems === 0">This channel does not have playlists.</div>
<button [cdkCopyToClipboard]="videoChannel.nameWithHostForced" (click)="activateCopiedMessage()"
class="btn btn-outline-secondary btn-sm copy-button"
>
- <span class="glyphicon glyphicon-copy"></span>
+ <span class="glyphicon glyphicon-duplicate"></span>
</button>
</div>
</div>
import { forkJoin } from 'rxjs'
-import { Component, EventEmitter, OnInit, Output } from '@angular/core'
+import { AfterViewChecked, AfterViewInit, Component, EventEmitter, OnInit, Output } from '@angular/core'
import { Router } from '@angular/router'
-import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core'
+import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService } from '@app/core'
import { scrollToTop } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
'./video-send.scss'
]
})
-export class VideoGoLiveComponent extends VideoSend implements OnInit, CanComponentDeactivate {
+export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterViewInit, CanComponentDeactivate {
@Output() firstStepDone = new EventEmitter<string>()
@Output() firstStepError = new EventEmitter<void>()
protected videoService: VideoService,
protected videoCaptionService: VideoCaptionService,
private liveVideoService: LiveVideoService,
- private router: Router
+ private router: Router,
+ private hooks: HooksService
) {
super()
}
super.ngOnInit()
}
+ ngAfterViewInit () {
+ this.hooks.runAction('action:go-live.init', 'video-edit')
+ }
+
canDeactivate () {
return { canDeactivate: true }
}
-import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
+import { AfterViewInit, Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
import { Router } from '@angular/router'
-import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core'
+import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService } from '@app/core'
import { scrollToTop } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
'./video-send.scss'
]
})
-export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate {
+export class VideoImportTorrentComponent extends VideoSend implements OnInit, AfterViewInit, CanComponentDeactivate {
@Output() firstStepDone = new EventEmitter<string>()
@Output() firstStepError = new EventEmitter<void>()
@ViewChild('torrentfileInput') torrentfileInput: ElementRef<HTMLInputElement>
protected videoService: VideoService,
protected videoCaptionService: VideoCaptionService,
private router: Router,
- private videoImportService: VideoImportService
+ private videoImportService: VideoImportService,
+ private hooks: HooksService
) {
super()
}
super.ngOnInit()
}
+ ngAfterViewInit () {
+ this.hooks.runAction('action:video-torrent-import.init', 'video-edit')
+ }
+
canDeactivate () {
return { canDeactivate: true }
}
import { map, switchMap } from 'rxjs/operators'
-import { Component, EventEmitter, OnInit, Output } from '@angular/core'
+import { AfterViewInit, Component, EventEmitter, OnInit, Output } from '@angular/core'
import { Router } from '@angular/router'
-import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core'
+import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService } from '@app/core'
import { getAbsoluteAPIUrl, scrollToTop } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
'./video-send.scss'
]
})
-export class VideoImportUrlComponent extends VideoSend implements OnInit, CanComponentDeactivate {
+export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterViewInit, CanComponentDeactivate {
@Output() firstStepDone = new EventEmitter<string>()
@Output() firstStepError = new EventEmitter<void>()
protected videoService: VideoService,
protected videoCaptionService: VideoCaptionService,
private router: Router,
- private videoImportService: VideoImportService
- ) {
+ private videoImportService: VideoImportService,
+ private hooks: HooksService
+ ) {
super()
}
super.ngOnInit()
}
+ ngAfterViewInit () {
+ this.hooks.runAction('action:video-url-import.init', 'video-edit')
+ }
+
canDeactivate () {
return { canDeactivate: true }
}
import { Subscription } from 'rxjs'
import { HttpErrorResponse, HttpEventType, HttpResponse } from '@angular/common/http'
-import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
+import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
import { Router } from '@angular/router'
-import { AuthService, CanComponentDeactivate, Notifier, ServerService, UserService } from '@app/core'
+import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService, UserService } from '@app/core'
import { scrollToTop, uploadErrorHandler } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms'
import { BytesPipe, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
+import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
import { VideoPrivacy } from '@shared/models'
import { VideoSend } from './video-send'
-import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
@Component({
selector: 'my-video-upload',
'./video-send.scss'
]
})
-export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate {
+export class VideoUploadComponent extends VideoSend implements OnInit, AfterViewInit, OnDestroy, CanComponentDeactivate {
@Output() firstStepDone = new EventEmitter<string>()
@Output() firstStepError = new EventEmitter<void>()
@ViewChild('videofileInput') videofileInput: ElementRef<HTMLInputElement>
protected videoService: VideoService,
protected videoCaptionService: VideoCaptionService,
private userService: UserService,
- private router: Router
+ private router: Router,
+ private hooks: HooksService
) {
super()
}
})
}
+ ngAfterViewInit () {
+ this.hooks.runAction('action:video-upload.init', 'video-edit')
+ }
+
ngOnDestroy () {
if (this.videoUploadObservable) this.videoUploadObservable.unsubscribe()
}
<div>
<div class="title-block">
<h2 class="title-page title-page-single">
- <ng-container *ngIf="totalNotDeletedComments > 0; then hasComments; else noComments"></ng-container>
- <ng-template #hasComments>
- <ng-container i18n *ngIf="totalNotDeletedComments === 1; else manyComments">1 Comment</ng-container>
- <ng-template i18n #manyComments>{{ totalNotDeletedComments }} Comments</ng-template>
- </ng-template>
- <ng-template i18n #noComments>Comments</ng-template>
+ {totalNotDeletedComments, plural, =0 {Comments} =1 {1 Comment} other {{{totalNotDeletedComments}} Comments}}
</h2>
<my-feed [syndicationItems]="syndicationItems"></my-feed>
<span class="glyphicon glyphicon-menu-down"></span>
<ng-container *ngIf="comment.totalRepliesFromVideoAuthor > 0; then hasAuthorComments; else noAuthorComments"></ng-container>
+
<ng-template #hasAuthorComments>
<ng-container *ngIf="comment.totalReplies !== comment.totalRepliesFromVideoAuthor; else onlyAuthorComments" i18n>
- View {{ comment.totalReplies }} replies from {{ video?.account?.displayName || 'the author' }} and others
+ View {comment.totalReplies, plural, =1 {1 reply} other {{{ comment.totalReplies }} replies}} from {{ video?.account?.displayName || 'the author' }} and others
</ng-container>
<ng-template i18n #onlyAuthorComments>
- View {{ comment.totalReplies }} replies from {{ video?.account?.displayName || 'the author' }}
+ View {comment.totalReplies, plural, =1 {1 reply} other {{{ comment.totalReplies }} replies}} from {{ video?.account?.displayName || 'the author' }}
</ng-template>
</ng-template>
- <ng-template i18n #noAuthorComments>View {{ comment.totalReplies }} replies</ng-template>
+
+ <ng-template i18n #noAuthorComments>View {comment.totalReplies, plural, =1 {1 reply} other {{{ comment.totalReplies }} replies}}</ng-template>
<my-small-loader class="comment-thread-loading ml-1" [loading]="threadLoading[comment.id]"></my-small-loader>
</div>
import { HooksService } from '@app/core/plugins/hooks.service'
import { Syndication, VideoDetails } from '@app/shared/shared-main'
import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment'
-import { ThisReceiver } from '@angular/compiler'
@Component({
selector: 'my-video-comments',
this.notificationSocket = this.io(environment.apiUrl + '/user-notifications', {
query: { accessToken: this.auth.getAccessToken() }
})
-
- this.notificationSocket.on('new-notification', (n: UserNotificationServer) => {
- this.ngZone.run(() => this.dispatchNotificationEvent('new', n))
- })
})
+ this.notificationSocket.on('new-notification', (n: UserNotificationServer) => {
+ this.ngZone.run(() => this.dispatchNotificationEvent('new', n))
+ })
}
private async initLiveVideosSocket () {
import { Injectable } from '@angular/core'
import { PluginService } from '@app/core/plugins/plugin.service'
import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models'
+import { AuthService, AuthStatus } from '../auth'
type RawFunction<U, T> = (params: U) => T
type ObservableFunction<U, T> = RawFunction<U, Observable<T>>
@Injectable()
export class HooksService {
- constructor (private pluginService: PluginService) { }
+ constructor (
+ private authService: AuthService,
+ private pluginService: PluginService
+ ) {
+ // Run auth hooks
+ this.authService.userInformationLoaded
+ .subscribe(() => this.runAction('action:auth-user.information-loaded', 'common', { user: this.authService.getUser() }))
+
+ this.authService.loginChangedSource.subscribe(obj => {
+ if (obj === AuthStatus.LoggedIn) {
+ this.runAction('action:auth-user.logged-in', 'common')
+ } else if (obj === AuthStatus.LoggedOut) {
+ this.runAction('action:auth-user.logged-out', 'common')
+ }
+ })
+ }
wrapObsFun
<P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName>
.toPromise()
},
+ getServerConfig: () => {
+ return this.server.getConfig()
+ .pipe(catchError(res => this.restExtractor.handleError(res)))
+ .toPromise()
+ },
+
isLoggedIn: () => {
return this.authService.isLoggedIn()
},
<!-- search instructions, when search input is empty -->
<div *ngIf="areInstructionsDisplayed()" id="typeahead-instructions" class="overflow-hidden">
- <div class="d-flex justify-content-between">
+ <span class="text-muted" i18n>Your query will be matched against video names or descriptions, channel names.</span>
+ <div class="d-flex justify-content-between mt-3">
<label class="small-title" i18n>ADVANCED SEARCH</label>
<div class="advanced-search-status c-help">
<span [ngClass]="canSearchAnyURI ? 'text-success' : 'text-muted'" i18n-title title="Determines whether you can resolve any distant content, or if this instance only allows doing so for instances it follows.">
<em>UUID</em> <span class="text-muted" i18n>will list the matching video</span>
</li>
</ul>
- <span class="text-muted" i18n>Any other input will return matching video or channel names.</span>
</div>
</div>
$menu-link-icon-size: 22px;
$menu-link-icon-margin-right: 18px;
+$footer-links-base-opacity: .8;
@mixin menu-link {
display: flex;
align-items: center;
justify-content: left;
- .logged-in-more {
- $main-radius: 25px;
+ my-notification {
+ margin-left: auto;
+ margin-right: 15px;
+ }
+ }
+}
- flex: 1;
- margin-left: 13px;
- border-radius: $main-radius;
- transition: all .1s ease-in-out;
- cursor: pointer;
+.logged-in-more {
+ $main-radius: 25px;
- *, & {
- line-height: 1;
- }
+ flex: 1;
+ margin-left: 13px;
+ border-radius: $main-radius;
+ transition: all .1s ease-in-out;
+ cursor: pointer;
- &.show {
- background-color: rgba(255, 255, 255, 0.20);
- box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325);
- }
+ *, & {
+ line-height: 1;
+ }
- @mixin display-hints($is-mobile: false) {
- background-color: rgba(255, 255, 255, 0.15);
-
- @if $is-mobile {
- .dropdown-toggle-indicator {
- display: inherit !important;
- }
- .dropdown-toggle:first-child {
- padding-right: 30px !important;
- }
- }
- }
+ &.show {
+ background-color: rgba(255, 255, 255, 0.20);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325);
+ }
+
+ @mixin display-hints($is-mobile: false) {
+ background-color: rgba(255, 255, 255, 0.15);
- &:hover {
- @include display-hints;
+ @if $is-mobile {
+ .dropdown-toggle-indicator {
+ display: inherit !important;
+ }
+ .dropdown-toggle:first-child {
+ padding-right: 30px !important;
}
+ }
+ }
- /* smartphones and touchscreens */
- @media (hover: none) and (pointer: coarse) {
- @include display-hints($is-mobile: true);
+ &:hover {
+ @include display-hints;
+ }
- /* fill space when on mobile */
- max-width: calc(100% - 80px);
- .dropdown-toggle {
- max-width: 100%;
- }
- .logged-in-info {
- max-width: calc(100% - 45px) !important;
- }
+ /* smartphones and touchscreens */
+ @media (hover: none) and (pointer: coarse) {
+ @include display-hints($is-mobile: true);
- }
+ /* fill space when on mobile */
+ max-width: calc(100% - 80px);
+ .dropdown-toggle {
+ max-width: 100%;
+ }
+ .logged-in-info {
+ max-width: calc(100% - 45px) !important;
+ }
- .dropdown-toggle-indicator {
- position: relative;
- width: 0;
- display: none;
-
- span {
- position: absolute;
- right: -35px;
- top: -8px;
- color: grey;
- width: $main-radius;
- }
- }
+ }
- .dropdown-toggle {
- &::after {
- border: none;
- }
- }
+ .dropdown-toggle-indicator {
+ position: relative;
+ width: 0;
+ display: none;
- .dropdown-toggle:first-child {
- display: flex;
- align-items: center;
- padding: 5px 7px;
- border-radius: $main-radius;
- }
+ span {
+ position: absolute;
+ right: -35px;
+ top: -8px;
+ color: grey;
+ width: $main-radius;
+ }
+ }
- img {
- @include avatar(34px);
+ .dropdown-toggle {
+ &::after {
+ border: none;
+ }
+ }
- margin-right: 10px;
- }
+ .dropdown-toggle:first-child {
+ display: flex;
+ align-items: center;
+ padding: 5px 7px;
+ border-radius: $main-radius;
+ }
+
+ img {
+ @include avatar(34px);
- .logged-in-info {
- max-width: 105px;
+ margin-right: 10px;
+ }
+}
- flex-grow: 1;
+.logged-in-info {
+ max-width: 105px;
- .logged-in-display-name,
- .logged-in-username {
- @include ellipsis;
- }
+ flex-grow: 1;
- .logged-in-display-name {
- font-size: 16px;
- font-weight: $font-semibold;
- color: pvar(--menuForegroundColor);
+ .logged-in-display-name,
+ .logged-in-username {
+ @include ellipsis;
+ }
- @include disable-default-a-behaviour;
- }
+ .logged-in-display-name {
+ font-size: 16px;
+ font-weight: $font-semibold;
+ color: pvar(--menuForegroundColor);
- .logged-in-username {
- font-size: 13px;
- color: #C6C6C6;
- margin-top: 3px;
- }
- }
- }
+ @include disable-default-a-behaviour;
+ }
- my-notification {
- margin-left: auto;
- margin-right: 15px;
- }
+ .logged-in-username {
+ font-size: 13px;
+ color: #C6C6C6;
+ margin-top: 3px;
}
+}
- .logged-in-menu {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- border-top: 1px solid var(--greyForegroundColor);
- line-height: $line-height-normal;
+.logged-in-menu {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ border-top: 1px solid var(--greyForegroundColor);
+ line-height: $line-height-normal;
- a {
- @include menu-link;
- @include disable-default-a-behaviour;
+ a {
+ @include menu-link;
+ @include disable-default-a-behaviour;
- $icon-size: 13px;
- $additional-margin: ($menu-link-icon-size - $icon-size) / 2;
+ $icon-size: 13px;
+ $additional-margin: ($menu-link-icon-size - $icon-size) / 2;
- font-size: 14px;
- width: 100%;
- min-height: 35px;
+ font-size: 14px;
+ width: 100%;
+ min-height: 35px;
- my-global-icon {
- width: $icon-size;
- height: $icon-size;
+ my-global-icon {
+ width: $icon-size;
+ height: $icon-size;
- // Keep aligned with other icons
- margin-left: $additional-margin;
+ // Keep aligned with other icons
+ margin-left: $additional-margin;
- &[iconName="channel"] {
- margin-top: -2px;
- }
+ &[iconName="channel"] {
+ margin-top: -2px;
}
+ }
- &.active,
- &:hover,
- &:focus-visible {
- my-global-icon {
- @include apply-svg-color(var(--menuForegroundColor));
- }
+ &.active,
+ &:hover,
+ &:focus-visible {
+ my-global-icon {
+ @include apply-svg-color(var(--menuForegroundColor));
}
+ }
- &.active {
- $border-left-width: 4px;
+ &.active {
+ $border-left-width: 4px;
- font-weight: $font-semibold;
- border-left: $border-left-width solid var(--mainColor);
+ font-weight: $font-semibold;
+ border-left: $border-left-width solid var(--mainColor);
- my-global-icon {
- margin-left: $additional-margin - $border-left-width;
- }
+ my-global-icon {
+ margin-left: $additional-margin - $border-left-width;
}
}
}
flex-direction: column;
padding: 0 $menu-lateral-padding;
}
+}
- $footer-links-base-opacity: .8;
-
- .footer-links {
- &, > div {
- display: flex;
- flex-wrap: wrap;
- }
+.footer-links {
+ &, > div {
+ display: flex;
+ flex-wrap: wrap;
+ }
- a, span[role=button] {
- display: inline-block;
- text-decoration: none;
- color: pvar(--menuForegroundColor);
- opacity: $footer-links-base-opacity;
+ a, span[role=button] {
+ display: inline-block;
+ text-decoration: none;
+ color: pvar(--menuForegroundColor);
+ opacity: $footer-links-base-opacity;
+ white-space: nowrap;
+ font-size: 90%;
+ font-weight: 500;
+ line-height: 1.4rem;
+ margin-right: 8px;
+
+ &.inline-global-icon {
+ display: inline-flex;
+ align-items: center;
white-space: nowrap;
- font-size: 90%;
- font-weight: 500;
- line-height: 1.4rem;
- margin-right: 8px;
-
- &.inline-global-icon {
- display: inline-flex;
- align-items: center;
- white-space: nowrap;
- height: 1.4rem;
-
- my-global-icon {
- @include apply-svg-color(pvar(--menuForegroundColor));
-
- display: flex;
- width: auto;
- height: 90%;
- margin-right: .2rem;
- }
+ height: 1.4rem;
+
+ my-global-icon {
+ @include apply-svg-color(pvar(--menuForegroundColor));
+
+ display: flex;
+ width: auto;
+ height: 90%;
+ margin-right: .2rem;
}
}
}
+}
- .footer-copyleft small a {
- @include disable-default-a-behaviour;
+.footer-copyleft small a {
+ @include disable-default-a-behaviour;
- color: pvar(--menuForegroundColor);
- opacity: $footer-links-base-opacity - .2;
- }
+ color: pvar(--menuForegroundColor);
+ opacity: $footer-links-base-opacity - .2;
}
.dropdown {
<li i18n *ngIf="!about.instance.administrator">Who you are</li>
<li i18n *ngIf="!about.instance.maintenanceLifetime">How long you plan to maintain your instance</li>
- <li i18n *ngIf="!about.instance.businessModel">How you plan to pay your instance</li>
+ <li i18n *ngIf="!about.instance.businessModel">How you plan to pay for keeping your instance running</li>
<li i18n *ngIf="!about.instance.moderationInformation">How you will moderate your instance</li>
<li i18n *ngIf="!about.instance.terms">Instance terms</li>
<button
*ngIf="withCopy" [cdkCopyToClipboard]="input.value" (click)="activateCopiedMessage()" type="button"
- class="btn btn-outline-secondary" i18n-title title="Copy"
+ class="btn btn-outline-secondary text-uppercase" i18n-title title="Copy"
>
- <span class="glyphicon glyphicon-copy"></span>
+ <span class="glyphicon glyphicon-duplicate"></span>
+ Copy
</button>
</div>
</div>
-import { Component, forwardRef, Input } from '@angular/core'
+import { Component, forwardRef, HostListener, Input } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { SelectOptionsItem } from '../../../../types/select-options-item.model'
propagateChange = (_: any) => { /* empty */ }
+ // Allow plugins to update our value
+ @HostListener('change', [ '$event.target' ])
+ handleChange (event: any) {
+ this.writeValue(event.value)
+ this.onModelChange()
+ }
+
writeValue (id: number | string) {
this.selectedId = id
}
--- /dev/null
+import { AfterViewInit, Directive, ElementRef } from '@angular/core'
+
+@Directive({
+ selector: '[autofocus]'
+})
+export class AutofocusDirective implements AfterViewInit {
+ constructor (private host: ElementRef) { }
+
+ ngAfterViewInit () {
+ this.host.nativeElement.focus()
+ }
+}
+export * from './autofocus.directive'
export * from './bytes.pipe'
export * from './duration-formatter.pipe'
export * from './from-now.pipe'
catchError((err: HttpErrorResponse) => {
if (err.status === HttpStatusCode.UNAUTHORIZED_401 && err.error && err.error.code === 'invalid_token') {
return this.handleTokenExpired(req, next)
- } else if (err.status === HttpStatusCode.UNAUTHORIZED_401) {
+ }
+
+ if (err.status === HttpStatusCode.UNAUTHORIZED_401) {
return this.handleNotAuthenticated(err)
}
import { SharedGlobalIconModule } from '../shared-icons'
import { AccountService, ActorAvatarInfoComponent, VideoAvatarChannelComponent } from './account'
import {
+ AutofocusDirective,
BytesPipe,
DurationFormatterPipe,
FromNowPipe,
NumberFormatterPipe,
BytesPipe,
DurationFormatterPipe,
+ AutofocusDirective,
InfiniteScrollerDirective,
PeerTubeTemplateDirective,
BytesPipe,
NumberFormatterPipe,
DurationFormatterPipe,
+ AutofocusDirective,
InfiniteScrollerDirective,
PeerTubeTemplateDirective,
AbuseState,
ActorInfo,
FollowState,
+ PluginType,
UserNotification as UserNotificationServer,
UserNotificationType,
UserRight,
}
}
+ plugin?: {
+ name: string
+ type: PluginType
+ latestVersion: string
+ }
+
+ peertube?: {
+ latestVersion: string
+ }
+
createdAt: string
updatedAt: string
// Additional fields
videoUrl?: string
commentUrl?: any[]
+
abuseUrl?: string
abuseQueryParams?: { [id: string]: string } = {}
+
videoAutoBlacklistUrl?: string
+
accountUrl?: string
+
videoImportIdentifier?: string
videoImportUrl?: string
+
instanceFollowUrl?: string
+ peertubeVersionLink?: string
+
+ pluginUrl?: string
+ pluginQueryParams?: { [id: string]: string } = {}
+
constructor (hash: UserNotificationServer, user: AuthUser) {
this.id = hash.id
this.type = hash.type
this.actorFollow = hash.actorFollow
if (this.actorFollow) this.setAccountAvatarUrl(this.actorFollow.follower)
+ this.plugin = hash.plugin
+ this.peertube = hash.peertube
+
this.createdAt = hash.createdAt
this.updatedAt = hash.updatedAt
case UserNotificationType.AUTO_INSTANCE_FOLLOWING:
this.instanceFollowUrl = '/admin/follows/following-list'
break
+
+ case UserNotificationType.NEW_PEERTUBE_VERSION:
+ this.peertubeVersionLink = 'https://joinpeertube.org/news'
+ break
+
+ case UserNotificationType.NEW_PLUGIN_VERSION:
+ this.pluginUrl = `/admin/plugins/list-installed`
+ this.pluginQueryParams.pluginType = this.plugin.type + ''
+ break
}
} catch (err) {
this.type = null
<div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)">
<ng-container [ngSwitch]="notification.type">
- <ng-container *ngSwitchCase="UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION">
+ <ng-container *ngSwitchCase="1"> <!-- UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION -->
<ng-container *ngIf="notification.video; then hasVideo; else noVideo"></ng-container>
<ng-template #hasVideo>
</ng-template>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.UNBLACKLIST_ON_MY_VIDEO">
+ <ng-container *ngSwitchCase="5"> <!-- UserNotificationType.UNBLACKLIST_ON_MY_VIDEO -->
<my-global-icon iconName="undo" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.BLACKLIST_ON_MY_VIDEO">
+ <ng-container *ngSwitchCase="4"> <!-- UserNotificationType.BLACKLIST_ON_MY_VIDEO -->
<my-global-icon iconName="no" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.NEW_ABUSE_FOR_MODERATORS">
+ <ng-container *ngSwitchCase="3"> <!-- UserNotificationType.NEW_ABUSE_FOR_MODERATORS -->
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
<div class="message" *ngIf="notification.videoUrl" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.ABUSE_STATE_CHANGE">
+ <ng-container *ngSwitchCase="15"> <!-- UserNotificationType.ABUSE_STATE_CHANGE -->
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.ABUSE_NEW_MESSAGE">
+ <ng-container *ngSwitchCase="16"> <!-- UserNotificationType.ABUSE_NEW_MESSAGE -->
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS">
+ <ng-container *ngSwitchCase="12"> <!-- UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS -->
<my-global-icon iconName="no" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.NEW_COMMENT_ON_MY_VIDEO">
+ <ng-container *ngSwitchCase="2">
<ng-container *ngIf="notification.comment">
<a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
</ng-container>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_PUBLISHED">
+ <ng-container *ngSwitchCase="6"> <!-- UserNotificationType.MY_VIDEO_PUBLISHED -->
<my-global-icon iconName="film" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_SUCCESS">
+ <ng-container *ngSwitchCase="7"> <!-- UserNotificationType.MY_VIDEO_IMPORT_SUCCESS -->
<my-global-icon iconName="cloud-download" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_ERROR">
+ <ng-container *ngSwitchCase="8"> <!-- UserNotificationType.MY_VIDEO_IMPORT_ERROR -->
<my-global-icon iconName="cloud-error" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.NEW_USER_REGISTRATION">
+ <ng-container *ngSwitchCase="9"> <!-- UserNotificationType.NEW_USER_REGISTRATION -->
<my-global-icon iconName="user-add" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.NEW_FOLLOW">
+ <ng-container *ngSwitchCase="10"> <!-- UserNotificationType.NEW_FOLLOW -->
<a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.actorFollow.follower.avatarUrl" />
</a>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.COMMENT_MENTION">
+ <ng-container *ngSwitchCase="11">
<ng-container *ngIf="notification.comment">
<a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
</ng-container>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.NEW_INSTANCE_FOLLOWER">
+ <ng-container *ngSwitchCase="13"> <!-- UserNotificationType.NEW_INSTANCE_FOLLOWER -->
<my-global-icon iconName="users" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
- <ng-container *ngSwitchCase="UserNotificationType.AUTO_INSTANCE_FOLLOWING">
+ <ng-container *ngSwitchCase="14"> <!-- UserNotificationType.AUTO_INSTANCE_FOLLOWING -->
<my-global-icon iconName="users" aria-hidden="true"></my-global-icon>
<div class="message" i18n>
</div>
</ng-container>
+ <ng-container *ngSwitchCase="17"> <!-- UserNotificationType.NEW_PLUGIN_VERSION -->
+ <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
+
+ <div class="message" i18n>
+ <a (click)="markAsRead(notification)" [routerLink]="notification.pluginUrl" [queryParams]="notification.pluginQueryParams">A new version of the plugin/theme {{ notification.plugin.name }}</a> is available: {{ notification.plugin.latestVersion }}
+ </div>
+ </ng-container>
+
+ <ng-container *ngSwitchCase="18"> <!-- UserNotificationType.NEW_PEERTUBE_VERSION -->
+ <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
+
+ <div class="message" i18n>
+ <a (click)="markAsRead(notification)" [href]="notification.peertubeVersionLink" target="_blank" rel="noopener noreferer">A new version of PeerTube</a> is available: {{ notification.peertube.latestVersion }}
+ </div>
+ </ng-container>
+
<ng-container *ngSwitchDefault>
<my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
notifications: UserNotification[] = []
sortField = 'createdAt'
- // So we can access it in the template
- UserNotificationType = UserNotificationType
-
componentPagination: ComponentPagination
onDataSubject = new Subject<any[]>()
}
loadNotifications (reset?: boolean) {
- this.userNotificationService.listMyNotifications({
+ const options = {
pagination: this.componentPagination,
ignoreLoadingBar: this.ignoreLoadingBar,
sort: {
// if we order by creation date, we want DESC. all other fields are ASC (like unread).
order: this.sortField === 'createdAt' ? -1 : 1
}
- })
+ }
+
+ this.userNotificationService.listMyNotifications(options)
.subscribe(
result => {
this.notifications = reset ? result.data : this.notifications.concat(result.data)
<input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getLink()" />
<div class="input-group-append" *ngIf="!isConfidentialVideo()">
<button [cdkCopyToClipboard]="urlInput.value" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
- <span class="glyphicon glyphicon-copy"></span>
+ <span class="glyphicon glyphicon-duplicate"></span>
</button>
</div>
</div>
import { mapValues, pick } from 'lodash-es'
+import { pipe } from 'rxjs'
+import { tap } from 'rxjs/operators'
import { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core'
-import { AuthService, Notifier } from '@app/core'
-import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { AuthService, HooksService, Notifier } from '@app/core'
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models'
import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main'
export class VideoDownloadComponent {
@ViewChild('modal', { static: true }) modal: ElementRef
- downloadType: 'direct' | 'torrent' = 'torrent'
+ downloadType: 'direct' | 'torrent' = 'direct'
resolutionId: number | string = -1
subtitleLanguageId: string
videoFileMetadataVideoStream: FileMetadata | undefined
videoFileMetadataAudioStream: FileMetadata | undefined
videoCaptions: VideoCaption[]
- activeModal: NgbActiveModal
+ activeModal: NgbModalRef
type: DownloadType = 'video'
private notifier: Notifier,
private modalService: NgbModal,
private videoService: VideoService,
- private auth: AuthService
+ private auth: AuthService,
+ private hooks: HooksService
) {
this.bytesPipe = new BytesPipe()
this.numbersPipe = new NumberFormatterPipe(this.localeId)
this.resolutionId = this.getVideoFiles()[0].resolution.id
this.onResolutionIdChange()
+
if (this.videoCaptions) this.subtitleLanguageId = this.videoCaptions[0].language.id
+
+ this.activeModal.shown.subscribe(() => {
+ this.hooks.runAction('action:modal.video-download.shown', 'common')
+ })
}
onClose () {
if (this.videoFile.metadata || !this.videoFile.metadataUrl) return
await this.hydrateMetadataFromMetadataUrl(this.videoFile)
+ if (!this.videoFile.metadata) return
this.videoFileMetadataFormat = this.videoFile
? this.getMetadataFormat(this.videoFile.metadata.format)
private hydrateMetadataFromMetadataUrl (file: VideoFile) {
const observable = this.videoService.getVideoFileMetadata(file.metadataUrl)
- observable.subscribe(res => file.metadata = res)
+ .pipe(tap(res => file.metadata = res))
return observable.toPromise()
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs/>
- <g fill="none" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-width="2">
+ <g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-width="2">
<path stroke-linejoin="round" d="M8 17H5h0a4 4 0 111-7.9v-.6a5.5 5.5 0 0110.8-1.4A5 5 0 0123 12a5 5 0 01-5 5h-2"/>
<path d="M12 13v8"/>
<path stroke-linejoin="round" d="M15 20l-3 3-3-3"/>
+++ /dev/null
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
- <defs/>
- <defs>
- <linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="97.33%">
- <stop stop-color="#000" offset="0%"/>
- <stop stop-color="#000" offset="100%" stop-opacity=".25"/>
- </linearGradient>
- <linearGradient id="b" x1="50%" x2="50%" y1="0%" y2="97.86%">
- <stop stop-color="#000" offset="0%"/>
- <stop stop-color="#000" offset="100%" stop-opacity=".25"/>
- </linearGradient>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <circle cx="12" cy="10" r="3" fill="#000"/>
- <path fill="url(#a)" fill-rule="nonzero" d="M16.39 13.85A5.68 5.68 0 0018 10c0-3.26-2.74-6-6-6s-6 2.74-6 6c0 1.42.58 2.7 1.62 3.85a.5.5 0 00.74-.67A4.7 4.7 0 017 10c0-2.7 2.3-5 5-5s5 2.3 5 5a4.7 4.7 0 01-1.36 3.18.5.5 0 10.75.67z"/>
- <path fill="url(#b)" fill-rule="nonzero" d="M17.57 18.3A9.99 9.99 0 0012 0a10 10 0 00-5.56 18.31 1 1 0 101.11-1.66 7.99 7.99 0 118.9 0 1 1 0 101.12 1.66z"/>
- <path fill="#000" d="M9.33 15.98A1.64 1.64 0 0111 14h2c1.1 0 1.85.88 1.67 1.98l-1 6.04c-.1.54-.61.98-1.17.98h-1c-.55 0-1.07-.43-1.16-.98l-1.01-6.04z"/>
- </g>
-</svg>
<svg xmlns="http://www.w3.org/2000/svg" transform="scale(1.2)" viewBox="0 0 200 200">
<defs/>
- <path stroke="#000" stroke-width="3" d="M93 155H42a18 18 0 01-18-18V29a5 5 0 015-5h89a5 5 0 015 6L98 151a5 5 0 01-5 4zM34 34v103a8 8 0 008 8h47l22-111z"/>
- <path stroke="#000" stroke-width="3" d="M171 176H75a5 5 0 01-5-6l4-21a5 5 0 0110 2l-3 15h85V63a8 8 0 00-8-8h-45a5 5 0 010-10h45a18 18 0 0118 18v108a5 5 0 01-5 5zM50 92h0a5 5 0 01-5-5V63a17 17 0 0135 0v24a5 5 0 01-10 0V62a7 7 0 00-15 0v25a5 5 0 01-5 5z"/>
- <path stroke="#000" stroke-width="3" d="M75 76H50a5 5 0 010-10h25a5 5 0 010 10zM120 155a5 5 0 01-3-9l21-21h-18a5 5 0 010-10h30a5 5 0 014 9l-30 30a5 5 0 01-4 1z"/>
- <path stroke="#000" stroke-width="3" d="M150 155a5 5 0 01-4-1l-14-15a5 5 0 017-7l15 14a5 5 0 01-4 9zM143 110h-15a5 5 0 110-10h15a5 5 0 010 10z"/>
+ <path stroke="currentColor" stroke-width="3" d="M93 155H42a18 18 0 01-18-18V29a5 5 0 015-5h89a5 5 0 015 6L98 151a5 5 0 01-5 4zM34 34v103a8 8 0 008 8h47l22-111z"/>
+ <path stroke="currentColor" stroke-width="3" d="M171 176H75a5 5 0 01-5-6l4-21a5 5 0 0110 2l-3 15h85V63a8 8 0 00-8-8h-45a5 5 0 010-10h45a18 18 0 0118 18v108a5 5 0 01-5 5zM50 92h0a5 5 0 01-5-5V63a17 17 0 0135 0v24a5 5 0 01-10 0V62a7 7 0 00-15 0v25a5 5 0 01-5 5z"/>
+ <path stroke="currentColor" stroke-width="3" d="M75 76H50a5 5 0 010-10h25a5 5 0 010 10zM120 155a5 5 0 01-3-9l21-21h-18a5 5 0 010-10h30a5 5 0 014 9l-30 30a5 5 0 01-4 1z"/>
+ <path stroke="currentColor" stroke-width="3" d="M150 155a5 5 0 01-4-1l-14-15a5 5 0 017-7l15 14a5 5 0 01-4 9zM143 110h-15a5 5 0 110-10h15a5 5 0 010 10z"/>
</svg>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 18 7" style="transform: scale(1.3) translateY(1px);">\r
- <path fill="#000" d="M0,0h18v6H9v1H5V6H0V0z M1,5h2V2h1v3h1V1H1V5z M6,1v5h2V5h2V1H6z M8,2h1v2H8V2z M11,1v4h2V2h1v3h1V2h1v3h1V1H11z"/>\r
+ <path fill="currentColor" d="M0,0h18v6H9v1H5V6H0V0z M1,5h2V2h1v3h1V1H1V5z M6,1v5h2V5h2V1H6z M8,2h1v2H8V2z M11,1v4h2V2h1v3h1V2h1v3h1V1H11z"/>\r
<polygon fill="#FFFFFF" points="1,5 3,5 3,2 4,2 4,5 5,5 5,1 1,1 "/>\r
<polygon fill="#FFFFFF" d="M6,1v5h2V5h2V1H6z M9,4H8V2h1V4z"/>\r
<polygon fill="#FFFFFF" points="11,1 11,5 13,5 13,2 14,2 14,5 15,5 15,2 16,2 16,5 17,5 17,1 "/>\r
-<?xml version="1.0" encoding="utf-8"?>\r
-<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\r
-<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\r
- viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">\r
-<style type="text/css">\r
- .st0{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}\r
- .st1{fill:#211F20;}\r
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">\r
+ <style type="text/css">\r
+ .st0{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}\r
+ .st1{fill:currentColor;}\r
</style>\r
-<line class="st0" x1="17.1" y1="9.5" x2="22.1" y2="14.5"/>\r
-<line class="st0" x1="22.1" y1="9.5" x2="17.1" y2="14.5"/>\r
-<g>\r
+ <line class="st0" x1="17.1" y1="9.5" x2="22.1" y2="14.5" />\r
+ <line class="st0" x1="22.1" y1="9.5" x2="17.1" y2="14.5" />\r
<g>\r
<g>\r
- <path class="st1" d="M2,2.6V12l6.9-4.3"/>\r
- <path class="st1" d="M2,12v9.4l6.9-5.2"/>\r
- <path class="st1" d="M8.9,7.7v8.6l6.9-4.3"/>\r
+ <g>\r
+ <path class="st1" d="M2,2.6V12l6.9-4.3" />\r
+ <path class="st1" d="M2,12v9.4l6.9-5.2" />\r
+ <path class="st1" d="M8.9,7.7v8.6l6.9-4.3" />\r
+ </g>\r
</g>\r
</g>\r
-</g>\r
</svg>\r
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 426.7 426.7">
<defs/>
- <path fill="#000" d="M0 64h256v42.7H0zM0 149.3h256V192H0zM0 234.7h170.7v42.7H0z"/>
- <path fill="#000" d="M341.3 234.7v-85.4h-42.6v85.4h-85.4v42.6h85.4v85.4h42.6v-85.4h85.4v-42.6z"/>
+ <path fill="currentColor" d="M0 64h256v42.7H0zM0 149.3h256V192H0zM0 234.7h170.7v42.7H0z"/>
+ <path fill="currentColor" d="M341.3 234.7v-85.4h-42.6v85.4h-85.4v42.6h85.4v85.4h42.6v-85.4h85.4v-42.6z"/>
</svg>
<g transform="translate(2.669496,27.625894)">
<g transform="matrix(0.1,0,0,-0.1,0,511)">
<path d="m 3744.3542,4564.3712 c -217.4,-34.2 -520.3,-200.3 -693.7,-376.2 -263.8,-263.8 -388.4,-571.6 -388.4,-952.6 0,-256.5 44,-437.2 173.4,-684 75.7,-144.1 197.9,-280.9 747.5,-842.7 1106.5,-1133.40001 1138.2,-1165.20001 1253,-1194.50001 188.1,-51.3 214.9,-29.3 1162.7,938.00001 498.3,508.1 911.1,950.2 962.4,1030.8 263.8,415.3 283.3,964.9 48.8,1409.4 -180.8,342 -581.3,620.4 -972.2,676.6 -332.2,48.9 -671.7,-36.6 -967.3,-236.9 l -156.3,-109.9 -119.7,87.9 c -158.8,117.2 -351.8,202.7 -554.5,244.3 -183.1,39.1 -295.4,41.6 -495.7,9.8 z"
- fill="#000"/>
+ fill="currentColor"/>
<path d="m 7991.4051,47.633899 c -39.1,-19.5 -473.9,-437.299999 -964.9,-925.800029 l -891.6,-891.59997 h -830.5 c -757.2,0 -837.8,4.9 -913.6,44 -207.6,112.4 -227.2,415.2 -39.1,561.8 66,53.7 83,53.7 950.2,53.7 989.3,0 1008.8,2.5 1094.3,173.49997 56.2,105 56.2,317.50003 4.9,427.50003 -83.1,175.9 4.8,168.5 -1915.1,168.5 h -1722 l -173.4,-63.5 c -95.3,-34.2 -232.1,-102.6 -305.3,-151.5 -73.3,-48.9 -442.1,-400.60003 -823.2,-779.2 l -688.80006,-693.7 664.40006,-647.3 c 366.4,-354.2 779.2,-754.8 918.4,-889.1 l 251.6,-241.8 481.2,481.2 481.2,481.2 h 1487.6 c 1294.6,0 1494.9,4.9 1565.8,39.1 58.6,26.9 339.6,368.8 1028.4,1248.2 522.8,666.89997 964.9,1243.3 982,1284.9 41.5,92.8 2.5,212.499999 -95.3,297.999999 -66,53.7 -95.3,61.1 -273.6,61.1 -132,-0.1 -224.8,-12.3 -273.6,-39.2 z"
- fill="#000"/>
+ fill="currentColor"/>
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" transform="scale(1.1)" viewBox="0 0 24 24">
<defs/>
<g class="layer">
- <path fill="#fff" fill-rule="evenodd" stroke="#000" stroke-width="1.8" d="M20.5 6.7s-.2-1.4-.8-2c-.7-.8-1.6-.8-2-.9-2.7-.2-6.9-.2-6.9-.2h0s-4.2 0-7 .2c-.3 0-1.2 0-2 .9-.5.6-.7 2-.7 2L.9 10v1.6l.2 3.3s.2 1.4.8 2c.7.8 1.7.8 2.2.9 1.6.2 6.7.2 6.7.2s4.2 0 7-.2c.3 0 1.2 0 2-.9.5-.6.7-2 .7-2l.2-3.3V10l-.2-3.3h0z"/>
+ <path fill="#fff" fill-rule="evenodd" stroke="currentColor" stroke-width="1.8" d="M20.5 6.7s-.2-1.4-.8-2c-.7-.8-1.6-.8-2-.9-2.7-.2-6.9-.2-6.9-.2h0s-4.2 0-7 .2c-.3 0-1.2 0-2 .9-.5.6-.7 2-.7 2L.9 10v1.6l.2 3.3s.2 1.4.8 2c.7.8 1.7.8 2.2.9 1.6.2 6.7.2 6.7.2s4.2 0 7-.2c.3 0 1.2 0 2-.9.5-.6.7-2 .7-2l.2-3.3V10l-.2-3.3h0z"/>
<path d="M8.7 14.7a.7.7 0 01-.5-1.2l2.9-3H8.7a.7.7 0 010-1.3h4a.7.7 0 01.5 1.2l-4 4a.7.7 0 01-.5.3zM11.7 8.6h-2a.7.7 0 110-1.4h2a.7.7 0 010 1.4z"/>
</g>
</svg>
animation: spin .7s infinite linear;
}
+.glyphicon-duplicate {
+ font-size: 70%;
+}
+
.flex-auto {
flex: auto;
}
word-break: break-word;
word-wrap: break-word;
overflow-wrap: break-word;
- -webkit-hyphens: auto;
- -ms-hyphens: auto;
- -moz-hyphens: auto;
hyphens: auto;
}
::ng-deep .material {
color: $color;
}
-
- ::ng-deep svg {
- path[fill="#000"],
- g[fill="#000"],
- rect[fill="#000"],
- circle[fill="#000"],
- polygon[fill="#000"] {
- fill: $color;
- }
-
- path[stroke="#000"],
- g[stroke="#000"],
- rect[stroke="#000"],
- circle[stroke="#000"],
- polygon[stroke="#000"] {
- stroke: $color;
- }
-
- stop[stop-color="#000"] {
- stop-color: $color;
- }
- }
}
@mixin fill-svg-color ($color) {
}
}
- .vjs-button > .vjs-icon-placeholder::before {
- line-height: $control-bar-height;
- }
-
.vjs-volume-level::before {
content: ''; /* Remove Circle From Progress Bar */
}
@include disable-outline;
cursor: pointer;
- font-size: $play-control-font-size;
width: 2em;
+
+ .vjs-icon-placeholder {
+ line-height: $control-bar-height;
+ position: relative;
+ top: -1px;
+
+ &::before {
+ font-size: 28px;
+ line-height: unset;
+ position: relative;
+ }
+ }
}
.vjs-time-control {
.vjs-mute-control {
@include disable-outline;
- line-height: $control-bar-height;
padding: 0;
width: 30px;
showModal: unimplemented,
+ getServerConfig: unimplemented,
+
markdownRenderer: {
textMarkdownToHTML: unimplemented,
enhancedMarkdownToHTML: unimplemented
import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model'
import { RegisterClientHookOptions } from '@shared/models/plugins/register-client-hook.model'
+import { ServerConfig } from '@shared/models/server'
export type RegisterClientOptions = {
registerHook: (options: RegisterClientHookOptions) => void
getSettings: () => Promise<{ [ name: string ]: string }>
+ getServerConfig: () => Promise<ServerConfig>
+
notifier: {
info: (text: string, title?: string, timeout?: number) => void,
error: (text: string, title?: string, timeout?: number) => void,
# yarn lockfile v1
-"@angular-devkit/architect@0.1102.2":
- version "0.1102.2"
- resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1102.2.tgz#3b3eb654ae7c8c204b248bba76982ce8de2f7b6c"
- integrity sha512-FE7DeT13elqDlELF23QqvEFnT2BkxeC5t31/QW85IN/OR5Tf/q7XEpj7giJXyzKFQ60M3ZzbznZyRz0EqtfaBQ==
+"@angular-devkit/architect@0.1102.5":
+ version "0.1102.5"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1102.5.tgz#431df157af0c6477e5951f64ff12f3d5d5f075ee"
+ integrity sha512-lVc6NmEAZZPzvc18GzMFLoxqKKvPlNOg4vEtFsFldZmrydLJJGFi4KAs2WaJd8qVR1XuY4el841cjDQAJSq6sQ==
dependencies:
- "@angular-devkit/core" "11.2.2"
+ "@angular-devkit/core" "11.2.5"
rxjs "6.6.3"
"@angular-devkit/build-angular@^0.1102.2":
- version "0.1102.2"
- resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.1102.2.tgz#c850818fd8bb4dd4fda6288390868475c4b3236e"
- integrity sha512-AjnvHrzkYTzDGzp0r5RmGoP9fyZXtaVFo0598PRusi1oWp1sW6B5FKPWw896iREOlotRXw3dsjqrGwbMcz0qyg==
- dependencies:
- "@angular-devkit/architect" "0.1102.2"
- "@angular-devkit/build-optimizer" "0.1102.2"
- "@angular-devkit/build-webpack" "0.1102.2"
- "@angular-devkit/core" "11.2.2"
+ version "0.1102.5"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.1102.5.tgz#7db51dfc33a8683458fa714d434f8c09fdc1f648"
+ integrity sha512-iAq/KbRq6kuA17rQZ67/0zQHEzpC9RzvtMZQ3wiiFsOmW5AIV5scjP7e6dn+F6vXZA44X4gCH5AUUkOLXyEtfg==
+ dependencies:
+ "@angular-devkit/architect" "0.1102.5"
+ "@angular-devkit/build-optimizer" "0.1102.5"
+ "@angular-devkit/build-webpack" "0.1102.5"
+ "@angular-devkit/core" "11.2.5"
"@babel/core" "7.12.10"
"@babel/generator" "7.12.11"
"@babel/plugin-transform-async-to-generator" "7.12.1"
"@babel/preset-env" "7.12.11"
"@babel/runtime" "7.12.5"
"@babel/template" "7.12.7"
+ "@discoveryjs/json-ext" "0.5.2"
"@jsdevtools/coverage-istanbul-loader" "3.0.5"
- "@ngtools/webpack" "11.2.2"
+ "@ngtools/webpack" "11.2.5"
ansi-colors "4.1.1"
autoprefixer "10.2.4"
babel-loader "8.2.2"
webpack-subresource-integrity "1.5.2"
worker-plugin "5.0.0"
-"@angular-devkit/build-optimizer@0.1102.2":
- version "0.1102.2"
- resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.1102.2.tgz#a306fee0bc648983405320953f05ad1fc60b6b84"
- integrity sha512-TCWWqAe+pWZzLp/g2gG8Z5NC8JSgDNfyEuMBWxEUfo1Sm3BluXoz0BbmnietuhXJZ+fPAp9rLLzEGZlHvOlmOA==
+"@angular-devkit/build-optimizer@0.1102.5":
+ version "0.1102.5"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.1102.5.tgz#5c17d82a8c4f03ec0a14110838c2c3da6cb24dfd"
+ integrity sha512-ujTwrevgMRNyWir4IdnJEdDRkVSLqugRpL6cU9OeqGn6Bu+zEzZQokLkMZvbw00eEKlf5Siej4hEeF1Hnx+LUA==
dependencies:
loader-utils "2.0.0"
source-map "0.7.3"
tslib "2.1.0"
- typescript "4.1.3"
+ typescript "4.1.5"
webpack-sources "2.2.0"
-"@angular-devkit/build-webpack@0.1102.2":
- version "0.1102.2"
- resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1102.2.tgz#f48501426a5d01b0610dafce33b4eb84d07181e6"
- integrity sha512-59CBbwbdN8lI5/whuNeAZHRJxPlOmDc5ux8aJJNwWI9w54fz0ut/MLT3iuPk+WZuKlGdpS1sGkObfZwWen5kIQ==
+"@angular-devkit/build-webpack@0.1102.5":
+ version "0.1102.5"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1102.5.tgz#e111acf7c0cbed761ae382089052a5c2dee71d96"
+ integrity sha512-VMsi+mFwgPUQi7eEc2oKcf7X0xD0R1xfoguLS/+HGy3sfh+b7oJy3BU4+TRzDPBtGj6vWvENK2rwHFN3cBWvxA==
dependencies:
- "@angular-devkit/architect" "0.1102.2"
- "@angular-devkit/core" "11.2.2"
+ "@angular-devkit/architect" "0.1102.5"
+ "@angular-devkit/core" "11.2.5"
rxjs "6.6.3"
-"@angular-devkit/core@11.2.2":
- version "11.2.2"
- resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-11.2.2.tgz#c6b40f941b24d2af447831fc958b744316cd7d87"
- integrity sha512-LUDO1AdIjereiMh0j5p9xJcdr9ifhbWCPxlZqfu5wHzUfhCx9gO2Lvjp6rZXQ3OedXg5IZUnyxHlzkszQOsgiw==
+"@angular-devkit/core@11.2.5":
+ version "11.2.5"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-11.2.5.tgz#f9ba8288a6cc388808ee639c383dada50d64d06a"
+ integrity sha512-DRFvEHRKoC+hTwcOAJqLe6UQa+bpXc/1IGCMHWEbuply0KIFIGQOlmaYwFZKixz3HdFZlmoCMcAVkAXvyaWVsQ==
dependencies:
ajv "6.12.6"
fast-json-stable-stringify "2.1.0"
rxjs "6.6.3"
source-map "0.7.3"
-"@angular-devkit/schematics@11.2.2":
- version "11.2.2"
- resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-11.2.2.tgz#0c8c4b98a30f00649dcbb7794d3783b9a067209f"
- integrity sha512-6bIxMwafz/+lwdtcshwOuFfhxTMU4RLma1uxBS34DXupMauPGl0IIXAy5cK9dXPlHLxuGsjeBiOM6eq033RLgw==
+"@angular-devkit/schematics@11.2.5":
+ version "11.2.5"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-11.2.5.tgz#ddcb966f3f1dc910e55f03067036f1f6a01b8222"
+ integrity sha512-7RoWgpMvhljPhW9CMz1EtqkwNnGpnsPyy0N29ClHPUq+o8wLR0hvbLBDz1fKSF7j1AwRccaQSNTj8KWsjzQJLQ==
dependencies:
- "@angular-devkit/core" "11.2.2"
+ "@angular-devkit/core" "11.2.5"
ora "5.3.0"
rxjs "6.6.3"
"@angular/animations@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-11.2.3.tgz#518183e5f7b8c3b304020ea86d12cc3216142cc9"
- integrity sha512-Z6sHIeTeeZrRAW83NI7FO7THF50cPCFkkuvVah3qmCqopY6FuoHKUBEENyGzQGH69LbGFYhEppY8KM/6JtVF6Q==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-11.2.6.tgz#36935bc0fe33f1486ed889f8b5e12915858ccf5a"
+ integrity sha512-fci034QakkoIrFeY/uOmDvf6AupZ7ziU1FlBMs/wn4HOqwsPCofpawvFQnfj5nez1+KM5JOJ1VHmZKJupkWfgw==
dependencies:
tslib "^2.0.0"
"@angular/cdk@^11.0.0":
- version "11.2.2"
- resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-11.2.2.tgz#f541069db3f5705d8c064138f6cd94568fe1b658"
- integrity sha512-p3lRDPlnOuJtLWEd020QOyn0ERyc1LF7OLi90hTdzMMxe9fT3v6sQJVRs8jIY3NTmpIm/pNDGi77+1/vKerLPQ==
+ version "11.2.5"
+ resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-11.2.5.tgz#e0cce8b28ca635b6151b834c6e1c4bc0a8dd7c04"
+ integrity sha512-ugalSDLME5E9JlxcRR8RGlOYlaV6rIzxOVQrGRBzY2tdhMT4Ng+BFtCkq1K88AU1sTLHq54xg9Xkfn7b5W2kiA==
dependencies:
tslib "^2.0.0"
optionalDependencies:
parse5 "^5.0.0"
"@angular/cli@^11.1.2":
- version "11.2.2"
- resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-11.2.2.tgz#ca56894f1a4d1f4e411408b8185b711614c3195a"
- integrity sha512-rOVBzDzrMuOgJY43O46/7yYbncx0egGfr+DMJDQdazePGH1H3INN/eA9gkVcVK53ztCYb9X1sbZKOs9TUhF6nw==
- dependencies:
- "@angular-devkit/architect" "0.1102.2"
- "@angular-devkit/core" "11.2.2"
- "@angular-devkit/schematics" "11.2.2"
- "@schematics/angular" "11.2.2"
- "@schematics/update" "0.1102.2"
+ version "11.2.5"
+ resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-11.2.5.tgz#3cf3e6432db41cebb364da2dcf3d44588535a34a"
+ integrity sha512-GIwK8l6wtg/++8aDYW++LSf7v1uqDtB6so2rPjNlOm7oYk5iqM73KaorQb/1A52oxWE3IRSJLNQaSyUlWvHvSA==
+ dependencies:
+ "@angular-devkit/architect" "0.1102.5"
+ "@angular-devkit/core" "11.2.5"
+ "@angular-devkit/schematics" "11.2.5"
+ "@schematics/angular" "11.2.5"
+ "@schematics/update" "0.1102.5"
"@yarnpkg/lockfile" "1.1.0"
ansi-colors "4.1.1"
debug "4.3.1"
uuid "8.3.2"
"@angular/common@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/common/-/common-11.2.3.tgz#e71d645fb6bdef9463f23a551cc072ef276c1d84"
- integrity sha512-51gVmr942SZtAFmhVfp7/3fcTQ+Tia7UxWjv6iUtYF3oCvTWbo/J1zki2VNSfmMNKJV8MaMq6XUw8UWbHA0sgQ==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/common/-/common-11.2.6.tgz#9985b9f1b3d82588f85bb74b1967749b0134d017"
+ integrity sha512-q1yR6bktd5p987gLEKiFY4CrHcmBxks9R6GcdgzGneQsucDtGESzEKdcJ0uaMXE+9teS+fQy5GvXel6DlA/J+w==
dependencies:
tslib "^2.0.0"
"@angular/compiler-cli@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-11.2.3.tgz#5307215b9aa6e32d772906fd3b2960ba03a7565d"
- integrity sha512-ObQVI6q2c0VTWbsDnWJDdUZv2Jz/u1jiQNcrdtu/rjtJARaldEno9dMakN838Q6Nw4FzKUO6uYZXmnvKCUjfxQ==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-11.2.6.tgz#456844d71079df3ca3f025aaa9d9df9ed5a79006"
+ integrity sha512-1OC8UkySaLzaw3aSrm8A6SA88CxQAdA4ffaOhBLE/Ee6CxpneVxn3ORlnccqnS8zWyEpschbootPJV56U3Azeg==
dependencies:
"@babel/core" "^7.8.6"
"@babel/types" "^7.8.6"
integrity sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==
"@angular/compiler@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-11.2.3.tgz#72427d57b992bf6840fb7268357a466095caf8eb"
- integrity sha512-De8BwtSwPVYGdvQa6CDq2C1SLmB78YjS0t/KNlvfp85cl4Gb3BdjTDsKMkJXkm/3ubnIXi1BaRIsFNVTCCF70Q==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-11.2.6.tgz#8b69cd2f2c3bb0fbc6f95ded1ccbe20e6858daed"
+ integrity sha512-3ijsCxnCLU1V1hy4UMf9qtMz5LR+wCdVFDqktEQccN9YEkN0cNtOc8Nu9EV9/mc2tqd1Q4xSBpb2o2mvpy7AhQ==
dependencies:
tslib "^2.0.0"
integrity sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==
"@angular/core@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/core/-/core-11.2.3.tgz#7dd59f35e0b2410543a61be6048c474c18a43f40"
- integrity sha512-+G7rZj21Mcmf6nWjQ79EwomwEOVQ1WLqw6YvCXWzgJ9ZlVjLi/Sti0/jIzUpgK0E0Fn86yuXw/vgYq5kjGeOcQ==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/core/-/core-11.2.6.tgz#c38ee7834519d3c94e51be62156784a984cd93d2"
+ integrity sha512-lS5JOQ/Y9gbk5WiMnCp5Zyz2pRIoZ+IWLOXHU5rkQeXy0zE3eMJhw0FfpEK+X5CeSNl2EPVSPLT0MtDtbNPodg==
dependencies:
tslib "^2.0.0"
"@angular/forms@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-11.2.3.tgz#57460a110e6601b50362f878fc0f67701c76dc24"
- integrity sha512-VfyKV8IxHTclcHQmt5gjGFmKC1kGz7sdNLYsEM+M0y88Bsufh3VIhK4kspfO4nhJxVfh6HFOt1JVQ5bvo6PDlQ==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-11.2.6.tgz#d82a1c655754d48ec861b9b3af370e6ee1e841cb"
+ integrity sha512-0xxayXCNc8lPQhDj5q/hAcG55cmDXPSBn2cxX4V+uDSGwKU1+h2CQID6gJdBJBh5wOaeMe6h8dK2s1pRgok66A==
dependencies:
tslib "^2.0.0"
"@angular/localize@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-11.2.3.tgz#df2e605341be53c2d4cead2d8b274415af8b3136"
- integrity sha512-SCpum70G+MuoRitbv+u92fjDlKEbYizTosukxryh56QNa47iO3/rkVp8P2R75FDYJVJrxqoTiMGl0Q9tKdrEGA==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-11.2.6.tgz#465f2541c5bcdc396725504becaec3b96c718ec8"
+ integrity sha512-8K+SdqKqIaRlNRegDBy//VAtf2rlwoZAmqoFfiM5ujuB4SFt32NAduxDUlFGWdZD5V3iPorFBrceq04bt695AA==
dependencies:
"@babel/core" "7.8.3"
glob "7.1.2"
yargs "^16.1.1"
"@angular/platform-browser-dynamic@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.3.tgz#3d7eb15ba4bcc9e227f68f13bf20258fa16efad1"
- integrity sha512-QUPCvack7De6u5AqWcW8O6FzczwqoL858R1NlnqojnNbcnN/dCtXtKvvETEEgp/9VMwLfcuLd1BWdBJSah7f6A==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.6.tgz#26acbe4de315019ebe1e925ee826eda20c95d881"
+ integrity sha512-B56b8yPW3vAmPe4VONiBYEMZ6B1i5CUkJvit8qWWK3y7t5XrYOihIiGC0UqEDaw/uAg72GXjixspcxZWan5e9w==
dependencies:
tslib "^2.0.0"
"@angular/platform-browser@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-11.2.3.tgz#0c6b537500a1c6304829fab19cf8c12daa2b48b9"
- integrity sha512-S0IP/kGinIH18+gfnX0gLFLbP0Euw1RBceDt/WipYhUeFZZryQHvot/6KFLFtO+8rVunfrg+UyBiaK65/TT9Og==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-11.2.6.tgz#d2af4323275f501e279ee2aa821ac5599c11feae"
+ integrity sha512-xnYpfoqWyQOUngfbHefsZMyelCSAaxpopu/WYP0gpbYh9qJiVhsN9s6zRMqOIPueq9lmvlEuGBMgaJjeD6Ei7Q==
dependencies:
tslib "^2.0.0"
"@angular/router@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/router/-/router-11.2.3.tgz#407a0797845c1cac963663537b30872e39e4b229"
- integrity sha512-lRuEIlNj2BcBZ17mt5SZY7v80PsvlS4J6EbKSOFeSYhALM/AQnaaCdrrMlQ1WyEa5bBUabxGT9/zvahBosy2yA==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/router/-/router-11.2.6.tgz#5845ef37e85400aeeaf0ffe670802a58569638cc"
+ integrity sha512-n/3Sp36slXzRXUcUO9nVs3CkgFxa6U9A8GENeyxq9XQtcE912jOP4dzjDi3hlaNKbX9ijOyEh505KpqmiSYATg==
dependencies:
tslib "^2.0.0"
"@angular/service-worker@^11.1.1":
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-11.2.3.tgz#316bfc07ccebdc5af1a9cbc825082880c551c0b9"
- integrity sha512-/JgA4rCH2SyIK/v0+sCqNgiBEV/pXQUcUoqfm//2zfc3VwerehvF3RtRBfabtLBpdwdO5a9DZ4nX+djvTJypvw==
+ version "11.2.6"
+ resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-11.2.6.tgz#65e895a7a1dc309c9365ea801806549f7572646c"
+ integrity sha512-nZGwVhHZ6eLptnPzIjiFiktnl4ImC+4kejR3AaElTX8PgS9TykhYhgENB+ILU49bZOGMe3RVnNthgx/JkIEgjQ==
dependencies:
tslib "^2.0.0"
dependencies:
"@babel/highlight" "^7.12.13"
-"@babel/compat-data@^7.12.7", "@babel/compat-data@^7.13.0":
- version "7.13.6"
- resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.6.tgz#11972d07db4c2317afdbf41d6feb3a730301ef4e"
- integrity sha512-VhgqKOWYVm7lQXlvbJnWOzwfAQATd2nV52koT0HZ/LdDH0m4DUDwkKYsH+IwpXb+bKPyBJzawA4I6nBKqZcpQw==
+"@babel/compat-data@^7.12.7", "@babel/compat-data@^7.13.8":
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.12.tgz#a8a5ccac19c200f9dd49624cac6e19d7be1236a1"
+ integrity sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==
"@babel/core@7.12.10":
version "7.12.10"
source-map "^0.5.0"
"@babel/core@^7.7.5", "@babel/core@^7.8.6":
- version "7.13.1"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.1.tgz#7ddd027176debe40f13bb88bac0c21218c5b1ecf"
- integrity sha512-FzeKfFBG2rmFtGiiMdXZPFt/5R5DXubVi82uYhjGX4Msf+pgYQMCFIqFXZWs5vbIYbf14VeBIgdGI03CDOOM1w==
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.10.tgz#07de050bbd8193fcd8a3c27918c0890613a94559"
+ integrity sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw==
dependencies:
"@babel/code-frame" "^7.12.13"
- "@babel/generator" "^7.13.0"
- "@babel/helper-compilation-targets" "^7.13.0"
+ "@babel/generator" "^7.13.9"
+ "@babel/helper-compilation-targets" "^7.13.10"
"@babel/helper-module-transforms" "^7.13.0"
- "@babel/helpers" "^7.13.0"
- "@babel/parser" "^7.13.0"
+ "@babel/helpers" "^7.13.10"
+ "@babel/parser" "^7.13.10"
"@babel/template" "^7.12.13"
"@babel/traverse" "^7.13.0"
"@babel/types" "^7.13.0"
gensync "^1.0.0-beta.2"
json5 "^2.1.2"
lodash "^4.17.19"
- semver "7.0.0"
+ semver "^6.3.0"
source-map "^0.5.0"
"@babel/generator@7.12.11":
jsesc "^2.5.1"
source-map "^0.5.0"
-"@babel/generator@^7.12.10", "@babel/generator@^7.13.0", "@babel/generator@^7.8.3":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.0.tgz#bd00d4394ca22f220390c56a0b5b85568ec1ec0c"
- integrity sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==
+"@babel/generator@^7.12.10", "@babel/generator@^7.13.0", "@babel/generator@^7.13.9", "@babel/generator@^7.8.3":
+ version "7.13.9"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39"
+ integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==
dependencies:
"@babel/types" "^7.13.0"
jsesc "^2.5.1"
"@babel/helper-explode-assignable-expression" "^7.12.13"
"@babel/types" "^7.12.13"
-"@babel/helper-compilation-targets@^7.12.5", "@babel/helper-compilation-targets@^7.13.0":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.0.tgz#c9cf29b82a76fd637f0faa35544c4ace60a155a1"
- integrity sha512-SOWD0JK9+MMIhTQiUVd4ng8f3NXhPVQvTv7D3UN4wbp/6cAHnB2EmMaU1zZA2Hh1gwme+THBrVSqTFxHczTh0Q==
+"@babel/helper-compilation-targets@^7.12.5", "@babel/helper-compilation-targets@^7.13.10", "@babel/helper-compilation-targets@^7.13.8":
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.10.tgz#1310a1678cb8427c07a753750da4f8ce442bdd0c"
+ integrity sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA==
dependencies:
- "@babel/compat-data" "^7.13.0"
+ "@babel/compat-data" "^7.13.8"
"@babel/helper-validator-option" "^7.12.17"
browserslist "^4.14.5"
- semver "7.0.0"
+ semver "^6.3.0"
"@babel/helper-create-class-features-plugin@^7.13.0":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.0.tgz#28d04ad9cfbd1ed1d8b988c9ea7b945263365846"
- integrity sha512-twwzhthM4/+6o9766AW2ZBHpIHPSGrPGk1+WfHiu13u/lBnggXGNYCpeAyVfNwGDKfkhEDp+WOD/xafoJ2iLjA==
+ version "7.13.11"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz#30d30a005bca2c953f5653fc25091a492177f4f6"
+ integrity sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw==
dependencies:
"@babel/helper-function-name" "^7.12.13"
"@babel/helper-member-expression-to-functions" "^7.13.0"
dependencies:
"@babel/types" "^7.12.13"
-"@babel/helper-hoist-variables@^7.12.13":
+"@babel/helper-hoist-variables@^7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz#5d5882e855b5c5eda91e0cadc26c6e7a2c8593d8"
integrity sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g==
"@babel/traverse" "^7.13.0"
"@babel/types" "^7.13.0"
-"@babel/helper-member-expression-to-functions@^7.13.0":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz#6aa4bb678e0f8c22f58cdb79451d30494461b091"
- integrity sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ==
+"@babel/helper-member-expression-to-functions@^7.13.0", "@babel/helper-member-expression-to-functions@^7.13.12":
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72"
+ integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==
dependencies:
- "@babel/types" "^7.13.0"
+ "@babel/types" "^7.13.12"
-"@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.12.5":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0"
- integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==
+"@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.12.5", "@babel/helper-module-imports@^7.13.12":
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977"
+ integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==
dependencies:
- "@babel/types" "^7.12.13"
+ "@babel/types" "^7.13.12"
-"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.12.13", "@babel/helper-module-transforms@^7.13.0":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.0.tgz#42eb4bd8eea68bab46751212c357bfed8b40f6f1"
- integrity sha512-Ls8/VBwH577+pw7Ku1QkUWIyRRNHpYlts7+qSqBBFCW3I8QteB9DxfcZ5YJpOwH6Ihe/wn8ch7fMGOP1OhEIvw==
+"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.13.0":
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.12.tgz#600e58350490828d82282631a1422268e982ba96"
+ integrity sha512-7zVQqMO3V+K4JOOj40kxiCrMf6xlQAkewBB0eu2b03OO/Q21ZutOzjpfD79A5gtE/2OWi1nv625MrDlGlkbknQ==
dependencies:
- "@babel/helper-module-imports" "^7.12.13"
- "@babel/helper-replace-supers" "^7.13.0"
- "@babel/helper-simple-access" "^7.12.13"
+ "@babel/helper-module-imports" "^7.13.12"
+ "@babel/helper-replace-supers" "^7.13.12"
+ "@babel/helper-simple-access" "^7.13.12"
"@babel/helper-split-export-declaration" "^7.12.13"
"@babel/helper-validator-identifier" "^7.12.11"
"@babel/template" "^7.12.13"
"@babel/traverse" "^7.13.0"
- "@babel/types" "^7.13.0"
- lodash "^4.17.19"
+ "@babel/types" "^7.13.12"
"@babel/helper-optimise-call-expression@^7.12.13":
version "7.12.13"
"@babel/helper-wrap-function" "^7.13.0"
"@babel/types" "^7.13.0"
-"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz#6034b7b51943094cb41627848cb219cb02be1d24"
- integrity sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw==
+"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0", "@babel/helper-replace-supers@^7.13.12":
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804"
+ integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==
dependencies:
- "@babel/helper-member-expression-to-functions" "^7.13.0"
+ "@babel/helper-member-expression-to-functions" "^7.13.12"
"@babel/helper-optimise-call-expression" "^7.12.13"
"@babel/traverse" "^7.13.0"
- "@babel/types" "^7.13.0"
+ "@babel/types" "^7.13.12"
-"@babel/helper-simple-access@^7.12.13":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz#8478bcc5cacf6aa1672b251c1d2dde5ccd61a6c4"
- integrity sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==
+"@babel/helper-simple-access@^7.12.13", "@babel/helper-simple-access@^7.13.12":
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6"
+ integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==
dependencies:
- "@babel/types" "^7.12.13"
+ "@babel/types" "^7.13.12"
"@babel/helper-skip-transparent-expression-wrappers@^7.12.1":
version "7.12.1"
"@babel/traverse" "^7.13.0"
"@babel/types" "^7.13.0"
-"@babel/helpers@^7.12.5", "@babel/helpers@^7.13.0", "@babel/helpers@^7.8.3":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.0.tgz#7647ae57377b4f0408bf4f8a7af01c42e41badc0"
- integrity sha512-aan1MeFPxFacZeSz6Ld7YZo5aPuqnKlD7+HZY75xQsueczFccP9A7V05+oe0XpLwHK3oLorPe9eaAUljL7WEaQ==
+"@babel/helpers@^7.12.5", "@babel/helpers@^7.13.10", "@babel/helpers@^7.8.3":
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8"
+ integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==
dependencies:
"@babel/template" "^7.12.13"
"@babel/traverse" "^7.13.0"
"@babel/types" "^7.13.0"
"@babel/highlight@^7.12.13":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c"
- integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1"
+ integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
chalk "^2.0.0"
js-tokens "^4.0.0"
-"@babel/parser@^7.12.10", "@babel/parser@^7.12.13", "@babel/parser@^7.12.7", "@babel/parser@^7.13.0", "@babel/parser@^7.8.3":
- version "7.13.4"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.4.tgz#340211b0da94a351a6f10e63671fa727333d13ab"
- integrity sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==
+"@babel/parser@^7.12.10", "@babel/parser@^7.12.13", "@babel/parser@^7.12.7", "@babel/parser@^7.13.0", "@babel/parser@^7.13.10", "@babel/parser@^7.8.3":
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.12.tgz#ba320059420774394d3b0c0233ba40e4250b81d1"
+ integrity sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==
"@babel/plugin-proposal-async-generator-functions@^7.12.1":
- version "7.13.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.5.tgz#69e3fbb9958949b09036e27b26eba1aafa1ba3db"
- integrity sha512-8cErJEDzhZgNKzYyjCKsHuyPqtWxG8gc9h4OFSUDJu0vCAOsObPU2LcECnW0kJwh/b+uUz46lObVzIXw0fzAbA==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz#87aacb574b3bc4b5603f6fe41458d72a5a2ec4b1"
+ integrity sha512-rPBnhj+WgoSmgq+4gQUtXx/vOcU+UYtjy1AA/aeD61Hwj410fwYyqfUcRP3lR8ucgliVJL/G7sXcNUecC75IXA==
dependencies:
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/helper-remap-async-to-generator" "^7.13.0"
- "@babel/plugin-syntax-async-generators" "^7.8.0"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-proposal-class-properties@^7.12.1":
version "7.13.0"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-proposal-dynamic-import@^7.12.1":
- version "7.12.17"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.17.tgz#e0ebd8db65acc37eac518fa17bead2174e224512"
- integrity sha512-ZNGoFZqrnuy9H2izB2jLlnNDAfVPlGl5NhFEiFe4D84ix9GQGygF+CWMGHKuE+bpyS/AOuDQCnkiRNqW2IzS1Q==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz#876a1f6966e1dec332e8c9451afda3bebcdf2e1d"
+ integrity sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.12.13"
- "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+ "@babel/helper-plugin-utils" "^7.13.0"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
"@babel/plugin-proposal-export-namespace-from@^7.12.1":
version "7.12.13"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"
"@babel/plugin-proposal-json-strings@^7.12.1":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.13.tgz#ced7888a2db92a3d520a2e35eb421fdb7fcc9b5d"
- integrity sha512-v9eEi4GiORDg8x+Dmi5r8ibOe0VXoKDeNPYcTTxdGN4eOWikrJfDJCJrr1l5gKGvsNyGJbrfMftC2dTL6oz7pg==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz#bf1fb362547075afda3634ed31571c5901afef7b"
+ integrity sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==
dependencies:
- "@babel/helper-plugin-utils" "^7.12.13"
- "@babel/plugin-syntax-json-strings" "^7.8.0"
+ "@babel/helper-plugin-utils" "^7.13.0"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
"@babel/plugin-proposal-logical-assignment-operators@^7.12.1":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.13.tgz#575b5d9a08d8299eeb4db6430da6e16e5cf14350"
- integrity sha512-fqmiD3Lz7jVdK6kabeSr1PZlWSUVqSitmHEe3Z00dtGTKieWnX9beafvavc32kjORa5Bai4QNHgFDwWJP+WtSQ==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz#93fa78d63857c40ce3c8c3315220fd00bfbb4e1a"
+ integrity sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A==
dependencies:
- "@babel/helper-plugin-utils" "^7.12.13"
+ "@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.0.tgz#1a96fdf2c43109cfe5568513c5379015a23f5380"
- integrity sha512-UkAvFA/9+lBBL015gjA68NvKiCReNxqFLm3SdNKaM3XXoDisA7tMAIX4PmIwatFoFqMxxT3WyG9sK3MO0Kting==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz#3730a31dafd3c10d8ccd10648ed80a2ac5472ef3"
+ integrity sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==
dependencies:
"@babel/helper-plugin-utils" "^7.13.0"
- "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
"@babel/plugin-proposal-numeric-separator@^7.12.7":
version "7.12.13"
"@babel/plugin-syntax-numeric-separator" "^7.10.4"
"@babel/plugin-proposal-object-rest-spread@^7.12.1":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.0.tgz#8f19ad247bb96bd5ad2d4107e6eddfe0a789937b"
- integrity sha512-B4qphdSTp0nLsWcuei07JPKeZej4+Hd22MdnulJXQa1nCcGSBlk8FiqenGERaPZ+PuYhz4Li2Wjc8yfJvHgUMw==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a"
+ integrity sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==
dependencies:
+ "@babel/compat-data" "^7.13.8"
+ "@babel/helper-compilation-targets" "^7.13.8"
"@babel/helper-plugin-utils" "^7.13.0"
- "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
"@babel/plugin-transform-parameters" "^7.13.0"
"@babel/plugin-proposal-optional-catch-binding@^7.12.1":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.13.tgz#4640520afe57728af14b4d1574ba844f263bcae5"
- integrity sha512-9+MIm6msl9sHWg58NvqpNpLtuFbmpFYk37x8kgnGzAHvX35E1FyAwSUt5hIkSoWJFSAH+iwU8bJ4fcD1zKXOzg==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz#3ad6bd5901506ea996fc31bdcf3ccfa2bed71107"
+ integrity sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==
dependencies:
- "@babel/helper-plugin-utils" "^7.12.13"
- "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+ "@babel/helper-plugin-utils" "^7.13.0"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
"@babel/plugin-proposal-optional-chaining@^7.12.7":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.0.tgz#75b41ce0d883d19e8fe635fc3f846be3b1664f4d"
- integrity sha512-OVRQOZEBP2luZrvEbNSX5FfWDousthhdEoAOpej+Tpe58HFLvqRClT89RauIvBuCDFEip7GW1eT86/5lMy2RNA==
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz#ba9feb601d422e0adea6760c2bd6bbb7bfec4866"
+ integrity sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ==
dependencies:
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
- "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
"@babel/plugin-proposal-private-methods@^7.12.1":
version "7.13.0"
"@babel/helper-create-regexp-features-plugin" "^7.12.13"
"@babel/helper-plugin-utils" "^7.12.13"
-"@babel/plugin-syntax-async-generators@^7.8.0":
+"@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
dependencies:
"@babel/helper-plugin-utils" "^7.12.13"
-"@babel/plugin-syntax-dynamic-import@^7.8.0":
+"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-json-strings@^7.8.0":
+"@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
dependencies:
"@babel/helper-plugin-utils" "^7.10.4"
-"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0":
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
dependencies:
"@babel/helper-plugin-utils" "^7.10.4"
-"@babel/plugin-syntax-object-rest-spread@^7.8.0":
+"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/plugin-syntax-optional-catch-binding@^7.8.0":
+"@babel/plugin-syntax-optional-catch-binding@^7.8.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/plugin-syntax-optional-chaining@^7.8.0":
+"@babel/plugin-syntax-optional-chaining@^7.8.0", "@babel/plugin-syntax-optional-chaining@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
babel-plugin-dynamic-import-node "^2.3.3"
"@babel/plugin-transform-modules-commonjs@^7.12.1":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.0.tgz#276932693a20d12c9776093fdc99c0d9995e34c6"
- integrity sha512-j7397PkIB4lcn25U2dClK6VLC6pr2s3q+wbE8R3vJvY6U1UTBBj0n6F+5v6+Fd/UwfDPAorMOs2TV+T4M+owpQ==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz#7b01ad7c2dcf2275b06fa1781e00d13d420b3e1b"
+ integrity sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw==
dependencies:
"@babel/helper-module-transforms" "^7.13.0"
"@babel/helper-plugin-utils" "^7.13.0"
babel-plugin-dynamic-import-node "^2.3.3"
"@babel/plugin-transform-modules-systemjs@^7.12.1":
- version "7.12.13"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.13.tgz#351937f392c7f07493fc79b2118201d50404a3c5"
- integrity sha512-aHfVjhZ8QekaNF/5aNdStCGzwTbU7SI5hUybBKlMzqIMC7w7Ho8hx5a4R/DkTHfRfLwHGGxSpFt9BfxKCoXKoA==
+ version "7.13.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3"
+ integrity sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==
dependencies:
- "@babel/helper-hoist-variables" "^7.12.13"
- "@babel/helper-module-transforms" "^7.12.13"
- "@babel/helper-plugin-utils" "^7.12.13"
+ "@babel/helper-hoist-variables" "^7.13.0"
+ "@babel/helper-module-transforms" "^7.13.0"
+ "@babel/helper-plugin-utils" "^7.13.0"
"@babel/helper-validator-identifier" "^7.12.11"
babel-plugin-dynamic-import-node "^2.3.3"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
- version "7.13.7"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.7.tgz#d494e39d198ee9ca04f4dcb76d25d9d7a1dc961a"
- integrity sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
+ integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
dependencies:
regenerator-runtime "^0.13.4"
globals "^11.1.0"
lodash "^4.17.19"
-"@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.13", "@babel/types@^7.12.7", "@babel/types@^7.13.0", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6":
- version "7.13.0"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80"
- integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==
+"@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.13", "@babel/types@^7.12.7", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6":
+ version "7.13.12"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.12.tgz#edbf99208ef48852acdff1c8a681a1e4ade580cd"
+ integrity sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
lodash "^4.17.19"
to-fast-properties "^2.0.0"
-"@discoveryjs/json-ext@^0.5.0":
+"@discoveryjs/json-ext@0.5.2", "@discoveryjs/json-ext@^0.5.0":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752"
integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==
dependencies:
tslib "^2.0.0"
-"@ngtools/webpack@11.2.2":
- version "11.2.2"
- resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-11.2.2.tgz#647862ed19761796c7f84d5fb3305661d2a3af67"
- integrity sha512-X1M/Xs0kLi9FrOIU6yJ74q3pCzhgwPQowO1XjJ68KLOoMbj/DM6Qm0Hi9N0Ay8h0s7BIdjKEu/C3pCdGu1Q54w==
+"@ngtools/webpack@11.2.5":
+ version "11.2.5"
+ resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-11.2.5.tgz#3e2265145d19fcdda9ec2894ccded83658b1fa66"
+ integrity sha512-7fhg8hvqTiTS5ESiEN4xR2qRnOVX0rhVSckMXbAFvNYTwQOuS865RiBrYCJ4CsKhGJ9P7XS5i2EIwA3/aLSivg==
dependencies:
- "@angular-devkit/core" "11.2.2"
+ "@angular-devkit/core" "11.2.5"
enhanced-resolve "5.7.0"
webpack-sources "2.2.0"
infer-owner "^1.0.4"
"@npmcli/run-script@^1.3.0":
- version "1.8.3"
- resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.3.tgz#07f440ed492400bb1114369bc37315eeaaae2bb3"
- integrity sha512-ELPGWAVU/xyU+A+H3pEPj0QOvYwLTX71RArXcClFzeiyJ/b/McsZ+d0QxpznvfFtZzxGN/gz/1cvlqICR4/suQ==
+ version "1.8.4"
+ resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.4.tgz#03ced92503a6fe948cbc0975ce39210bc5e824d6"
+ integrity sha512-Yd9HXTtF1JGDXZw0+SOn+mWLYS0e7bHBHVC/2C8yqs4wUrs/k8rwBSinD7rfk+3WG/MFGRZKxjyoD34Pch2E/A==
dependencies:
"@npmcli/node-gyp" "^1.0.2"
"@npmcli/promise-spawn" "^1.3.2"
infer-owner "^1.0.4"
node-gyp "^7.1.0"
- puka "^1.0.1"
read-package-json-fast "^2.0.1"
"@polka/url@^1.0.0-next.9":
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71"
integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==
-"@schematics/angular@11.2.2":
- version "11.2.2"
- resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-11.2.2.tgz#ff69a66b6e1acf5aa36ed0795973f3f57d893d0b"
- integrity sha512-TcxPy58adUnkirGXyZVVSMuKkA0eIz2PWSQWEgB9l7kO+5LvDOn+RMoc6AVx0s/bU9nH+eozBUJ1XAD/E8QnYQ==
+"@schematics/angular@11.2.5":
+ version "11.2.5"
+ resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-11.2.5.tgz#c984687c95be32d3fa6016faa8b5a61715a830f5"
+ integrity sha512-pjaK0gZyqhzgAVxMKElG6cDpAvNZ3adVCTA8dhEixpH+JaQdoczl59hMn7rH75yQW0PApe+8g7HMwVK6bLRmxQ==
dependencies:
- "@angular-devkit/core" "11.2.2"
- "@angular-devkit/schematics" "11.2.2"
+ "@angular-devkit/core" "11.2.5"
+ "@angular-devkit/schematics" "11.2.5"
jsonc-parser "3.0.0"
-"@schematics/update@0.1102.2":
- version "0.1102.2"
- resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.1102.2.tgz#f8aed68bbcefdc8633c7804e47ff891ef06bd5ef"
- integrity sha512-Nz8kjeixzDnOw00bnZznq3qrbIv8yWEWNb9eDkRBqgOUXQwlhKJY/sYBK58JF2D+conaRVuEqMsBlX08GlFtIA==
+"@schematics/update@0.1102.5":
+ version "0.1102.5"
+ resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.1102.5.tgz#538493f0a7d06d794d521cca4f2ff588f05cc733"
+ integrity sha512-iz9pM8mabieqQnPZjrqP5jfRFvPm81/uIg46kY3KjtDtSBi4GAF2dnFyX1dC2mG1rq+e+8zeQLvOvhdLifYlEA==
dependencies:
- "@angular-devkit/core" "11.2.2"
- "@angular-devkit/schematics" "11.2.2"
+ "@angular-devkit/core" "11.2.5"
+ "@angular-devkit/schematics" "11.2.5"
"@yarnpkg/lockfile" "1.1.0"
ini "2.0.0"
npm-package-arg "^8.0.0"
"@types/node" "*"
"@types/chart.js@^2.9.16":
- version "2.9.30"
- resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.30.tgz#34b99897f4f5ef0f74c8fe4ced70ac52b4d752dd"
- integrity sha512-EgjxUUZFvf6ls3kW2CwyrnSJhgyKxgwrlp/W5G9wqyPEO9iFatO63zAA7L24YqgMxiDjQ+tG7ODU+2yWH91lPg==
+ version "2.9.31"
+ resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.31.tgz#e8ebc7ed18eb0e5114c69bd46ef8e0037c89d39d"
+ integrity sha512-hzS6phN/kx3jClk3iYqEHNnYIRSi4RZrIGJ8CDLjgatpHoftCezvC44uqB3o3OUm9ftU1m7sHG8+RLyPTlACrA==
dependencies:
moment "^2.10.2"
integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==
"@types/jasmine@*", "@types/jasmine@^3.3.15":
- version "3.6.4"
- resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.6.4.tgz#22ade1b692d5656f859ef9bc6c62d88632cc27e0"
- integrity sha512-CTdMERA4iGNcxeqzD7pavb4WLIFq6bGnx6nIJD+1D4Knx24GE6QBPrWVhO8UlIy7gf7rbIt3ZD7iIzryRD2TgA==
+ version "3.6.7"
+ resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.6.7.tgz#e762d3ead78538efb7900ab932d7daf334acb0b4"
+ integrity sha512-8dtfiykrpe4Ysn6ONj0tOjmpDIh1vWxPk80eutSeWmyaJvAZXZ84219fS4gLrvz05eidhp7BP17WVQBaXHSyXQ==
"@types/jasminewd2@^2.0.3":
version "2.0.8"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/linkify-it@*":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.0.tgz#c0ca4c253664492dbf47a646f31cfd483a6bbc95"
- integrity sha512-x9OaQQTb1N2hPZ/LWJsqushexDvz7NgzuZxiRmZio44WPuolTZNHDBCrOxCzRVOMwamJRO2dWax5NbygOf1OTQ==
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.1.tgz#4d26a9efe3aa2caf829234ec5a39580fc88b6001"
+ integrity sha512-pQv3Sygwxxh6jYQzXaiyWDAHevJqWtqDUv6t11Sa9CPGiXny66II7Pl6PR8QO5OVysD6HYOkHMeBgIjLnk9SkQ==
"@types/linkifyjs@^2.1.2":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.3.tgz#3159a01a2b21c9155a3d8f85588885d725dc987d"
integrity sha512-13gmo3M2qVvjQrWNseqM3+cR6S2Ss3grbR2NZltgMq94wOwqJYQdgn8qzwDshzgXqMlSUtyPZjysImmktu22ew==
-"@types/node@*", "@types/node@^14.0.14", "@types/node@^14.14.10":
- version "14.14.31"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
- integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
+"@types/node@*", "@types/node@>=10.0.0", "@types/node@^14.0.14":
+ version "14.14.35"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313"
+ integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==
"@types/parse-json@^4.0.0":
version "4.0.0"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
"@types/react@*":
- version "17.0.2"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8"
- integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==
+ version "17.0.3"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
+ integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
dependencies:
"@types/prop-types" "*"
+ "@types/scheduler" "*"
csstype "^3.0.2"
"@types/sanitize-html@1.27.1":
dependencies:
htmlparser2 "^4.1.0"
+"@types/scheduler@*":
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
+ integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
+
"@types/selenium-webdriver@^3.0.0":
version "3.0.17"
resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz#50bea0c3c2acc31c959c5b1e747798b3b3d06d4b"
integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==
"@types/uglify-js@*":
- version "3.12.0"
- resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.12.0.tgz#2bb061c269441620d46b946350c8f16d52ef37c5"
- integrity sha512-sYAF+CF9XZ5cvEBkI7RtrG9g2GtMBkviTnBxYYyq+8BWvO4QtXfwwR6a2LFwCi4evMKZfpv6U43ViYvv17Wz3Q==
+ version "3.13.0"
+ resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.0.tgz#1cad8df1fb0b143c5aba08de5712ea9d1ff71124"
+ integrity sha512-EGkrJD5Uy+Pg0NUR8uA4bJ5WMfljyad0G+784vLCNUkD+QwOJXUbBYExXfVGf7YtyzdQp3L/XMYcliB987kL5Q==
dependencies:
source-map "^0.6.1"
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
acorn@^8.0.4:
- version "8.0.5"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.5.tgz#a3bfb872a74a6a7f661bc81b9849d9cac12601b7"
- integrity sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe"
+ integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==
addr-to-ip-port@^1.0.1, addr-to-ip-port@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
-cacache@15.0.5, cacache@^15.0.5:
+cacache@15.0.5:
version "15.0.5"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0"
integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==
unique-filename "^1.1.1"
y18n "^4.0.0"
+cacache@^15.0.5:
+ version "15.0.6"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.6.tgz#65a8c580fda15b59150fb76bf3f3a8e45d583099"
+ integrity sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==
+ dependencies:
+ "@npmcli/move-file" "^1.0.1"
+ chownr "^2.0.0"
+ fs-minipass "^2.0.0"
+ glob "^7.1.4"
+ infer-owner "^1.0.4"
+ lru-cache "^6.0.0"
+ minipass "^3.1.1"
+ minipass-collect "^1.0.2"
+ minipass-flush "^1.0.5"
+ minipass-pipeline "^1.2.2"
+ mkdirp "^1.0.3"
+ p-map "^4.0.0"
+ promise-inflight "^1.0.1"
+ rimraf "^3.0.2"
+ ssri "^8.0.1"
+ tar "^6.0.2"
+ unique-filename "^1.1.1"
+
cache-base@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001032, caniuse-lite@^1.0.30001181:
- version "1.0.30001192"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001192.tgz#b848ebc0ab230cf313d194a4775a30155d50ae40"
- integrity sha512-63OrUnwJj5T1rUmoyqYTdRWBqFFxZFlyZnRRjDR8NSUQFB6A+j/uBORU/SyJ5WzDLg4SPiZH40hQCBNdZ/jmAw==
+ version "1.0.30001204"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz#256c85709a348ec4d175e847a3b515c66e79f2aa"
+ integrity sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==
canonical-path@1.0.0:
version "1.0.0"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
-chalk@^4.0.0, chalk@^4.1.0:
+chalk@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
tslib "^1.9.0"
chunk-store-stream@^4.1.1:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/chunk-store-stream/-/chunk-store-stream-4.2.0.tgz#18f673c495946c4cdcf14124a3ebd5f31eb0ea35"
- integrity sha512-90iueoPoqT2isnmy1fyqwzgFy5FokuaxQuijOQG1VgC/6DaXRfeYN0da8iWENkzqElWhqLxo8pWc7pH9dmxlcA==
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/chunk-store-stream/-/chunk-store-stream-4.3.0.tgz#3de5f4dfe19729366c29bb7ed52d139f9af29f0e"
+ integrity sha512-qby+/RXoiMoTVtPiylWZt7KFF1jy6M829TzMi2hxZtBIH9ptV19wxcft6zGiXLokJgCbuZPGNGab6DWHqiSEKw==
dependencies:
block-stream2 "^2.0.0"
readable-stream "^3.6.0"
restore-cursor "^3.1.0"
cli-spinners@^2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.5.0.tgz#12763e47251bf951cb75c201dfa58ff1bcb2d047"
- integrity sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939"
+ integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==
cli-width@^2.0.0:
version "2.2.1"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.5.4:
- version "1.5.4"
- resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6"
- integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014"
+ integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
color-convert "^1.9.1"
color-string "^1.5.4"
-colorette@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
- integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
+colorette@^1.2.1, colorette@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
+ integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
colors@1.4.0, colors@^1.4.0:
version "1.4.0"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
commander@^7.0.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff"
- integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
+ integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
commondir@^1.0.1:
version "1.0.1"
webpack-sources "^1.4.3"
core-js-compat@^3.8.0:
- version "3.9.0"
- resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.0.tgz#29da39385f16b71e1915565aa0385c4e0963ad56"
- integrity sha512-YK6fwFjCOKWwGnjFUR3c544YsnA/7DoLL0ysncuOJ4pwbriAtOpvM2bygdlcXbvQCQZ7bBU9CL4t7tGl7ETRpQ==
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.1.tgz#4e572acfe90aff69d76d8c37759d21a5c59bb455"
+ integrity sha512-jXAirMQxrkbiiLsCx9bQPJFA6llDadKMpYrBJQJ3/c4/vsPP/fAf29h24tviRlvwUL6AmY5CHLu2GvjuYviQqA==
dependencies:
browserslist "^4.16.3"
semver "7.0.0"
integrity sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==
core-js@^3.1.4:
- version "3.9.0"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.0.tgz#790b1bb11553a2272b36e2625c7179db345492f8"
- integrity sha512-PyFBJaLq93FlyYdsndE5VaueA9K5cNB7CGzeCj191YYLhkQM0gdZR2SKihM70oF0wdqKSKClv/tEBOpoRmdOVQ==
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae"
+ integrity sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
semver "^7.3.2"
css-loader@^5.0.1:
- version "5.0.2"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.2.tgz#24f758dae349bad0a440c50d7e2067742e0899cb"
- integrity sha512-gbkBigdcHbmNvZ1Cg6aV6qh6k9N6XOr8YWzISLQGrwk2mgOH8LLrizhkxbDhQtaLtktyKHD4970S0xwz5btfTA==
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.3.tgz#87f6fc96816b20debe3cf682f85c7e56a963d0d1"
+ integrity sha512-CoPZvyh8sLiGARK3gqczpfdedbM74klGWurF2CsNZ2lhNaXdLIUks+3Mfax3WBeRuHoglU+m7KG/+7gY6G4aag==
dependencies:
camelcase "^6.2.0"
cssesc "^3.0.0"
icss-utils "^5.1.0"
loader-utils "^2.0.0"
- postcss "^8.2.4"
+ postcss "^8.2.8"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
detect-node@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
- integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79"
+ integrity sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==
dexie@^3.0.0:
version "3.0.3"
domelementtype "1"
domutils@^2.0.0, domutils@^2.4.4:
- version "2.4.4"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
- integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.0.tgz#42f49cffdabb92ad243278b331fd761c1c2d3039"
+ integrity sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.0.1"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.649:
- version "1.3.673"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.673.tgz#b4f81c930b388f962b7eba20d0483299aaa40913"
- integrity sha512-ms+QR2ckfrrpEAjXweLx6kNCbpAl66DcW//3BZD4BV5KhUgr0RZRce1ON/9J3QyA3JO28nzgb5Xv8DnPr05ILg==
+ version "1.3.695"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.695.tgz#955f419cf99137226180cc4cca2e59015a4e248d"
+ integrity sha512-lz66RliUqLHU1Ojxx1A4QUxKydjiQ79Y4dZyPobs2Dmxj5aVL2TM3KoQ2Gs7HS703Bfny+ukI3KOxwAB0xceHQ==
elliptic@^6.5.3:
version "6.5.4"
once "^1.4.0"
engine.io-client@~4.1.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-4.1.1.tgz#109942705079f15a4fcf1090bc86d3a1341c0a61"
- integrity sha512-iYasV/EttP/2pLrdowe9G3zwlNIFhwny8VSIh+vPlMnYZqSzLsTzSLa9hFy015OrH1s4fzoYxeHjVkO8hSFKwg==
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-4.1.2.tgz#823b4f005360321c41445fc23ce8ee028ef2e36b"
+ integrity sha512-1mwvwKYMa0AaCy+sPgvJ/SnKyO5MJZ1HEeXfA3Rm/KHkHGiYD5bQVq8QzvIrkI01FuVtOdZC5lWdRw1BGXB2NQ==
dependencies:
base64-arraybuffer "0.1.4"
component-emitter "~1.3.0"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
env-paths@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
- integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
+ integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
envinfo@^7.7.3:
version "7.7.4"
dependencies:
is-arrayish "^0.2.1"
-es-abstract@^1.17.2:
- version "1.17.7"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
- integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
- dependencies:
- es-to-primitive "^1.2.1"
- function-bind "^1.1.1"
- has "^1.0.3"
- has-symbols "^1.0.1"
- is-callable "^1.2.2"
- is-regex "^1.1.1"
- object-inspect "^1.8.0"
- object-keys "^1.1.1"
- object.assign "^4.1.1"
- string.prototype.trimend "^1.0.1"
- string.prototype.trimstart "^1.0.1"
-
-es-abstract@^1.18.0-next.2:
- version "1.18.0-next.2"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.2.tgz#088101a55f0541f595e7e057199e27ddc8f3a5c2"
- integrity sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==
+es-abstract@^1.17.2, es-abstract@^1.18.0-next.2:
+ version "1.18.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4"
+ integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==
dependencies:
call-bind "^1.0.2"
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
- get-intrinsic "^1.0.2"
+ get-intrinsic "^1.1.1"
has "^1.0.3"
- has-symbols "^1.0.1"
- is-callable "^1.2.2"
+ has-symbols "^1.0.2"
+ is-callable "^1.2.3"
is-negative-zero "^2.0.1"
- is-regex "^1.1.1"
+ is-regex "^1.1.2"
+ is-string "^1.0.5"
object-inspect "^1.9.0"
object-keys "^1.1.1"
object.assign "^4.1.2"
- string.prototype.trimend "^1.0.3"
- string.prototype.trimstart "^1.0.3"
+ string.prototype.trimend "^1.0.4"
+ string.prototype.trimstart "^1.0.4"
+ unbox-primitive "^1.0.0"
es-to-primitive@^1.2.1:
version "1.2.1"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
events@^3.0.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
- integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
+ integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
eventsource@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"
- integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.0.tgz#00e8ca7c92109e94b0ddf32dac677d841028cfaf"
+ integrity sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==
dependencies:
original "^1.0.0"
integrity sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==
follow-redirects@^1.0.0:
- version "1.13.2"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147"
- integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==
+ version "1.13.3"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
+ integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-get-intrinsic@^1.0.2:
+get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
path-dirname "^1.0.0"
glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
- integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
globby@^11.0.1:
- version "11.0.2"
- resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83"
- integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==
+ version "11.0.3"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb"
+ integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==
dependencies:
array-union "^2.1.0"
dir-glob "^3.0.1"
dependencies:
ansi-regex "^2.0.0"
+has-bigints@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
+ integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
+
has-cors@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-has-symbols@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
- integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
+ integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
has-unicode@^2.0.0:
version "2.0.1"
dependencies:
lru-cache "^6.0.0"
+hosted-git-info@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.1.tgz#710ef5452ea429a844abc33c981056e7371edab7"
+ integrity sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==
+ dependencies:
+ lru-cache "^6.0.0"
+
hpack.js@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
entities "^2.0.0"
htmlparser2@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.0.tgz#c2da005030390908ca4c91e5629e418e0665ac01"
- integrity sha512-numTQtDZMoh78zJpaNdJ9MXb2cv5G3jwUoe3dMQODubZvLoGvTE/Ofp6sHvH8OGKcN/8A47pGLi/k58xHP/Tfw==
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.1.tgz#422521231ef6d42e56bd411da8ba40aa36e91446"
+ integrity sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
resolved "https://registry.yarnpkg.com/is-ascii/-/is-ascii-1.0.0.tgz#f02ad0259a0921cd199ff21ce1b09e0f6b4e3929"
integrity sha1-8CrQJZoJIc0Zn/Ic4bCeD2tOOSk=
+is-bigint@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2"
+ integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==
+
is-binary-path@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
dependencies:
binary-extensions "^2.0.0"
+is-boolean-object@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0"
+ integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==
+ dependencies:
+ call-bind "^1.0.0"
+
is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
-is-callable@^1.1.4, is-callable@^1.2.2:
+is-callable@^1.1.4, is-callable@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
+is-number-object@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
+ integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
+
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=
-is-regex@^1.0.4, is-regex@^1.1.1:
+is-regex@^1.0.4, is-regex@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
+is-string@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
+ integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
+
is-svg@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
dependencies:
html-comment-regex "^1.1.0"
-is-symbol@^1.0.2:
+is-symbol@^1.0.2, is-symbol@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
+is-unicode-supported@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
+ integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
+
is-what@^3.12.0:
version "3.14.1"
resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1"
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
-jasmine-core@^3.6.0, jasmine-core@~3.6.0:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20"
- integrity sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==
+jasmine-core@^3.6.0:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.7.1.tgz#0401327f6249eac993d47bbfa18d4e8efacfb561"
+ integrity sha512-DH3oYDS/AUvvr22+xUBW62m1Xoy7tUlY1tsxKEJvl5JeJ7q8zd1K5bUwiOxdH+erj6l2vAMM3hV25Xs9/WrmuQ==
jasmine-core@~2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
integrity sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=
+jasmine-core@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20"
+ integrity sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==
+
jasmine-spec-reporter@~6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-6.0.0.tgz#3b9c85689676a351f343ba8dd6d3957f11a4bf1d"
source-map-support "^0.5.5"
karma@~6.1.0:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/karma/-/karma-6.1.1.tgz#a7539618cca0f2cbb26d5497120ec31fe340c2a1"
- integrity sha512-vVDFxFGAsclgmFjZA/qGw5xqWdZIWxVD7xLyCukYUYd5xs/uGzYbXGOT5zOruVBQleKEmXIr4H2hzGCTn+M9Cg==
+ version "6.1.2"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-6.1.2.tgz#9d7394559f5deb150b3021c1860960281c3a0e50"
+ integrity sha512-mKbxgsJrt3UHBPdKfCxC2eg3lpqyt6hQRFhNWJ2sk0wUnbnLPEiCpgIgiycuLSra0vC6TaK9OPJiMGATGzgH/A==
dependencies:
body-parser "^1.19.0"
braces "^3.0.2"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
log-symbols@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
- integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
+ integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
dependencies:
- chalk "^4.0.0"
+ chalk "^4.1.0"
+ is-unicode-supported "^0.1.0"
log4js@^6.2.1:
version "6.3.0"
global "^4.3.2"
m3u8-parser@^4.4.0:
- version "4.5.2"
- resolved "https://registry.yarnpkg.com/m3u8-parser/-/m3u8-parser-4.5.2.tgz#f7d48a60112466e528324624c4e66d52ed341a75"
- integrity sha512-sN/lu3TiRxmG2RFjZxo5c0/7Dr4RrEztl43jXrWwj5gFZ7vfa2iIxGfiPx485dm5QCazaIcKk+vNkUso8Aq0Ag==
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/m3u8-parser/-/m3u8-parser-4.6.0.tgz#a0e2f5dcf8391c9a6e59895a084fa38f27b52124"
+ integrity sha512-dKhhpMcPqDM/KzULVrNyDZ/z766peQjwUghDTcl6TE7DQKAt/vm74/IMUAxpO34f6LDpM+OH/dYGQwW1eM4yWw==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "^3.0.0"
webpack-sources "^1.1.0"
mini-css-extract-plugin@^1.3.1:
- version "1.3.8"
- resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.8.tgz#639047b78c2ee728704285aa468d2a5a8d91d566"
- integrity sha512-u+2kVov/Gcs74iz+x3phEBWMAGw2djjnKfYez+Pl/b5dyXL7aM4Lp5QQtIq16CDwRHT/woUJki49gBNMhfm1eA==
+ version "1.3.9"
+ resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.9.tgz#47a32132b0fd97a119acd530e8421e8f6ab16d5e"
+ integrity sha512-Ac4s+xhVbqlyhXS5J/Vh/QXUz3ycXlCqoCPpg0vdfhsIBH9eg/It/9L1r1XhSCH737M1lqcWnMuWL13zcygn5A==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nanoid@^3.1.20:
- version "3.1.20"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
- integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
+ version "3.1.22"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
+ integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==
nanomatch@^1.2.9:
version "1.2.13"
semver "^7.0.0"
validate-npm-package-name "^3.0.0"
-npm-package-arg@^8.0.0, npm-package-arg@^8.0.1:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.1.tgz#00ebf16ac395c63318e67ce66780a06db6df1b04"
- integrity sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg==
+npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.2:
+ version "8.1.2"
+ resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.2.tgz#b868016ae7de5619e729993fbd8d11dc3c52ab62"
+ integrity sha512-6Eem455JsSMJY6Kpd3EyWE+n5hC+g9bSyHr9K9U2zqZb7+02+hObQ2c0+8iDk/mNF+8r1MhY44WypKJAkySIYA==
dependencies:
- hosted-git-info "^3.0.6"
- semver "^7.0.0"
+ hosted-git-info "^4.0.1"
+ semver "^7.3.4"
validate-npm-package-name "^3.0.0"
npm-packlist@^2.1.4:
npm-bundled "^1.1.1"
npm-normalize-package-bin "^1.0.1"
-npm-pick-manifest@6.1.0, npm-pick-manifest@^6.0.0:
+npm-pick-manifest@6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.0.tgz#2befed87b0fce956790f62d32afb56d7539c022a"
integrity sha512-ygs4k6f54ZxJXrzT0x34NybRlLeZ4+6nECAIbr2i0foTnijtS1TJiyzpqtuUAJOps/hO0tNDr8fRV5g+BtRlTw==
npm-package-arg "^8.0.0"
semver "^7.0.0"
+npm-pick-manifest@^6.0.0:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148"
+ integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==
+ dependencies:
+ npm-install-checks "^4.0.0"
+ npm-normalize-package-bin "^1.0.1"
+ npm-package-arg "^8.1.2"
+ semver "^7.3.4"
+
npm-registry-fetch@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz#86f3feb4ce00313bc0b8f1f8f69daae6face1661"
define-property "^0.2.5"
kind-of "^3.0.3"
-object-inspect@^1.8.0, object-inspect@^1.9.0:
+object-inspect@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
dependencies:
isobject "^3.0.0"
-object.assign@^4.1.0, object.assign@^4.1.1, object.assign@^4.1.2:
+object.assign@^4.1.0, object.assign@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
source-map "^0.6.1"
supports-color "^6.1.0"
-postcss@^8.0.2, postcss@^8.1.4, postcss@^8.2.4:
- version "8.2.6"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe"
- integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg==
+postcss@^8.0.2, postcss@^8.1.4, postcss@^8.2.8:
+ version "8.2.8"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
+ integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
dependencies:
- colorette "^1.2.1"
+ colorette "^1.2.2"
nanoid "^3.1.20"
source-map "^0.6.1"
renderkid "^2.0.4"
primeng@^11.0.0-rc.1:
- version "11.2.3"
- resolved "https://registry.yarnpkg.com/primeng/-/primeng-11.2.3.tgz#66e3d817fe27c9a7703726537c03ddcc1998bb44"
- integrity sha512-8elRAGal8a+qXJ4egRKXU+bUvIyfCxsiCerXgOPbwbo/TU/DBK7WBXGGGi6KJOamFqClAqj/FO3WLAdofKQSRQ==
+ version "11.3.1"
+ resolved "https://registry.yarnpkg.com/primeng/-/primeng-11.3.1.tgz#644dd59d1f0808227a9529ea6ffaad31bdb5e5df"
+ integrity sha512-B86/su/3sNP2GfhyegvZh2MpHcUZHas+13bPL98QmZhoiPBQp2jz3H0iD716+piC00Wee6pi/PPm7e9y9qxGDg==
dependencies:
tslib "^2.0.0"
randombytes "^2.0.1"
safe-buffer "^5.1.2"
-puka@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/puka/-/puka-1.0.1.tgz#a2df782b7eb4cf9564e4c93a5da422de0dfacc02"
- integrity sha512-ssjRZxBd7BT3dte1RR3VoeT2cT/ODH8x+h0rUF1rMqB0srHYf48stSDWfiYakTp5UBZMxroZhB2+ExLDHm7W3g==
-
pump@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
-queue-microtask@^1.1.2, queue-microtask@^1.2.0, queue-microtask@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3"
- integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==
+queue-microtask@^1.2.0, queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
random-access-file@^2.0.1:
- version "2.1.5"
- resolved "https://registry.yarnpkg.com/random-access-file/-/random-access-file-2.1.5.tgz#27af6115b920a9adabb44559e29ea9944bb35bfe"
- integrity sha512-lqmUGgF9X+LD0XSeWSHcs7U2nSLYp+RQvkDDqKWoxW8jcd13tZ00G6PHV32OZqDIHmS9ewoEUEa6jcvyB7UCvg==
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/random-access-file/-/random-access-file-2.2.0.tgz#b49b999efefb374afb7587f219071fec5ce66546"
+ integrity sha512-B744003Mj7v3EcuPl9hCiB2Ot4aZjgtU2mV6yFY1THiWU/XfGf1uSadR+SlQdJcwHgAWeG7Lbos0aUqjtj8FQg==
dependencies:
mkdirp-classic "^0.5.2"
random-access-storage "^1.1.1"
integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
regjsparser@^0.6.4:
- version "0.6.7"
- resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.7.tgz#c00164e1e6713c2e3ee641f1701c4b7aa0a7f86c"
- integrity sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ==
+ version "0.6.8"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.8.tgz#4532c3da36d75d56e3f394ce2ea6842bde7496bd"
+ integrity sha512-3weFrFQREJhJ2PW+iCGaG6TenyzNSZgsBKZ/oEf6Trme31COSeIWhHw9O6FPkuXktfx+b6Hf/5e6dKPHaROq2g==
dependencies:
jsesc "~0.5.0"
css "^2.0.0"
rfdc@^1.1.4:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.2.0.tgz#9e9894258f48f284b43c3143c68070a4f373b949"
- integrity sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA==
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
+ integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
rgb-regex@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.9.tgz#15ba9cb90e6a6c054e67c98e1dc063df0ecc113a"
integrity sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==
-rusha@^0.8.1:
+rusha@^0.8.13:
version "0.8.13"
resolved "https://registry.yarnpkg.com/rusha/-/rusha-0.8.13.tgz#9a084e7b860b17bff3015b92c67a6a336191513a"
integrity sha1-mghOe4YLF7/zAVuSxnpqM2GRUTo=
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sanitize-html@^2.1.2:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.3.2.tgz#a1954aea877a096c408aca7b0c260bef6e4fc402"
- integrity sha512-p7neuskvC8pSurUjdVmbWPXmc9A4+QpOXIL+4gwFC+av5h+lYCXFT8uEneqsFQg/wEA1IH+cKQA60AaQI6p3cg==
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.3.3.tgz#3db382c9a621cce4c46d90f10c64f1e9da9e8353"
+ integrity sha512-DCFXPt7Di0c6JUnlT90eIgrjs6TsJl/8HYU3KLdmrVclFN4O0heTcVbJiMa23OKVr6aR051XYtsgd8EWwEBwUA==
dependencies:
deepmerge "^4.2.2"
escape-string-regexp "^4.0.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
-semver@7.3.4, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4:
+semver@7.3.4:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4:
+ version "7.3.5"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+ integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+ dependencies:
+ lru-cache "^6.0.0"
+
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
simple-concat "^1.0.0"
simple-peer@^9.5.0, simple-peer@^9.7.1, simple-peer@^9.9.3:
- version "9.9.3"
- resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.9.3.tgz#b52c39d1173620d06c8b29ada7ee2ad3384bb469"
- integrity sha512-T3wuv0UqBpDTV0x0pJPPsz4thy0tC0fTOHE4g9+AF43RUxxT+MWeXVtdQcK5Xuzv/XTVrB2NrGzdfO1IFBqOkw==
+ version "9.10.0"
+ resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.10.0.tgz#f458444300f635e6fcc2f5a5166c45d71eafb57f"
+ integrity sha512-sKrKtca1UdmwdZIbvuT3iEL05tDGt/xdLP6+ej8rh1ADgtDk44yLaEZjIyPJ6c34zsSih46Ou7zUIT7e4hPK7g==
dependencies:
buffer "^6.0.2"
debug "^4.2.0"
readable-stream "^3.6.0"
simple-sha1@^3.0.0, simple-sha1@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/simple-sha1/-/simple-sha1-3.0.1.tgz#b34c3c978d74ac4baf99b6555c1e6736e0d6e700"
- integrity sha512-q7ehqWfHc1VhOm7sW099YDZ4I0yYX7rqyhqqhHV1IYeUTjPOhHyD3mXvv8k2P+rO7+7c8R4/D+8ffzC9BE7Cqg==
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/simple-sha1/-/simple-sha1-3.1.0.tgz#40cac8436dfaf9924332fc46a5c7bca45f656131"
+ integrity sha512-ArTptMRC1v08H8ihPD6l0wesKvMfF9e8XL5rIHPanI7kGOsSsbY514MwVu6X1PITHCTB2F08zB7cyEbfc4wQjg==
dependencies:
- queue-microtask "^1.1.2"
- rusha "^0.8.1"
+ queue-microtask "^1.2.2"
+ rusha "^0.8.13"
simple-swizzle@^0.2.2:
version "0.2.2"
integrity sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==
socket.io-client@^3.0.3:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-3.1.1.tgz#43dfc3feddbb675b274a724f685d6b6af319b3e3"
- integrity sha512-BLgIuCjI7Sf3mDHunKddX9zKR/pbkP7IACM3sJS3jha+zJ6/pGKRV6Fz5XSBHCfUs9YzT8kYIqNwOOuFNLtnYA==
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-3.1.3.tgz#57ddcefea58cfab71f0e94c21124de8e3c5aa3e2"
+ integrity sha512-4sIGOGOmCg3AOgGi7EEr6ZkTZRkrXwub70bBB/F0JSkMOUFpA77WsL87o34DffQQ31PkbMUIadGOk+3tx1KGbw==
dependencies:
"@types/component-emitter" "^1.2.10"
backo2 "~1.0.2"
debug "~4.3.1"
socket.io@^3.1.0:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.1.tgz#905e3d4a3b37d8e7970e67a4a6eb81110a5778ba"
- integrity sha512-7cBWdsDC7bbyEF6WbBqffjizc/H4YF1wLdZoOzuYfo2uMNSFjJKuQ36t0H40o9B20DO6p+mSytEd92oP4S15bA==
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.2.tgz#06e27caa1c4fc9617547acfbb5da9bc1747da39a"
+ integrity sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==
dependencies:
"@types/cookie" "^0.4.0"
"@types/cors" "^2.8.8"
- "@types/node" "^14.14.10"
+ "@types/node" ">=10.0.0"
accepts "~1.3.4"
base64id "~2.0.0"
debug "~4.3.1"
socks "^2.3.3"
socks@^2.3.3:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/socks/-/socks-2.5.1.tgz#7720640b6b5ec9a07d556419203baa3f0596df5f"
- integrity sha512-oZCsJJxapULAYJaEYBSzMcz8m3jqgGrHaGhkmU/o/PQfFWYWxkAaA0UMGImb6s6tEXfKi959X6VJjMMQ3P6TTQ==
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.0.tgz#6b984928461d39871b3666754b9000ecf39dfac2"
+ integrity sha512-mNmr9owlinMplev0Wd7UHFlqI4ofnBnNzFuzrm63PPaHgbkqCFe4T5LzwKmtQ/f2tX0NTpcdVLyD/FHxFBstYw==
dependencies:
ip "^1.1.5"
smart-buffer "^4.1.0"
dependencies:
figgy-pudding "^3.5.1"
-ssri@^8.0.0:
+ssri@^8.0.0, ssri@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af"
integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==
strip-ansi "^5.1.0"
string-width@^4.1.0, string-width@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
- integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
+ integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
-string.prototype.trimend@^1.0.1, string.prototype.trimend@^1.0.3:
+string.prototype.trimend@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
call-bind "^1.0.2"
define-properties "^1.1.3"
-string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.3:
+string.prototype.trimstart@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
source-map-support "~0.5.12"
terser@^5.3.4:
- version "5.6.0"
- resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2"
- integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA==
+ version "5.6.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.1.tgz#a48eeac5300c0a09b36854bf90d9c26fb201973c"
+ integrity sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==
dependencies:
commander "^2.20.0"
source-map "~0.7.2"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
ts-loader@^8.0.14:
- version "8.0.17"
- resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.17.tgz#98f2ccff9130074f4079fd89b946b4c637b1f2fc"
- integrity sha512-OeVfSshx6ot/TCxRwpBHQ/4lRzfgyTkvi7ghDVrLXOHzTbSK413ROgu/xNqM72i3AFeAIJgQy78FwSMKmOW68w==
+ version "8.0.18"
+ resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.18.tgz#b2385cbe81c34ad9f997915129cdde3ad92a61ea"
+ integrity sha512-hRZzkydPX30XkLaQwJTDcWDoxZHK6IrEMDQpNd7tgcakFruFkeUp/aY+9hBb7BUGb+ZWKI0jiOGMo0MckwzdDQ==
dependencies:
chalk "^4.1.0"
enhanced-resolve "^4.0.0"
tslib "^1.8.1"
tsutils@^3.0.0:
- version "3.20.0"
- resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.20.0.tgz#ea03ea45462e146b53d70ce0893de453ff24f698"
- integrity sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==
+ version "3.21.0"
+ resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
+ integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
dependencies:
tslib "^1.8.1"
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
type@^2.0.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/type/-/type-2.3.0.tgz#ada7c045f07ead08abf9e2edd29be1a0c0661132"
- integrity sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg==
+ 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.0.0:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typescript@4.1.3:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
- integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
-
-typescript@~4.1.3:
+typescript@4.1.5, typescript@~4.1.3:
version "4.1.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72"
integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
uglify-js@^3.0.6:
- version "3.12.8"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.8.tgz#a82e6e53c9be14f7382de3d068ef1e26e7d4aaf8"
- integrity sha512-fvBeuXOsvqjecUtF/l1dwsrrf5y2BCUk9AOJGzGcm6tE7vegku5u/YvqjyDaAGr422PLoLnrxg3EnRvTqsdC1w==
+ version "3.13.2"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.2.tgz#fe10319861bccc8682bfe2e8151fbdd8aa921c44"
+ integrity sha512-SbMu4D2Vo95LMC/MetNaso1194M1htEA+JrqE9Hk+G2DhI+itfS9TRu9ZKeCahLDNa/J3n4MqUJ/fOHMzQpRWw==
uint64be@^2.0.2:
version "2.0.2"
dependencies:
buffer-alloc "^1.1.0"
+unbox-primitive@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f"
+ integrity sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA==
+ dependencies:
+ function-bind "^1.1.1"
+ has-bigints "^1.0.0"
+ has-symbols "^1.0.0"
+ which-boxed-primitive "^1.0.1"
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
v8-compile-cache@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
- integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
+ integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
validate-npm-package-license@^3.0.1:
version "3.0.4"
utp-native "^2.3.0"
whatwg-fetch@^3.0.0:
- version "3.6.1"
- resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.1.tgz#93bc4005af6c2cc30ba3e42ec3125947c8f54ed3"
- integrity sha512-IEmN/ZfmMw6G1hgZpVd0LuZXOQDisrMOZrzYd5x3RAK4bMPlJohKUZWZ9t/QsTvH0dV9TbPDcc2OSuIDcihnHA==
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
+ integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==
whatwg-mimetype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
+which-boxed-primitive@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
+ integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
+ dependencies:
+ is-bigint "^1.0.1"
+ is-boolean-object "^1.1.0"
+ is-number-object "^1.0.4"
+ is-string "^1.0.5"
+ is-symbol "^1.0.3"
+
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
async-limiter "~1.0.0"
ws@^7.3.0, ws@^7.3.1, ws@^7.4.2, ws@~7.4.2:
- version "7.4.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz#1f9643de34a543b8edb124bdcbc457ae55a6e5cd"
- integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59"
+ integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==
xml2js@^0.4.17:
version "0.4.23"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.10.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
- integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
+ integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^13.1.2:
version "13.1.2"
decamelize "^1.2.0"
yargs-parser@^20.2.2:
- version "20.2.6"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.6.tgz#69f920addf61aafc0b8b89002f5d66e28f2d8b20"
- integrity sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==
+ version "20.2.7"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
+ integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
yargs-parser@^7.0.0:
version "7.0.0"
# We still suggest you to enable this setting even if your users will loose most of their video's likes/dislikes
cleanup_remote_interactions: false
+peertube:
+ check_latest_version:
+ # Check and notify admins of new PeerTube versions
+ enabled: true
+ # You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
+ url: 'https://joinpeertube.org/api/v1/versions.json'
+
cache:
previews:
size: 500 # Max number of previews you want to cache
# We still suggest you to enable this setting even if your users will loose most of their video's likes/dislikes
cleanup_remote_interactions: false
+peertube:
+ check_latest_version:
+ # Check and notify admins of new PeerTube versions
+ enabled: true
+ # You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
+ url: 'https://joinpeertube.org/api/v1/versions.json'
###############################################################################
#
contact_form:
enabled: true
+peertube:
+ check_latest_version:
+ enabled: false
+
redundancy:
videos:
check_interval: '1 minute'
"start:server": "node dist/server --no-client",
"update-host": "node ./dist/scripts/update-host.js",
"create-transcoding-job": "node ./dist/scripts/create-transcoding-job.js",
+ "regenerate-thumbnails": "node ./dist/scripts/regenerate-thumbnails.js",
"create-import-video-file-job": "node ./dist/scripts/create-import-video-file-job.js",
"print-transcode-command": "node ./dist/scripts/print-transcode-command.js",
"test": "scripty",
"swagger-cli": "swagger-cli",
"sass-lint": "sass-lint"
},
- "resolutions": {
- "oauth2-server": "3.1.0-beta.1",
- "http-signature": "1.3.5"
- },
"dependencies": {
"apicache": "1.6.2",
"async": "^3.0.1",
"deep-object-diff": "^1.1.0",
"email-templates": "^8.0.3",
"express": "^4.12.4",
- "express-oauth-server": "^2.0.0",
"express-rate-limit": "^5.0.0",
"express-validator": "^6.4.0",
"flat": "^5.0.0",
"fluent-ffmpeg": "^2.1.0",
"fs-extra": "^9.0.0",
+ "got": "^11.8.2",
"helmet": "^4.1.0",
"http-signature": "1.3.5",
"ip-anonymize": "^0.1.0",
"multer": "^1.1.0",
"node-media-server": "^2.1.4",
"nodemailer": "^6.0.0",
- "oauth2-server": "3.1.0-beta.1",
+ "oauth2-server": "3.1.1",
"parse-torrent": "^9.1.0",
"password-generator": "^2.0.2",
"pem": "^1.12.3",
"pug": "^3.0.0",
"redis": "^3.0.2",
"reflect-metadata": "^0.1.12",
- "request": "^2.81.0",
"sanitize-html": "2.x",
"scripty": "^2.0.0",
"sequelize": "6.5.0",
program
.option('-l, --level [level]', 'Level log (debug/info/warn/error)')
.option('-f, --files [file...]', 'Files to parse. If not provided, the script will parse the latest log file from config)')
+ .option('-t, --tags [tags...]', 'Display only lines with these tags')
+ .option('-nt, --not-tags [tags...]', 'Donrt display lines containing these tags')
.parse(process.argv)
const options = program.opts()
message: true,
splat: true,
timestamp: true,
+ tags: true,
label: true,
sql: true
}
rl.on('line', line => {
try {
const log = JSON.parse(line)
+ if (options.tags && !containsTags(log.tags, options.tags)) {
+ return
+ }
+
+ if (options.notTags && containsTags(log.tags, options.notTags)) {
+ return
+ }
+
// Don't know why but loggerFormat does not remove splat key
Object.assign(log, { splat: undefined })
return new Date(timestamp).toISOString()
}
+
+function containsTags (loggerTags: string[], optionsTags: string[]) {
+ if (!loggerTags) return false
+
+ for (const lt of loggerTags) {
+ for (const ot of optionsTags) {
+ if (lt === ot) return true
+ }
+ }
+
+ return false
+}
--- /dev/null
+import { registerTSPaths } from '../server/helpers/register-ts-paths'
+registerTSPaths()
+
+import * as Bluebird from 'bluebird'
+import * as program from 'commander'
+import { pathExists } from 'fs-extra'
+import { processImage } from '@server/helpers/image-utils'
+import { THUMBNAILS_SIZE } from '@server/initializers/constants'
+import { VideoModel } from '@server/models/video/video'
+import { MVideo } from '@server/types/models'
+import { initDatabaseModels } from '@server/initializers/database'
+
+program
+ .description('Regenerate local thumbnails using preview files')
+ .parse(process.argv)
+
+run()
+ .then(() => process.exit(0))
+ .catch(err => console.error(err))
+
+async function run () {
+ await initDatabaseModels(true)
+
+ const videos = await VideoModel.listLocal()
+
+ await Bluebird.map(videos, v => {
+ return processVideo(v)
+ .catch(err => console.error('Cannot process video %s.', v.url, err))
+ }, { concurrency: 20 })
+}
+
+async function processVideo (videoArg: MVideo) {
+ const video = await VideoModel.loadWithFiles(videoArg.id)
+
+ const thumbnail = video.getMiniature()
+ const preview = video.getPreview()
+
+ const thumbnailPath = thumbnail.getPath()
+ const previewPath = preview.getPath()
+
+ if (!await pathExists(thumbnailPath)) {
+ throw new Error(`Thumbnail ${thumbnailPath} does not exist on disk`)
+ }
+
+ if (!await pathExists(previewPath)) {
+ throw new Error(`Preview ${previewPath} does not exist on disk`)
+ }
+
+ const size = {
+ width: THUMBNAILS_SIZE.width,
+ height: THUMBNAILS_SIZE.height
+ }
+ await processImage(previewPath, thumbnailPath, size, true)
+}
checkNodeVersion()
-import { checkConfig, checkActivityPubUrls } from './server/initializers/checker-after-init'
+import { checkConfig, checkActivityPubUrls, checkFFmpegVersion } from './server/initializers/checker-after-init'
const errorMessage = checkConfig()
if (errorMessage !== null) {
import { PeerTubeSocket } from './server/lib/peertube-socket'
import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
+import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler'
import { Hooks } from './server/lib/plugins/hooks'
import { PluginManager } from './server/lib/plugins/plugin-manager'
import { LiveManager } from './server/lib/live-manager'
return req.get('user-agent')
})
app.use(morgan('combined', {
- stream: { write: logger.info.bind(logger) },
+ stream: {
+ write: (str: string) => logger.info(str, { tags: [ 'http' ] })
+ },
skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
}))
process.exit(-1)
})
+ checkFFmpegVersion()
+ .catch(err => logger.error('Cannot check ffmpeg version', { err }))
+
// Email initialization
Emailer.Instance.init()
RemoveOldHistoryScheduler.Instance.enable()
RemoveOldViewsScheduler.Instance.enable()
PluginsCheckScheduler.Instance.enable()
+ PeerTubeVersionCheckScheduler.Instance.enable()
AutoFollowIndexInstances.Instance.enable()
// Redis initialization
authenticate,
ensureUserHasRight,
jobsSortValidator,
+ paginationValidatorBuilder,
setDefaultPagination,
setDefaultSort
} from '../../middlewares'
-import { paginationValidator } from '../../middlewares/validators'
import { listJobsValidator } from '../../middlewares/validators/jobs'
const jobsRouter = express.Router()
jobsRouter.get('/:state?',
authenticate,
ensureUserHasRight(UserRight.MANAGE_JOBS),
- paginationValidator,
+ paginationValidatorBuilder([ 'jobs' ]),
jobsSortValidator,
setDefaultSort,
setDefaultPagination,
if (!resultList) {
return res.status(HttpStatusCode.SERVICE_UNAVAILABLE_503)
.json({ error: 'Plugin index unavailable. Please retry later' })
- .end()
}
return res.json(resultList)
import * as express from 'express'
import { sanitizeUrl } from '@server/helpers/core-utils'
-import { doRequest } from '@server/helpers/requests'
+import { doJSONRequest } from '@server/helpers/requests'
import { CONFIG } from '@server/initializers/config'
import { getOrCreateVideoAndAccountAndChannel } from '@server/lib/activitypub/videos'
+import { Hooks } from '@server/lib/plugins/hooks'
import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
import { getServerActor } from '@server/models/application/application'
import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
paginationValidator,
setDefaultPagination,
setDefaultSearchSort,
- videoChannelsSearchSortValidator,
videoChannelsListSearchValidator,
+ videoChannelsSearchSortValidator,
videosSearchSortValidator,
videosSearchValidator
} from '../../middlewares'
async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: express.Response) {
const result = await buildMutedForSearchIndex(res)
- const body = Object.assign(query, result)
+ const body = await Hooks.wrapObject(Object.assign(query, result), 'filter:api.search.video-channels.index.list.params')
const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/video-channels'
try {
logger.debug('Doing video channels search index request on %s.', url, { body })
- const searchIndexResult = await doRequest<ResultList<VideoChannel>>({ uri: url, body, json: true })
+ const { body: searchIndexResult } = await doJSONRequest<ResultList<VideoChannel>>(url, { method: 'POST', json: body })
+ const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.video-channels.index.list.result')
- return res.json(searchIndexResult.body)
+ return res.json(jsonResult)
} catch (err) {
logger.warn('Cannot use search index to make video channels search.', { err })
async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: express.Response) {
const serverActor = await getServerActor()
- const options = {
+ const apiOptions = await Hooks.wrapObject({
actorId: serverActor.id,
search: query.search,
start: query.start,
count: query.count,
sort: query.sort
- }
- const resultList = await VideoChannelModel.searchForApi(options)
+ }, 'filter:api.search.video-channels.local.list.params')
+
+ const resultList = await Hooks.wrapPromiseFun(
+ VideoChannelModel.searchForApi,
+ apiOptions,
+ 'filter:api.search.video-channels.local.list.result'
+ )
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
async function searchVideosIndex (query: VideosSearchQuery, res: express.Response) {
const result = await buildMutedForSearchIndex(res)
- const body: VideosSearchQuery = Object.assign(query, result)
+ let body: VideosSearchQuery = Object.assign(query, result)
// Use the default instance NSFW policy if not specified
if (!body.nsfw) {
: 'both'
}
+ body = await Hooks.wrapObject(body, 'filter:api.search.videos.index.list.params')
+
const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/videos'
try {
logger.debug('Doing videos search index request on %s.', url, { body })
- const searchIndexResult = await doRequest<ResultList<Video>>({ uri: url, body, json: true })
+ const { body: searchIndexResult } = await doJSONRequest<ResultList<Video>>(url, { method: 'POST', json: body })
+ const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.videos.index.list.result')
- return res.json(searchIndexResult.body)
+ return res.json(jsonResult)
} catch (err) {
logger.warn('Cannot use search index to make video search.', { err })
}
async function searchVideosDB (query: VideosSearchQuery, res: express.Response) {
- const options = Object.assign(query, {
+ const apiOptions = await Hooks.wrapObject(Object.assign(query, {
includeLocalVideos: true,
nsfw: buildNSFWFilter(res, query.nsfw),
filter: query.filter,
user: res.locals.oauth ? res.locals.oauth.token.User : undefined
- })
- const resultList = await VideoModel.searchAndPopulateAccountAndServer(options)
+ }), 'filter:api.search.videos.local.list.params')
+
+ const resultList = await Hooks.wrapPromiseFun(
+ VideoModel.searchAndPopulateAccountAndServer,
+ apiOptions,
+ 'filter:api.search.videos.local.list.result'
+ )
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
import * as RateLimit from 'express-rate-limit'
import { tokensRouter } from '@server/controllers/api/users/token'
import { Hooks } from '@server/lib/plugins/hooks'
+import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
import { MUser, MUserAccountDefault } from '@server/types/models'
import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
+import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
import { UserRegister } from '../../../../shared/models/users/user-register.model'
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
import { sequelizeTypescript } from '../../../initializers/database'
import { Emailer } from '../../../lib/emailer'
import { Notifier } from '../../../lib/notifier'
-import { deleteUserToken } from '../../../lib/oauth-model'
import { Redis } from '../../../lib/redis'
import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
import {
import { myNotificationsRouter } from './my-notifications'
import { mySubscriptionsRouter } from './my-subscriptions'
import { myVideoPlaylistsRouter } from './my-video-playlists'
-import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
const auditLogger = auditLoggerFactory('users')
const user = await userToUpdate.save()
// Destroy user token to refresh rights
- if (roleChanged || body.password !== undefined) await deleteUserToken(userToUpdate.id)
+ if (roleChanged || body.password !== undefined) await OAuthTokenModel.deleteUserToken(userToUpdate.id)
auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
user.blockedReason = reason || null
await sequelizeTypescript.transaction(async t => {
- await deleteUserToken(user.id, t)
+ await OAuthTokenModel.deleteUserToken(user.id, t)
await user.save({ transaction: t })
})
newInstanceFollower: body.newInstanceFollower,
autoInstanceFollowing: body.autoInstanceFollowing,
abuseNewMessage: body.abuseNewMessage,
- abuseStateChange: body.abuseStateChange
+ abuseStateChange: body.abuseStateChange,
+ newPeerTubeVersion: body.newPeerTubeVersion,
+ newPluginVersion: body.newPluginVersion
}
await UserNotificationSettingModel.update(values, query)
-import { handleLogin, handleTokenRevocation } from '@server/lib/auth'
+import * as express from 'express'
import * as RateLimit from 'express-rate-limit'
+import { v4 as uuidv4 } from 'uuid'
+import { logger } from '@server/helpers/logger'
import { CONFIG } from '@server/initializers/config'
-import * as express from 'express'
+import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
+import { handleOAuthToken } from '@server/lib/auth/oauth'
+import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
import { Hooks } from '@server/lib/plugins/hooks'
import { asyncMiddleware, authenticate } from '@server/middlewares'
import { ScopedToken } from '@shared/models/users/user-scoped-token'
-import { v4 as uuidv4 } from 'uuid'
const tokensRouter = express.Router()
tokensRouter.post('/token',
loginRateLimiter,
- handleLogin,
- tokenSuccess
+ asyncMiddleware(handleToken)
)
tokensRouter.post('/revoke-token',
}
// ---------------------------------------------------------------------------
-function tokenSuccess (req: express.Request) {
- const username = req.body.username
+async function handleToken (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const grantType = req.body.grant_type
+
+ try {
+ const bypassLogin = await buildByPassLogin(req, grantType)
+
+ const refreshTokenAuthName = grantType === 'refresh_token'
+ ? await getAuthNameFromRefreshGrant(req.body.refresh_token)
+ : undefined
+
+ const options = {
+ refreshTokenAuthName,
+ bypassLogin
+ }
+
+ const token = await handleOAuthToken(req, options)
+
+ res.set('Cache-Control', 'no-store')
+ res.set('Pragma', 'no-cache')
+
+ Hooks.runAction('action:api.user.oauth2-got-token', { username: token.user.username, ip: req.ip })
+
+ return res.json({
+ token_type: 'Bearer',
- Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip })
+ access_token: token.accessToken,
+ refresh_token: token.refreshToken,
+
+ expires_in: token.accessTokenExpiresIn,
+ refresh_token_expires_in: token.refreshTokenExpiresIn
+ })
+ } catch (err) {
+ logger.warn('Login error', { err })
+
+ return res.status(err.code || 400).json({
+ code: err.name,
+ error: err.message
+ })
+ }
+}
+
+async function handleTokenRevocation (req: express.Request, res: express.Response) {
+ const token = res.locals.oauth.token
+
+ const result = await revokeToken(token, { req, explicitLogout: true })
+
+ return res.json(result)
}
function getScopedTokens (req: express.Request, res: express.Response) {
feedToken: user.feedToken
} as ScopedToken)
}
+
+async function buildByPassLogin (req: express.Request, grantType: string): Promise<BypassLogin> {
+ if (grantType !== 'password') return undefined
+
+ if (req.body.externalAuthToken) {
+ // Consistency with the getBypassFromPasswordGrant promise
+ return getBypassFromExternalAuth(req.body.username, req.body.externalAuthToken)
+ }
+
+ return getBypassFromPasswordGrant(req.body.username, req.body.password)
+}
import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils'
import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
-import { logger } from '../../../helpers/logger'
+import { logger, loggerTagsFactory } from '../../../helpers/logger'
import { getFormattedObjects } from '../../../helpers/utils'
import { CONFIG } from '../../../initializers/config'
import {
import { rateVideoRouter } from './rate'
import { watchingRouter } from './watching'
+const lTags = loggerTagsFactory('api', 'video')
const auditLogger = auditLoggerFactory('videos')
const videosRouter = express.Router()
})
auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
- logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
+ logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid))
return { videoCreated }
})
// Create the torrent file in async way because it could be long
createTorrentAndSetInfoHashAsync(video, videoFile)
- .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err }))
+ .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err, ...lTags(video.uuid) }))
.then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id))
.then(refreshedVideo => {
if (!refreshedVideo) return
return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t))
})
})
- .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err }))
+ .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) }))
if (video.state === VideoState.TO_TRANSCODE) {
await addOptimizeOrMergeAudioJob(videoCreated, videoFile, res.locals.oauth.token.User)
new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()),
oldVideoAuditView
)
- logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
+ logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid, lTags(videoInstance.uuid))
return videoInstanceUpdated
})
import { constants, promises as fs } from 'fs'
import { readFile } from 'fs-extra'
import { join } from 'path'
+import { logger } from '@server/helpers/logger'
import { CONFIG } from '@server/initializers/config'
+import { Hooks } from '@server/lib/plugins/hooks'
import { HttpStatusCode } from '@shared/core-utils'
import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n'
import { root } from '../helpers/core-utils'
? embedCSP
: (req: express.Request, res: express.Response, next: express.NextFunction) => next(),
+ // Set headers
(req: express.Request, res: express.Response, next: express.NextFunction) => {
res.removeHeader('X-Frame-Options')
}
async function generateEmbedHtmlPage (req: express.Request, res: express.Response) {
+ const hookName = req.originalUrl.startsWith('/video-playlists/')
+ ? 'filter:html.embed.video-playlist.allowed.result'
+ : 'filter:html.embed.video.allowed.result'
+
+ const allowParameters = { req }
+
+ const allowedResult = await Hooks.wrapFun(
+ isEmbedAllowed,
+ allowParameters,
+ hookName
+ )
+
+ if (!allowedResult || allowedResult.allowed !== true) {
+ logger.info('Embed is not allowed.', { allowedResult })
+
+ return sendHTML(allowedResult?.html || '', res)
+ }
+
const html = await ClientHtml.getEmbedHTML()
return sendHTML(html, res)
}
}
}
+
+type AllowedResult = { allowed: boolean, html?: string }
+function isEmbedAllowed (_object: {
+ req: express.Request
+}): AllowedResult {
+ return { allowed: true }
+}
import * as cors from 'cors'
import * as express from 'express'
+import { logger } from '@server/helpers/logger'
import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
+import { Hooks } from '@server/lib/plugins/hooks'
import { getVideoFilePath } from '@server/lib/video-paths'
-import { MVideoFile, MVideoFullLight } from '@server/types/models'
+import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
import { VideoStreamingPlaylistType } from '@shared/models'
import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants'
downloadRouter.use(
STATIC_DOWNLOAD_PATHS.TORRENTS + ':filename',
- downloadTorrent
+ asyncMiddleware(downloadTorrent)
)
downloadRouter.use(
STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension',
asyncMiddleware(videosDownloadValidator),
- downloadVideoFile
+ asyncMiddleware(downloadVideoFile)
)
downloadRouter.use(
STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension',
asyncMiddleware(videosDownloadValidator),
- downloadHLSVideoFile
+ asyncMiddleware(downloadHLSVideoFile)
)
// ---------------------------------------------------------------------------
const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename)
if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
+ const allowParameters = { torrentPath: result.path, downloadName: result.downloadName }
+
+ const allowedResult = await Hooks.wrapFun(
+ isTorrentDownloadAllowed,
+ allowParameters,
+ 'filter:api.download.torrent.allowed.result'
+ )
+
+ if (!checkAllowResult(res, allowParameters, allowedResult)) return
+
return res.download(result.path, result.downloadName)
}
-function downloadVideoFile (req: express.Request, res: express.Response) {
+async function downloadVideoFile (req: express.Request, res: express.Response) {
const video = res.locals.videoAll
const videoFile = getVideoFile(req, video.VideoFiles)
if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end()
+ const allowParameters = { video, videoFile }
+
+ const allowedResult = await Hooks.wrapFun(
+ isVideoDownloadAllowed,
+ allowParameters,
+ 'filter:api.download.video.allowed.result'
+ )
+
+ if (!checkAllowResult(res, allowParameters, allowedResult)) return
+
return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`)
}
-function downloadHLSVideoFile (req: express.Request, res: express.Response) {
+async function downloadHLSVideoFile (req: express.Request, res: express.Response) {
const video = res.locals.videoAll
- const playlist = getHLSPlaylist(video)
- if (!playlist) return res.status(HttpStatusCode.NOT_FOUND_404).end
+ const streamingPlaylist = getHLSPlaylist(video)
+ if (!streamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).end
- const videoFile = getVideoFile(req, playlist.VideoFiles)
+ const videoFile = getVideoFile(req, streamingPlaylist.VideoFiles)
if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end()
- const filename = `${video.name}-${videoFile.resolution}p-${playlist.getStringType()}${videoFile.extname}`
- return res.download(getVideoFilePath(playlist, videoFile), filename)
+ const allowParameters = { video, streamingPlaylist, videoFile }
+
+ const allowedResult = await Hooks.wrapFun(
+ isVideoDownloadAllowed,
+ allowParameters,
+ 'filter:api.download.video.allowed.result'
+ )
+
+ if (!checkAllowResult(res, allowParameters, allowedResult)) return
+
+ const filename = `${video.name}-${videoFile.resolution}p-${streamingPlaylist.getStringType()}${videoFile.extname}`
+ return res.download(getVideoFilePath(streamingPlaylist, videoFile), filename)
}
function getVideoFile (req: express.Request, files: MVideoFile[]) {
return Object.assign(playlist, { Video: video })
}
+
+type AllowedResult = {
+ allowed: boolean
+ errorMessage?: string
+}
+
+function isTorrentDownloadAllowed (_object: {
+ torrentPath: string
+}): AllowedResult {
+ return { allowed: true }
+}
+
+function isVideoDownloadAllowed (_object: {
+ video: MVideo
+ videoFile: MVideoFile
+ streamingPlaylist?: MStreamingPlaylist
+}): AllowedResult {
+ return { allowed: true }
+}
+
+function checkAllowResult (res: express.Response, allowParameters: any, result?: AllowedResult) {
+ if (!result || result.allowed !== true) {
+ logger.info('Download is not allowed.', { result, allowParameters })
+ res.status(HttpStatusCode.FORBIDDEN_403)
+ .json({ error: result?.errorMessage || 'Refused download' })
+
+ return false
+ }
+
+ return true
+}
import * as express from 'express'
import * as Feed from 'pfeed'
+import { VideoFilter } from '../../shared/models/videos/video-query.type'
import { buildNSFWFilter } from '../helpers/express-utils'
import { CONFIG } from '../initializers/config'
-import { FEEDS, ROUTE_CACHE_LIFETIME, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants'
+import { FEEDS, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
import {
asyncMiddleware,
commonVideosFiltersValidator,
import { cacheRoute } from '../middlewares/cache'
import { VideoModel } from '../models/video/video'
import { VideoCommentModel } from '../models/video/video-comment'
-import { VideoFilter } from '../../shared/models/videos/video-query.type'
const feedsRouter = express.Router()
},
thumbnail: [
{
- url: WEBSERVER.URL + video.getMiniatureStaticPath(),
- height: THUMBNAILS_SIZE.height,
- width: THUMBNAILS_SIZE.width
+ url: WEBSERVER.URL + video.getPreviewStaticPath(),
+ height: PREVIEWS_SIZE.height,
+ width: PREVIEWS_SIZE.width
}
]
})
import * as express from 'express'
-import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
import { join } from 'path'
-import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager'
-import { getPluginValidator, pluginStaticDirectoryValidator, getExternalAuthValidator } from '../middlewares/validators/plugins'
-import { serveThemeCSSValidator } from '../middlewares/validators/themes'
-import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
+import { logger } from '@server/helpers/logger'
+import { optionalAuthenticate } from '@server/middlewares/auth'
import { getCompleteLocale, is18nLocale } from '../../shared/core-utils/i18n'
+import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
import { PluginType } from '../../shared/models/plugins/plugin.type'
import { isTestInstance } from '../helpers/core-utils'
-import { logger } from '@server/helpers/logger'
-import { optionalAuthenticate } from '@server/middlewares/oauth'
+import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
+import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager'
+import { getExternalAuthValidator, getPluginValidator, pluginStaticDirectoryValidator } from '../middlewares/validators/plugins'
+import { serveThemeCSSValidator } from '../middlewares/validators/themes'
const sendFileOptions = {
maxAge: '30 days',
import validator from 'validator'
import { ContextType } from '@shared/models/activitypub/context'
import { ResultList } from '../../shared/models'
-import { Activity } from '../../shared/models/activitypub'
import { ACTIVITY_PUB, REMOTE_SCHEME } from '../initializers/constants'
import { MActor, MVideoWithHost } from '../types/models'
import { pageToStartAndCount } from './core-utils'
}
-function buildSignedActivity (byActor: MActor, data: Object, contextType?: ContextType) {
+function buildSignedActivity <T> (byActor: MActor, data: T, contextType?: ContextType) {
const activity = activityPubContextify(data, contextType)
- return signJsonLDObject(byActor, activity) as Promise<Activity>
+ return signJsonLDObject(byActor, activity)
}
function getAPId (activity: string | { id: string }) {
import { truncate } from 'lodash'
import { basename, isAbsolute, join, resolve } from 'path'
import * as pem from 'pem'
+import { pipeline } from 'stream'
import { URL } from 'url'
+import { promisify } from 'util'
const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
if (!oldObject || typeof oldObject !== 'object') {
}
}
+type SemVersion = { major: number, minor: number, patch: number }
+function parseSemVersion (s: string) {
+ const parsed = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/i)
+
+ return {
+ major: parseInt(parsed[1]),
+ minor: parseInt(parsed[2]),
+ patch: parseInt(parsed[3])
+ } as SemVersion
+}
+
const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
const execPromise2 = promisify2<string, any, string>(exec)
const execPromise = promisify1<string, string>(exec)
+const pipelinePromise = promisify(pipeline)
// ---------------------------------------------------------------------------
createPrivateKey,
getPublicKey,
execPromise2,
- execPromise
+ execPromise,
+ pipelinePromise,
+
+ parseSemVersion
}
import validator from 'validator'
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
+import { isAbuseReasonValid } from '../abuses'
import { exists } from '../misc'
import { sanitizeAndCheckActorObject } from './actor'
import { isCacheFileObjectValid } from './cache-file'
-import { isFlagActivityValid } from './flag'
import { isActivityPubUrlValid, isBaseActivityValid, isObjectValid } from './misc'
import { isPlaylistObjectValid } from './playlist'
-import { isDislikeActivityValid, isLikeActivityValid } from './rate'
-import { isShareActivityValid } from './share'
import { sanitizeAndCheckVideoCommentObject } from './video-comments'
import { sanitizeAndCheckVideoTorrentObject } from './videos'
-import { isViewActivityValid } from './view'
function isRootActivityValid (activity: any) {
return isCollection(activity) || isActivity(activity)
}
const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = {
- Create: checkCreateActivity,
- Update: checkUpdateActivity,
- Delete: checkDeleteActivity,
- Follow: checkFollowActivity,
- Accept: checkAcceptActivity,
- Reject: checkRejectActivity,
- Announce: checkAnnounceActivity,
- Undo: checkUndoActivity,
- Like: checkLikeActivity,
- View: checkViewActivity,
- Flag: checkFlagActivity,
- Dislike: checkDislikeActivity
+ Create: isCreateActivityValid,
+ Update: isUpdateActivityValid,
+ Delete: isDeleteActivityValid,
+ Follow: isFollowActivityValid,
+ Accept: isAcceptActivityValid,
+ Reject: isRejectActivityValid,
+ Announce: isAnnounceActivityValid,
+ Undo: isUndoActivityValid,
+ Like: isLikeActivityValid,
+ View: isViewActivityValid,
+ Flag: isFlagActivityValid,
+ Dislike: isDislikeActivityValid
}
function isActivityValid (activity: any) {
return checker(activity)
}
-// ---------------------------------------------------------------------------
-
-export {
- isRootActivityValid,
- isActivityValid
+function isFlagActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Flag') &&
+ isAbuseReasonValid(activity.content) &&
+ isActivityPubUrlValid(activity.object)
}
-// ---------------------------------------------------------------------------
-
-function checkViewActivity (activity: any) {
- return isBaseActivityValid(activity, 'View') &&
- isViewActivityValid(activity)
+function isLikeActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Like') &&
+ isObjectValid(activity.object)
}
-function checkFlagActivity (activity: any) {
- return isBaseActivityValid(activity, 'Flag') &&
- isFlagActivityValid(activity)
+function isDislikeActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Dislike') &&
+ isObjectValid(activity.object)
}
-function checkDislikeActivity (activity: any) {
- return isDislikeActivityValid(activity)
+function isAnnounceActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Announce') &&
+ isObjectValid(activity.object)
}
-function checkLikeActivity (activity: any) {
- return isLikeActivityValid(activity)
+function isViewActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'View') &&
+ isActivityPubUrlValid(activity.actor) &&
+ isActivityPubUrlValid(activity.object)
}
-function checkCreateActivity (activity: any) {
+function isCreateActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Create') &&
(
isViewActivityValid(activity.object) ||
)
}
-function checkUpdateActivity (activity: any) {
+function isUpdateActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Update') &&
(
isCacheFileObjectValid(activity.object) ||
)
}
-function checkDeleteActivity (activity: any) {
+function isDeleteActivityValid (activity: any) {
// We don't really check objects
return isBaseActivityValid(activity, 'Delete') &&
isObjectValid(activity.object)
}
-function checkFollowActivity (activity: any) {
+function isFollowActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Follow') &&
isObjectValid(activity.object)
}
-function checkAcceptActivity (activity: any) {
+function isAcceptActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Accept')
}
-function checkRejectActivity (activity: any) {
+function isRejectActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Reject')
}
-function checkAnnounceActivity (activity: any) {
- return isShareActivityValid(activity)
-}
-
-function checkUndoActivity (activity: any) {
+function isUndoActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Undo') &&
(
- checkFollowActivity(activity.object) ||
- checkLikeActivity(activity.object) ||
- checkDislikeActivity(activity.object) ||
- checkAnnounceActivity(activity.object) ||
- checkCreateActivity(activity.object)
+ isFollowActivityValid(activity.object) ||
+ isLikeActivityValid(activity.object) ||
+ isDislikeActivityValid(activity.object) ||
+ isAnnounceActivityValid(activity.object) ||
+ isCreateActivityValid(activity.object)
)
}
+
+// ---------------------------------------------------------------------------
+
+export {
+ isRootActivityValid,
+ isActivityValid,
+ isFlagActivityValid,
+ isLikeActivityValid,
+ isDislikeActivityValid,
+ isAnnounceActivityValid,
+ isViewActivityValid,
+ isCreateActivityValid,
+ isUpdateActivityValid,
+ isDeleteActivityValid,
+ isFollowActivityValid,
+ isAcceptActivityValid,
+ isRejectActivityValid,
+ isUndoActivityValid
+}
+++ /dev/null
-import { isActivityPubUrlValid } from './misc'
-import { isAbuseReasonValid } from '../abuses'
-
-function isFlagActivityValid (activity: any) {
- return activity.type === 'Flag' &&
- isAbuseReasonValid(activity.content) &&
- isActivityPubUrlValid(activity.object)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- isFlagActivityValid
-}
+++ /dev/null
-import { isBaseActivityValid, isObjectValid } from './misc'
-
-function isLikeActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Like') &&
- isObjectValid(activity.object)
-}
-
-function isDislikeActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Dislike') &&
- isObjectValid(activity.object)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- isDislikeActivityValid,
- isLikeActivityValid
-}
+++ /dev/null
-import { isBaseActivityValid, isObjectValid } from './misc'
-
-function isShareActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Announce') &&
- isObjectValid(activity.object)
-}
-// ---------------------------------------------------------------------------
-
-export {
- isShareActivityValid
-}
+++ /dev/null
-import { isActivityPubUrlValid } from './misc'
-
-function isViewActivityValid (activity: any) {
- return activity.type === 'View' &&
- isActivityPubUrlValid(activity.actor) &&
- isActivityPubUrlValid(activity.object)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- isViewActivityValid
-}
-import { exists } from './misc'
import validator from 'validator'
-import { UserNotificationType } from '../../../shared/models/users'
import { UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
+import { exists } from './misc'
function isUserNotificationTypeValid (value: any) {
- return exists(value) && validator.isInt('' + value) && UserNotificationType[value] !== undefined
+ return exists(value) && validator.isInt('' + value)
}
function isUserNotificationSettingValid (value: any) {
import { FFMPEG_NICE, VIDEO_LIVE } from '@server/initializers/constants'
import { AvailableEncoders, EncoderOptionsBuilder, EncoderProfile, VideoResolution } from '../../shared/models/videos'
import { CONFIG } from '../initializers/config'
-import { promisify0 } from './core-utils'
+import { execPromise, promisify0 } from './core-utils'
import { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils'
import { processImage } from './image-utils'
import { logger } from './logger'
return command
}
+function getFFmpegVersion () {
+ return new Promise<string>((res, rej) => {
+ (ffmpeg() as any)._getFfmpegPath((err, ffmpegPath) => {
+ if (err) return rej(err)
+ if (!ffmpegPath) return rej(new Error('Could not find ffmpeg path'))
+
+ return execPromise(`${ffmpegPath} -version`)
+ .then(stdout => {
+ const parsed = stdout.match(/ffmpeg version .(\d+\.\d+\.\d+)/)
+ if (!parsed || !parsed[1]) return rej(new Error(`Could not find ffmpeg version in ${stdout}`))
+
+ return res(parsed[1])
+ })
+ .catch(err => rej(err))
+ })
+ })
+}
+
async function runCommand (options: {
command: ffmpeg.FfmpegCommand
silent?: boolean // false
TranscodeOptionsType,
transcode,
runCommand,
+ getFFmpegVersion,
resetSupportedEncoders,
}
const consoleLoggerFormat = winston.format.printf(info => {
- const toOmit = [ 'label', 'timestamp', 'level', 'message', 'sql' ]
+ const toOmit = [ 'label', 'timestamp', 'level', 'message', 'sql', 'tags' ]
const obj = omit(info, ...toOmit)
error: bunyanLogFactory('error'),
fatal: bunyanLogFactory('error')
}
+
+function loggerTagsFactory (...defaultTags: string[]) {
+ return (...tags: string[]) => {
+ return { tags: defaultTags.concat(tags) }
+ }
+}
+
// ---------------------------------------------------------------------------
export {
consoleLoggerFormat,
jsonLoggerFormat,
logger,
+ loggerTagsFactory,
bunyanLogger
}
return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
}
-async function signJsonLDObject (byActor: MActor, data: any) {
+async function signJsonLDObject <T> (byActor: MActor, data: T) {
const signature = {
type: 'RsaSignature2017',
creator: byActor.url,
-import * as Bluebird from 'bluebird'
import { createWriteStream, remove } from 'fs-extra'
-import * as request from 'request'
+import got, { CancelableRequest, Options as GotOptions, RequestError } from 'got'
+import { join } from 'path'
+import { CONFIG } from '../initializers/config'
import { ACTIVITY_PUB, PEERTUBE_VERSION, WEBSERVER } from '../initializers/constants'
+import { pipelinePromise } from './core-utils'
import { processImage } from './image-utils'
-import { join } from 'path'
import { logger } from './logger'
-import { CONFIG } from '../initializers/config'
-function doRequest <T> (
- requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean },
- bodyKBLimit = 1000 // 1MB
-): Bluebird<{ response: request.RequestResponse, body: T }> {
- if (!(requestOptions.headers)) requestOptions.headers = {}
- requestOptions.headers['User-Agent'] = getUserAgent()
+export interface PeerTubeRequestError extends Error {
+ statusCode?: number
+ responseBody?: any
+}
- if (requestOptions.activityPub === true) {
- requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER
+const httpSignature = require('http-signature')
+
+type PeerTubeRequestOptions = {
+ activityPub?: boolean
+ bodyKBLimit?: number // 1MB
+ httpSignature?: {
+ algorithm: string
+ authorizationHeaderName: string
+ keyId: string
+ key: string
+ headers: string[]
}
+ jsonResponse?: boolean
+} & Pick<GotOptions, 'headers' | 'json' | 'method' | 'searchParams'>
+
+const peertubeGot = got.extend({
+ headers: {
+ 'user-agent': getUserAgent()
+ },
+
+ handlers: [
+ (options, next) => {
+ const promiseOrStream = next(options) as CancelableRequest<any>
+ const bodyKBLimit = options.context?.bodyKBLimit as number
+ if (!bodyKBLimit) throw new Error('No KB limit for this request')
+
+ const bodyLimit = bodyKBLimit * 1000
+
+ /* eslint-disable @typescript-eslint/no-floating-promises */
+ promiseOrStream.on('downloadProgress', progress => {
+ if (progress.transferred > bodyLimit && progress.percent !== 1) {
+ const message = `Exceeded the download limit of ${bodyLimit} B`
+ logger.warn(message)
+
+ // CancelableRequest
+ if (promiseOrStream.cancel) {
+ promiseOrStream.cancel()
+ return
+ }
+
+ // Stream
+ (promiseOrStream as any).destroy()
+ }
+ })
- return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => {
- request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body }))
- .on('data', onRequestDataLengthCheck(bodyKBLimit))
- })
+ return promiseOrStream
+ }
+ ],
+
+ hooks: {
+ beforeRequest: [
+ options => {
+ const headers = options.headers || {}
+ headers['host'] = options.url.host
+ },
+
+ options => {
+ const httpSignatureOptions = options.context?.httpSignature
+
+ if (httpSignatureOptions) {
+ const method = options.method ?? 'GET'
+ const path = options.path ?? options.url.pathname
+
+ if (!method || !path) {
+ throw new Error(`Cannot sign request without method (${method}) or path (${path}) ${options}`)
+ }
+
+ httpSignature.signRequest({
+ getHeader: function (header) {
+ return options.headers[header]
+ },
+
+ setHeader: function (header, value) {
+ options.headers[header] = value
+ },
+
+ method,
+ path
+ }, httpSignatureOptions)
+ }
+ }
+ ]
+ }
+})
+
+function doRequest (url: string, options: PeerTubeRequestOptions = {}) {
+ const gotOptions = buildGotOptions(options)
+
+ return peertubeGot(url, gotOptions)
+ .catch(err => { throw buildRequestError(err) })
}
-function doRequestAndSaveToFile (
- requestOptions: request.CoreOptions & request.UriOptions,
+function doJSONRequest <T> (url: string, options: PeerTubeRequestOptions = {}) {
+ const gotOptions = buildGotOptions(options)
+
+ return peertubeGot<T>(url, { ...gotOptions, responseType: 'json' })
+ .catch(err => { throw buildRequestError(err) })
+}
+
+async function doRequestAndSaveToFile (
+ url: string,
destPath: string,
- bodyKBLimit = 10000 // 10MB
+ options: PeerTubeRequestOptions = {}
) {
- if (!requestOptions.headers) requestOptions.headers = {}
- requestOptions.headers['User-Agent'] = getUserAgent()
-
- return new Bluebird<void>((res, rej) => {
- const file = createWriteStream(destPath)
- file.on('finish', () => res())
+ const gotOptions = buildGotOptions(options)
- request(requestOptions)
- .on('data', onRequestDataLengthCheck(bodyKBLimit))
- .on('error', err => {
- file.close()
+ const outFile = createWriteStream(destPath)
- remove(destPath)
- .catch(err => logger.error('Cannot remove %s after request failure.', destPath, { err }))
+ try {
+ await pipelinePromise(
+ peertubeGot.stream(url, gotOptions),
+ outFile
+ )
+ } catch (err) {
+ remove(destPath)
+ .catch(err => logger.error('Cannot remove %s after request failure.', destPath, { err }))
- return rej(err)
- })
- .pipe(file)
- })
+ throw buildRequestError(err)
+ }
}
async function downloadImage (url: string, destDir: string, destName: string, size: { width: number, height: number }) {
const tmpPath = join(CONFIG.STORAGE.TMP_DIR, 'pending-' + destName)
- await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath)
+ await doRequestAndSaveToFile(url, tmpPath)
const destPath = join(destDir, destName)
export {
doRequest,
+ doJSONRequest,
doRequestAndSaveToFile,
downloadImage
}
// ---------------------------------------------------------------------------
-// Thanks to https://github.com/request/request/issues/2470#issuecomment-268929907 <3
-function onRequestDataLengthCheck (bodyKBLimit: number) {
- let bufferLength = 0
- const bytesLimit = bodyKBLimit * 1000
+function buildGotOptions (options: PeerTubeRequestOptions) {
+ const { activityPub, bodyKBLimit = 1000 } = options
- return function (chunk) {
- bufferLength += chunk.length
- if (bufferLength > bytesLimit) {
- this.abort()
+ const context = { bodyKBLimit, httpSignature: options.httpSignature }
- const error = new Error(`Response was too large - aborted after ${bytesLimit} bytes.`)
- this.emit('error', error)
- }
+ let headers = options.headers || {}
+
+ if (!headers.date) {
+ headers = { ...headers, date: new Date().toUTCString() }
+ }
+
+ if (activityPub && !headers.accept) {
+ headers = { ...headers, accept: ACTIVITY_PUB.ACCEPT_HEADER }
}
+
+ return {
+ method: options.method,
+ json: options.json,
+ searchParams: options.searchParams,
+ headers,
+ context
+ }
+}
+
+function buildRequestError (error: RequestError) {
+ const newError: PeerTubeRequestError = new Error(error.message)
+ newError.name = error.name
+ newError.stack = error.stack
+
+ if (error.response) {
+ newError.responseBody = error.response.body
+ newError.statusCode = error.response.statusCode
+ }
+
+ return newError
}
import { createWriteStream } from 'fs'
import { ensureDir, move, pathExists, remove, writeFile } from 'fs-extra'
+import got from 'got'
import { join } from 'path'
-import * as request from 'request'
import { CONFIG } from '@server/initializers/config'
import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
import { VideoResolution } from '../../shared/models/videos'
import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../initializers/constants'
import { getEnabledResolutions } from '../lib/video-transcoding'
-import { peertubeTruncate, root } from './core-utils'
+import { peertubeTruncate, pipelinePromise, root } from './core-utils'
import { isVideoFileExtnameValid } from './custom-validators/videos'
import { logger } from './logger'
import { generateVideoImportTmpPath } from './utils'
await ensureDir(binDirectory)
- return new Promise<void>(res => {
- request.get(url, { followRedirect: false }, (err, result) => {
- if (err) {
- logger.error('Cannot update youtube-dl.', { err })
- return res()
- }
-
- if (result.statusCode !== HttpStatusCode.FOUND_302) {
- logger.error('youtube-dl update error: did not get redirect for the latest version link. Status %d', result.statusCode)
- return res()
- }
-
- const url = result.headers.location
- const downloadFile = request.get(url)
- const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(url)[1]
-
- downloadFile.on('response', result => {
- if (result.statusCode !== HttpStatusCode.OK_200) {
- logger.error('Cannot update youtube-dl: new version response is not 200, it\'s %d.', result.statusCode)
- return res()
- }
-
- const writeStream = createWriteStream(bin, { mode: 493 }).on('error', err => {
- logger.error('youtube-dl update error in write stream', { err })
- return res()
- })
+ try {
+ const result = await got(url, { followRedirect: false })
- downloadFile.pipe(writeStream)
- })
+ if (result.statusCode !== HttpStatusCode.FOUND_302) {
+ logger.error('youtube-dl update error: did not get redirect for the latest version link. Status %d', result.statusCode)
+ return
+ }
- downloadFile.on('error', err => {
- logger.error('youtube-dl update error.', { err })
- return res()
- })
+ const newUrl = result.headers.location
+ const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(newUrl)[1]
- downloadFile.on('end', () => {
- const details = JSON.stringify({ version: newVersion, path: bin, exec: 'youtube-dl' })
- writeFile(detailsPath, details, { encoding: 'utf8' }, err => {
- if (err) {
- logger.error('youtube-dl update error: cannot write details.', { err })
- return res()
- }
+ const downloadFileStream = got.stream(newUrl)
+ const writeStream = createWriteStream(bin, { mode: 493 })
- logger.info('youtube-dl updated to version %s.', newVersion)
- return res()
- })
- })
- })
- })
+ await pipelinePromise(
+ downloadFileStream,
+ writeStream
+ )
+
+ const details = JSON.stringify({ version: newVersion, path: bin, exec: 'youtube-dl' })
+ await writeFile(detailsPath, details, { encoding: 'utf8' })
+
+ logger.info('youtube-dl updated to version %s.', newVersion)
+ } catch (err) {
+ logger.error('Cannot update youtube-dl.', { err })
+ }
}
async function safeGetYoutubeDL () {
import * as config from 'config'
-import { isProdInstance, isTestInstance } from '../helpers/core-utils'
-import { UserModel } from '../models/account/user'
-import { getServerActor, ApplicationModel } from '../models/application/application'
-import { OAuthClientModel } from '../models/oauth/oauth-client'
+import { uniq } from 'lodash'
import { URL } from 'url'
-import { CONFIG, isEmailEnabled } from './config'
-import { logger } from '../helpers/logger'
+import { getFFmpegVersion } from '@server/helpers/ffmpeg-utils'
+import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type'
import { RecentlyAddedStrategy } from '../../shared/models/redundancy'
+import { isProdInstance, isTestInstance, parseSemVersion } from '../helpers/core-utils'
import { isArray } from '../helpers/custom-validators/misc'
-import { uniq } from 'lodash'
+import { logger } from '../helpers/logger'
+import { UserModel } from '../models/account/user'
+import { ApplicationModel, getServerActor } from '../models/application/application'
+import { OAuthClientModel } from '../models/oauth/oauth-client'
+import { CONFIG, isEmailEnabled } from './config'
import { WEBSERVER } from './constants'
-import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type'
async function checkActivityPubUrls () {
const actor = await getServerActor()
return totalApplication !== 0
}
+async function checkFFmpegVersion () {
+ const version = await getFFmpegVersion()
+ const { major, minor } = parseSemVersion(version)
+
+ if (major < 4 || (major === 4 && minor < 1)) {
+ logger.warn('Your ffmpeg version (%s) is outdated. PeerTube supports ffmpeg >= 4.1. Please upgrade.', version)
+ }
+}
+
// ---------------------------------------------------------------------------
export {
checkConfig,
clientsExist,
+ checkFFmpegVersion,
usersExist,
applicationExist,
checkActivityPubUrls
import * as config from 'config'
-import { promisify0 } from '../helpers/core-utils'
+import { parseSemVersion, promisify0 } from '../helpers/core-utils'
import { logger } from '../helpers/logger'
// ONLY USE CORE MODULES IN THIS FILE!
'theme.default',
'remote_redundancy.videos.accept_from',
'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions',
+ 'peertube.check_latest_version.enabled', 'peertube.check_latest_version.url',
'search.remote_uri.users', 'search.remote_uri.anonymous', 'search.search_index.enabled', 'search.search_index.url',
'search.search_index.disable_local_search', 'search.search_index.is_default_search',
'live.enabled', 'live.allow_replay', 'live.max_duration', 'live.max_user_lives', 'live.max_instance_lives',
function checkNodeVersion () {
const v = process.version
- const majorString = v.split('.')[0].replace('v', '')
- const major = parseInt(majorString, 10)
+ const { major } = parseSemVersion(v)
logger.debug('Checking NodeJS version %s.', v)
CLEANUP_REMOTE_INTERACTIONS: config.get<boolean>('federation.videos.cleanup_remote_interactions')
}
},
+ PEERTUBE: {
+ CHECK_LATEST_VERSION: {
+ ENABLED: config.get<boolean>('peertube.check_latest_version.enabled'),
+ URL: config.get<string>('peertube.check_latest_version.url')
+ }
+ },
ADMIN: {
get EMAIL () { return config.get<string>('admin.email') }
},
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 610
+const LAST_MIGRATION_VERSION = 625
// ---------------------------------------------------------------------------
const API_VERSION = 'v1'
-const PEERTUBE_VERSION = require(join(root(), 'package.json')).version
+const PEERTUBE_VERSION: string = require(join(root(), 'package.json')).version
const PAGINATION = {
GLOBAL: {
updateVideos: 60000, // 1 minute
youtubeDLUpdate: 60000 * 60 * 24, // 1 day
checkPlugins: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL,
+ checkPeerTubeVersion: 60000 * 60 * 24, // 1 day
autoFollowIndexInstances: 60000 * 60 * 24, // 1 day
removeOldViews: 60000 * 60 * 24, // 1 day
removeOldHistory: 60000 * 60 * 24, // 1 day
SCHEDULER_INTERVALS_MS.updateVideos = 5000
SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000
SCHEDULER_INTERVALS_MS.updateInboxStats = 5000
+ SCHEDULER_INTERVALS_MS.checkPeerTubeVersion = 2000
REPEAT_JOBS['videos-views'] = { every: 5000 }
REPEAT_JOBS['activitypub-cleaner'] = { every: 5000 }
newMessage += ' in ' + benchmark + 'ms'
}
- logger.debug(newMessage, { sql: message })
+ logger.debug(newMessage, { sql: message, tags: [ 'sql' ] })
}
})
--- /dev/null
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction
+ queryInterface: Sequelize.QueryInterface
+ sequelize: Sequelize.Sequelize
+ db: any
+}): Promise<void> {
+ {
+ const notificationSettingColumns = [ 'newPeerTubeVersion', 'newPluginVersion' ]
+
+ for (const column of notificationSettingColumns) {
+ const data = {
+ type: Sequelize.INTEGER,
+ defaultValue: null,
+ allowNull: true
+ }
+ await utils.queryInterface.addColumn('userNotificationSetting', column, data)
+ }
+
+ {
+ const query = 'UPDATE "userNotificationSetting" SET "newPeerTubeVersion" = 3, "newPluginVersion" = 1'
+ await utils.sequelize.query(query)
+ }
+
+ for (const column of notificationSettingColumns) {
+ const data = {
+ type: Sequelize.INTEGER,
+ defaultValue: null,
+ allowNull: false
+ }
+ await utils.queryInterface.changeColumn('userNotificationSetting', column, data)
+ }
+ }
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
--- /dev/null
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction
+ queryInterface: Sequelize.QueryInterface
+ sequelize: Sequelize.Sequelize
+ db: any
+}): Promise<void> {
+
+ {
+ const data = {
+ type: Sequelize.STRING,
+ defaultValue: null,
+ allowNull: true
+ }
+ await utils.queryInterface.addColumn('application', 'latestPeerTubeVersion', data)
+ }
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
--- /dev/null
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction
+ queryInterface: Sequelize.QueryInterface
+ sequelize: Sequelize.Sequelize
+ db: any
+}): Promise<void> {
+
+ {
+ await utils.sequelize.query(`
+ ALTER TABLE "userNotification"
+ ADD COLUMN "applicationId" INTEGER REFERENCES "application" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
+ ADD COLUMN "pluginId" INTEGER REFERENCES "plugin" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+ `)
+ }
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
import * as Bluebird from 'bluebird'
+import { extname } from 'path'
import { Op, Transaction } from 'sequelize'
import { URL } from 'url'
import { v4 as uuidv4 } from 'uuid'
+import { getServerActor } from '@server/models/application/application'
+import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
import { ActivityPubActor, ActivityPubActorType, ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
+import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor'
import { sanitizeAndCheckActorObject } from '../../helpers/custom-validators/activitypub/actor'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils'
import { logger } from '../../helpers/logger'
import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
-import { doRequest } from '../../helpers/requests'
+import { doJSONRequest, PeerTubeRequestError } from '../../helpers/requests'
import { getUrlFromWebfinger } from '../../helpers/webfinger'
import { MIMETYPES, WEBSERVER } from '../../initializers/constants'
+import { sequelizeTypescript } from '../../initializers/database'
import { AccountModel } from '../../models/account/account'
import { ActorModel } from '../../models/activitypub/actor'
import { AvatarModel } from '../../models/avatar/avatar'
import { ServerModel } from '../../models/server/server'
import { VideoChannelModel } from '../../models/video/video-channel'
-import { JobQueue } from '../job-queue'
-import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor'
-import { sequelizeTypescript } from '../../initializers/database'
import {
MAccount,
MAccountDefault,
MActorId,
MChannel
} from '../../types/models'
-import { extname } from 'path'
-import { getServerActor } from '@server/models/application/application'
-import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
+import { JobQueue } from '../job-queue'
// Set account keys, this could be long so process after the account creation and do not block the client
async function generateAndSaveActorKeys <T extends MActor> (actor: T) {
}
async function fetchActorTotalItems (url: string) {
- const options = {
- uri: url,
- method: 'GET',
- json: true,
- activityPub: true
- }
-
try {
- const { body } = await doRequest<ActivityPubOrderedCollection<unknown>>(options)
- return body.totalItems ? body.totalItems : 0
+ const { body } = await doJSONRequest<ActivityPubOrderedCollection<unknown>>(url, { activityPub: true })
+
+ return body.totalItems || 0
} catch (err) {
logger.warn('Cannot fetch remote actor count %s.', url, { err })
return 0
actorUrl = actor.url
}
- const { result, statusCode } = await fetchRemoteActor(actorUrl)
-
- if (statusCode === HttpStatusCode.NOT_FOUND_404) {
- logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
- actor.Account
- ? await actor.Account.destroy()
- : await actor.VideoChannel.destroy()
-
- return { actor: undefined, refreshed: false }
- }
+ const { result } = await fetchRemoteActor(actorUrl)
if (result === undefined) {
logger.warn('Cannot fetch remote actor in refresh actor.')
return { refreshed: true, actor }
})
} catch (err) {
+ if ((err as PeerTubeRequestError).statusCode === HttpStatusCode.NOT_FOUND_404) {
+ logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
+ actor.Account
+ ? await actor.Account.destroy()
+ : await actor.VideoChannel.destroy()
+
+ return { actor: undefined, refreshed: false }
+ }
+
logger.warn('Cannot refresh actor %s.', actor.url, { err })
return { actor, refreshed: false }
}
attributedTo: ActivityPubAttributedTo[]
}
async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: number, result: FetchRemoteActorResult }> {
- const options = {
- uri: actorUrl,
- method: 'GET',
- json: true,
- activityPub: true
- }
-
logger.info('Fetching remote actor %s.', actorUrl)
- const requestResult = await doRequest<ActivityPubActor>(options)
+ const requestResult = await doJSONRequest<ActivityPubActor>(actorUrl, { activityPub: true })
const actorJSON = requestResult.body
if (sanitizeAndCheckActorObject(actorJSON) === false) {
logger.debug('Remote actor JSON is not valid.', { actorJSON })
- return { result: undefined, statusCode: requestResult.response.statusCode }
+ return { result: undefined, statusCode: requestResult.statusCode }
}
if (checkUrlsSameHost(actorJSON.id, actorUrl) !== true) {
logger.warn('Actor url %s has not the same host than its AP id %s', actorUrl, actorJSON.id)
- return { result: undefined, statusCode: requestResult.response.statusCode }
+ return { result: undefined, statusCode: requestResult.statusCode }
}
const followersCount = await fetchActorTotalItems(actorJSON.followers)
const name = actorJSON.name || actorJSON.preferredUsername
return {
- statusCode: requestResult.response.statusCode,
+ statusCode: requestResult.statusCode,
result: {
actor,
name,
-import { ACTIVITY_PUB, REQUEST_TIMEOUT, WEBSERVER } from '../../initializers/constants'
-import { doRequest } from '../../helpers/requests'
-import { logger } from '../../helpers/logger'
import * as Bluebird from 'bluebird'
-import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
import { URL } from 'url'
+import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
+import { logger } from '../../helpers/logger'
+import { doJSONRequest } from '../../helpers/requests'
+import { ACTIVITY_PUB, REQUEST_TIMEOUT, WEBSERVER } from '../../initializers/constants'
type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>)
type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>)
-async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T>, cleaner?: CleanerFunction) {
- logger.info('Crawling ActivityPub data on %s.', uri)
+async function crawlCollectionPage <T> (argUrl: string, handler: HandlerFunction<T>, cleaner?: CleanerFunction) {
+ let url = argUrl
+
+ logger.info('Crawling ActivityPub data on %s.', url)
const options = {
- method: 'GET',
- uri,
- json: true,
activityPub: true,
timeout: REQUEST_TIMEOUT
}
const startDate = new Date()
- const response = await doRequest<ActivityPubOrderedCollection<T>>(options)
+ const response = await doJSONRequest<ActivityPubOrderedCollection<T>>(url, options)
const firstBody = response.body
const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT
const remoteHost = new URL(nextLink).host
if (remoteHost === WEBSERVER.HOST) continue
- options.uri = nextLink
+ url = nextLink
- const res = await doRequest<ActivityPubOrderedCollection<T>>(options)
+ const res = await doJSONRequest<ActivityPubOrderedCollection<T>>(url, options)
body = res.body
} else {
// nextLink is already the object we want
if (Array.isArray(body.orderedItems)) {
const items = body.orderedItems
- logger.info('Processing %i ActivityPub items for %s.', items.length, options.uri)
+ logger.info('Processing %i ActivityPub items for %s.', items.length, url)
await handler(items)
}
+import * as Bluebird from 'bluebird'
+import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
+import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
-import { crawlCollectionPage } from './crawl'
-import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
+import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
+import { checkUrlsSameHost } from '../../helpers/activitypub'
+import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist'
import { isArray } from '../../helpers/custom-validators/misc'
-import { getOrCreateActorAndServerAndModel } from './actor'
import { logger } from '../../helpers/logger'
+import { doJSONRequest, PeerTubeRequestError } from '../../helpers/requests'
+import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
+import { sequelizeTypescript } from '../../initializers/database'
import { VideoPlaylistModel } from '../../models/video/video-playlist'
-import { doRequest } from '../../helpers/requests'
-import { checkUrlsSameHost } from '../../helpers/activitypub'
-import * as Bluebird from 'bluebird'
-import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
-import { getOrCreateVideoAndAccountAndChannel } from './videos'
-import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist'
import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
-import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
-import { sequelizeTypescript } from '../../initializers/database'
-import { createPlaylistMiniatureFromUrl } from '../thumbnail'
-import { FilteredModelAttributes } from '../../types/sequelize'
import { MAccountDefault, MAccountId, MVideoId } from '../../types/models'
import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../types/models/video/video-playlist'
-import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
+import { FilteredModelAttributes } from '../../types/sequelize'
+import { createPlaylistMiniatureFromUrl } from '../thumbnail'
+import { getOrCreateActorAndServerAndModel } from './actor'
+import { crawlCollectionPage } from './crawl'
+import { getOrCreateVideoAndAccountAndChannel } from './videos'
function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
if (exists === true) return
// Fetch url
- const { body } = await doRequest<PlaylistObject>({
- uri: playlistUrl,
- json: true,
- activityPub: true
- })
+ const { body } = await doJSONRequest<PlaylistObject>(playlistUrl, { activityPub: true })
if (!isPlaylistObjectValid(body)) {
throw new Error(`Invalid playlist object when fetch account playlists: ${JSON.stringify(body)}`)
if (!videoPlaylist.isOutdated()) return videoPlaylist
try {
- const { statusCode, playlistObject } = await fetchRemoteVideoPlaylist(videoPlaylist.url)
- if (statusCode === HttpStatusCode.NOT_FOUND_404) {
- logger.info('Cannot refresh remote video playlist %s: it does not exist anymore. Deleting it.', videoPlaylist.url)
-
- await videoPlaylist.destroy()
- return undefined
- }
+ const { playlistObject } = await fetchRemoteVideoPlaylist(videoPlaylist.url)
if (playlistObject === undefined) {
logger.warn('Cannot refresh remote playlist %s: invalid body.', videoPlaylist.url)
return videoPlaylist
} catch (err) {
+ if ((err as PeerTubeRequestError).statusCode === HttpStatusCode.NOT_FOUND_404) {
+ logger.info('Cannot refresh remote video playlist %s: it does not exist anymore. Deleting it.', videoPlaylist.url)
+
+ await videoPlaylist.destroy()
+ return undefined
+ }
+
logger.warn('Cannot refresh video playlist %s.', videoPlaylist.url, { err })
await videoPlaylist.setAsRefreshed()
await Bluebird.map(elementUrls, async elementUrl => {
try {
- // Fetch url
- const { body } = await doRequest<PlaylistElementObject>({
- uri: elementUrl,
- json: true,
- activityPub: true
- })
+ const { body } = await doJSONRequest<PlaylistElementObject>(elementUrl, { activityPub: true })
if (!isPlaylistElementObjectValid(body)) throw new Error(`Invalid body in video get playlist element ${elementUrl}`)
}
async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> {
- const options = {
- uri: playlistUrl,
- method: 'GET',
- json: true,
- activityPub: true
- }
-
logger.info('Fetching remote playlist %s.', playlistUrl)
- const { response, body } = await doRequest<any>(options)
+ const { body, statusCode } = await doJSONRequest<any>(playlistUrl, { activityPub: true })
if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) {
logger.debug('Remote video playlist JSON is not valid.', { body })
- return { statusCode: response.statusCode, playlistObject: undefined }
+ return { statusCode, playlistObject: undefined }
}
- return { statusCode: response.statusCode, playlistObject: body }
+ return { statusCode, playlistObject: body }
}
import { VideoCommentModel } from '../../../models/video/video-comment'
import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience'
-import { logger } from '../../../helpers/logger'
+import { logger, loggerTagsFactory } from '../../../helpers/logger'
import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
import {
MActorLight,
import { getServerActor } from '@server/models/application/application'
import { ContextType } from '@shared/models/activitypub/context'
+const lTags = loggerTagsFactory('ap', 'create')
+
async function sendCreateVideo (video: MVideoAP, t: Transaction) {
if (!video.hasPrivacyForFederation()) return undefined
- logger.info('Creating job to send video creation of %s.', video.url)
+ logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
const byActor = video.VideoChannel.Account.Actor
const videoObject = video.toActivityPubObject()
video: MVideoAccountLight,
fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
) {
- logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
+ logger.info('Creating job to send file cache of %s.', fileRedundancy.url, lTags(video.uuid))
return sendVideoRelatedCreateActivity({
byActor,
async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) {
if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
- logger.info('Creating job to send create video playlist of %s.', playlist.url)
+ logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
const byActor = playlist.OwnerAccount.Actor
const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
+import * as Bluebird from 'bluebird'
import { Transaction } from 'sequelize'
+import { getServerActor } from '@server/models/application/application'
+import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
+import { logger, loggerTagsFactory } from '../../helpers/logger'
+import { doJSONRequest } from '../../helpers/requests'
+import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
import { VideoShareModel } from '../../models/video/video-share'
+import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video'
+import { getOrCreateActorAndServerAndModel } from './actor'
import { sendUndoAnnounce, sendVideoAnnounce } from './send'
import { getLocalVideoAnnounceActivityPubUrl } from './url'
-import * as Bluebird from 'bluebird'
-import { doRequest } from '../../helpers/requests'
-import { getOrCreateActorAndServerAndModel } from './actor'
-import { logger } from '../../helpers/logger'
-import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
-import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
-import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video'
-import { getServerActor } from '@server/models/application/application'
+
+const lTags = loggerTagsFactory('share')
async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) {
if (!video.hasPrivacyForFederation()) return undefined
oldVideoChannel: MChannelActorLight,
t: Transaction
) {
- logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name)
+ logger.info(
+ 'Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name,
+ lTags(video.uuid)
+ )
await undoShareByVideoChannel(video, oldVideoChannel, t)
async function addVideoShares (shareUrls: string[], video: MVideoId) {
await Bluebird.map(shareUrls, async shareUrl => {
try {
- // Fetch url
- const { body } = await doRequest<any>({
- uri: shareUrl,
- json: true,
- activityPub: true
- })
+ const { body } = await doJSONRequest<any>(shareUrl, { activityPub: true })
if (!body || !body.actor) throw new Error('Body or body actor is invalid')
const actorUrl = getAPId(body.actor)
+import * as Bluebird from 'bluebird'
+import { checkUrlsSameHost } from '../../helpers/activitypub'
import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments'
import { logger } from '../../helpers/logger'
-import { doRequest } from '../../helpers/requests'
+import { doJSONRequest } from '../../helpers/requests'
import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
import { VideoCommentModel } from '../../models/video/video-comment'
+import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video'
import { getOrCreateActorAndServerAndModel } from './actor'
import { getOrCreateVideoAndAccountAndChannel } from './videos'
-import * as Bluebird from 'bluebird'
-import { checkUrlsSameHost } from '../../helpers/activitypub'
-import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video'
type ResolveThreadParams = {
url: string
type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }>
async function addVideoComments (commentUrls: string[]) {
- return Bluebird.map(commentUrls, commentUrl => {
- return resolveThread({ url: commentUrl, isVideo: false })
+ return Bluebird.map(commentUrls, async commentUrl => {
+ try {
+ await resolveThread({ url: commentUrl, isVideo: false })
+ } catch (err) {
+ logger.warn('Cannot resolve thread %s.', commentUrl, { err })
+ }
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
}
throw new Error('Recursion limit reached when resolving a thread')
}
- const { body } = await doRequest<any>({
- uri: url,
- json: true,
- activityPub: true
- })
+ const { body } = await doJSONRequest<any>(url, { activityPub: true })
if (sanitizeAndCheckVideoCommentObject(body) === false) {
throw new Error(`Remote video comment JSON ${url} is not valid:` + JSON.stringify(body))
+import * as Bluebird from 'bluebird'
import { Transaction } from 'sequelize'
-import { sendLike, sendUndoDislike, sendUndoLike } from './send'
+import { doJSONRequest } from '@server/helpers/requests'
import { VideoRateType } from '../../../shared/models/videos'
-import * as Bluebird from 'bluebird'
-import { getOrCreateActorAndServerAndModel } from './actor'
-import { AccountVideoRateModel } from '../../models/account/account-video-rate'
+import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
import { logger } from '../../helpers/logger'
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
-import { doRequest } from '../../helpers/requests'
-import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
-import { getVideoDislikeActivityPubUrlByLocalActor, getVideoLikeActivityPubUrlByLocalActor } from './url'
-import { sendDislike } from './send/send-dislike'
+import { AccountVideoRateModel } from '../../models/account/account-video-rate'
import { MAccountActor, MActorUrl, MVideo, MVideoAccountLight, MVideoId } from '../../types/models'
+import { getOrCreateActorAndServerAndModel } from './actor'
+import { sendLike, sendUndoDislike, sendUndoLike } from './send'
+import { sendDislike } from './send/send-dislike'
+import { getVideoDislikeActivityPubUrlByLocalActor, getVideoLikeActivityPubUrlByLocalActor } from './url'
async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateType) {
await Bluebird.map(ratesUrl, async rateUrl => {
try {
// Fetch url
- const { body } = await doRequest<any>({
- uri: rateUrl,
- json: true,
- activityPub: true
- })
+ const { body } = await doJSONRequest<any>(rateUrl, { activityPub: true })
if (!body || !body.actor) throw new Error('Body or body actor is invalid')
const actorUrl = getAPId(body.actor)
import { maxBy, minBy } from 'lodash'
import * as magnetUtil from 'magnet-uri'
import { basename, join } from 'path'
-import * as request from 'request'
import { Transaction } from 'sequelize/types'
import { TrackerModel } from '@server/models/server/tracker'
import { VideoLiveModel } from '@server/models/video/video-live'
import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
import { logger } from '../../helpers/logger'
-import { doRequest } from '../../helpers/requests'
+import { doJSONRequest, PeerTubeRequestError } from '../../helpers/requests'
import { fetchVideoByUrl, getExtFromMimetype, VideoFetchByUrlType } from '../../helpers/video'
import {
ACTIVITY_PUB,
}
}
-async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request.RequestResponse, videoObject: VideoObject }> {
- const options = {
- uri: videoUrl,
- method: 'GET',
- json: true,
- activityPub: true
- }
-
+async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> {
logger.info('Fetching remote video %s.', videoUrl)
- const { response, body } = await doRequest<any>(options)
+ const { statusCode, body } = await doJSONRequest<any>(videoUrl, { activityPub: true })
if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) {
logger.debug('Remote video JSON is not valid.', { body })
- return { response, videoObject: undefined }
+ return { statusCode, videoObject: undefined }
}
- return { response, videoObject: body }
+ return { statusCode, videoObject: body }
}
async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
const host = video.VideoChannel.Account.Actor.Server.host
const path = video.getDescriptionAPIPath()
- const options = {
- uri: REMOTE_SCHEME.HTTP + '://' + host + path,
- json: true
- }
+ const url = REMOTE_SCHEME.HTTP + '://' + host + path
- const { body } = await doRequest<any>(options)
- return body.description ? body.description : ''
+ const { body } = await doJSONRequest<any>(url)
+ return body.description || ''
}
function getOrCreateVideoChannelFromVideoObject (videoObject: VideoObject) {
: await VideoModel.loadByUrlAndPopulateAccount(options.video.url)
try {
- const { response, videoObject } = await fetchRemoteVideo(video.url)
- if (response.statusCode === HttpStatusCode.NOT_FOUND_404) {
- logger.info('Cannot refresh remote video %s: video does not exist anymore. Deleting it.', video.url)
-
- // Video does not exist anymore
- await video.destroy()
- return undefined
- }
+ const { videoObject } = await fetchRemoteVideo(video.url)
if (videoObject === undefined) {
logger.warn('Cannot refresh remote video %s: invalid body.', video.url)
return video
} catch (err) {
+ if ((err as PeerTubeRequestError).statusCode === HttpStatusCode.NOT_FOUND_404) {
+ logger.info('Cannot refresh remote video %s: video does not exist anymore. Deleting it.', video.url)
+
+ // Video does not exist anymore
+ await video.destroy()
+ return undefined
+ }
+
logger.warn('Cannot refresh video %s.', options.video.url, { err })
ActorFollowScoreCache.Instance.addBadServerId(video.VideoChannel.Actor.serverId)
+
import { isUserDisplayNameValid, isUserRoleValid, isUserUsernameValid } from '@server/helpers/custom-validators/users'
import { logger } from '@server/helpers/logger'
import { generateRandomString } from '@server/helpers/utils'
-import { OAUTH_LIFETIME, PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME } from '@server/initializers/constants'
-import { revokeToken } from '@server/lib/oauth-model'
+import { PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME } from '@server/initializers/constants'
import { PluginManager } from '@server/lib/plugins/plugin-manager'
import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
-import { UserRole } from '@shared/models'
import {
RegisterServerAuthenticatedResult,
RegisterServerAuthPassOptions,
RegisterServerExternalAuthenticatedResult
} from '@server/types/plugins/register-server-auth.model'
-import * as express from 'express'
-import * as OAuthServer from 'express-oauth-server'
-import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
-
-const oAuthServer = new OAuthServer({
- useErrorHandler: true,
- accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN,
- refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN,
- allowExtendedTokenAttributes: true,
- continueMiddleware: true,
- model: require('./oauth-model')
-})
+import { UserRole } from '@shared/models'
// Token is the key, expiration date is the value
const authBypassTokens = new Map<string, {
npmName: string
}>()
-async function handleLogin (req: express.Request, res: express.Response, next: express.NextFunction) {
- const grantType = req.body.grant_type
-
- if (grantType === 'password') {
- if (req.body.externalAuthToken) proxifyExternalAuthBypass(req, res)
- else await proxifyPasswordGrant(req, res)
- } else if (grantType === 'refresh_token') {
- await proxifyRefreshGrant(req, res)
- }
-
- return forwardTokenReq(req, res, next)
-}
-
-async function handleTokenRevocation (req: express.Request, res: express.Response) {
- const token = res.locals.oauth.token
-
- res.locals.explicitLogout = true
- const result = await revokeToken(token)
-
- // FIXME: uncomment when https://github.com/oauthjs/node-oauth2-server/pull/289 is released
- // oAuthServer.revoke(req, res, err => {
- // if (err) {
- // logger.warn('Error in revoke token handler.', { err })
- //
- // return res.status(err.status)
- // .json({
- // error: err.message,
- // code: err.name
- // })
- // .end()
- // }
- // })
-
- return res.json(result)
-}
-
async function onExternalUserAuthenticated (options: {
npmName: string
authName: string
authName
})
- // Cleanup
+ // Cleanup expired tokens
const now = new Date()
for (const [ key, value ] of authBypassTokens) {
if (value.expires.getTime() < now.getTime()) {
res.redirect(`/login?externalAuthToken=${bypassToken}&username=${user.username}`)
}
-// ---------------------------------------------------------------------------
-
-export { oAuthServer, handleLogin, onExternalUserAuthenticated, handleTokenRevocation }
-
-// ---------------------------------------------------------------------------
-
-function forwardTokenReq (req: express.Request, res: express.Response, next?: express.NextFunction) {
- return oAuthServer.token()(req, res, err => {
- if (err) {
- logger.warn('Login error.', { err })
-
- return res.status(err.status)
- .json({
- error: err.message,
- code: err.name
- })
- }
-
- if (next) return next()
- })
-}
-
-async function proxifyRefreshGrant (req: express.Request, res: express.Response) {
- const refreshToken = req.body.refresh_token
- if (!refreshToken) return
+async function getAuthNameFromRefreshGrant (refreshToken?: string) {
+ if (!refreshToken) return undefined
const tokenModel = await OAuthTokenModel.loadByRefreshToken(refreshToken)
- if (tokenModel?.authName) res.locals.refreshTokenAuthName = tokenModel.authName
+
+ return tokenModel?.authName
}
-async function proxifyPasswordGrant (req: express.Request, res: express.Response) {
+async function getBypassFromPasswordGrant (username: string, password: string) {
const plugins = PluginManager.Instance.getIdAndPassAuths()
const pluginAuths: { npmName?: string, registerAuthOptions: RegisterServerAuthPassOptions }[] = []
})
const loginOptions = {
- id: req.body.username,
- password: req.body.password
+ id: username,
+ password
}
for (const pluginAuth of pluginAuths) {
authName, npmName, loginOptions.id
)
- res.locals.bypassLogin = {
+ return {
bypass: true,
pluginName: pluginAuth.npmName,
authName: authOptions.authName,
user: buildUserResult(loginResult)
}
-
- return
} catch (err) {
logger.error('Error in auth method %s of plugin %s', authOptions.authName, pluginAuth.npmName, { err })
}
}
+
+ return undefined
}
-function proxifyExternalAuthBypass (req: express.Request, res: express.Response) {
- const obj = authBypassTokens.get(req.body.externalAuthToken)
- if (!obj) {
- logger.error('Cannot authenticate user with unknown bypass token')
- return res.sendStatus(HttpStatusCode.BAD_REQUEST_400)
- }
+function getBypassFromExternalAuth (username: string, externalAuthToken: string) {
+ const obj = authBypassTokens.get(externalAuthToken)
+ if (!obj) throw new Error('Cannot authenticate user with unknown bypass token')
const { expires, user, authName, npmName } = obj
const now = new Date()
if (now.getTime() > expires.getTime()) {
- logger.error('Cannot authenticate user with an expired external auth token')
- return res.sendStatus(HttpStatusCode.BAD_REQUEST_400)
+ throw new Error('Cannot authenticate user with an expired external auth token')
}
- if (user.username !== req.body.username) {
- logger.error('Cannot authenticate user %s with invalid username %s.', req.body.username)
- return res.sendStatus(HttpStatusCode.BAD_REQUEST_400)
+ if (user.username !== username) {
+ throw new Error(`Cannot authenticate user ${user.username} with invalid username ${username}`)
}
- // Bypass oauth library validation
- req.body.password = 'fake'
-
logger.info(
'Auth success with external auth method %s of plugin %s for %s.',
authName, npmName, user.email
)
- res.locals.bypassLogin = {
+ return {
bypass: true,
pluginName: npmName,
authName: authName,
displayName: pluginResult.displayName || pluginResult.username
}
}
+
+// ---------------------------------------------------------------------------
+
+export {
+ onExternalUserAuthenticated,
+ getBypassFromExternalAuth,
+ getAuthNameFromRefreshGrant,
+ getBypassFromPasswordGrant
+}
import * as express from 'express'
-import * as LRUCache from 'lru-cache'
import { AccessDeniedError } from 'oauth2-server'
-import { Transaction } from 'sequelize'
import { PluginManager } from '@server/lib/plugins/plugin-manager'
import { ActorModel } from '@server/models/activitypub/actor'
+import { MOAuthClient } from '@server/types/models'
import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token'
import { MUser } from '@server/types/models/user/user'
import { UserAdminFlag } from '@shared/models/users/user-flag.model'
import { UserRole } from '@shared/models/users/user-role'
-import { logger } from '../helpers/logger'
-import { CONFIG } from '../initializers/config'
-import { LRU_CACHE } from '../initializers/constants'
-import { UserModel } from '../models/account/user'
-import { OAuthClientModel } from '../models/oauth/oauth-client'
-import { OAuthTokenModel } from '../models/oauth/oauth-token'
-import { createUserAccountAndChannelAndPlaylist } from './user'
-
-type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
-
-const accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
-const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
-
-// ---------------------------------------------------------------------------
-
-function deleteUserToken (userId: number, t?: Transaction) {
- clearCacheByUserId(userId)
-
- return OAuthTokenModel.deleteUserToken(userId, t)
+import { logger } from '../../helpers/logger'
+import { CONFIG } from '../../initializers/config'
+import { UserModel } from '../../models/account/user'
+import { OAuthClientModel } from '../../models/oauth/oauth-client'
+import { OAuthTokenModel } from '../../models/oauth/oauth-token'
+import { createUserAccountAndChannelAndPlaylist } from '../user'
+import { TokensCache } from './tokens-cache'
+
+type TokenInfo = {
+ accessToken: string
+ refreshToken: string
+ accessTokenExpiresAt: Date
+ refreshTokenExpiresAt: Date
}
-function clearCacheByUserId (userId: number) {
- const token = userHavingToken.get(userId)
-
- if (token !== undefined) {
- accessTokenCache.del(token)
- userHavingToken.del(userId)
- }
-}
-
-function clearCacheByToken (token: string) {
- const tokenModel = accessTokenCache.get(token)
-
- if (tokenModel !== undefined) {
- userHavingToken.del(tokenModel.userId)
- accessTokenCache.del(token)
+export type BypassLogin = {
+ bypass: boolean
+ pluginName: string
+ authName?: string
+ user: {
+ username: string
+ email: string
+ displayName: string
+ role: UserRole
}
}
let tokenModel: MOAuthTokenUser
- if (accessTokenCache.has(bearerToken)) {
- tokenModel = accessTokenCache.get(bearerToken)
+ if (TokensCache.Instance.hasToken(bearerToken)) {
+ tokenModel = TokensCache.Instance.getByToken(bearerToken)
} else {
tokenModel = await OAuthTokenModel.getByTokenAndPopulateUser(bearerToken)
- if (tokenModel) {
- accessTokenCache.set(bearerToken, tokenModel)
- userHavingToken.set(tokenModel.userId, tokenModel.accessToken)
- }
+ if (tokenModel) TokensCache.Instance.setToken(tokenModel)
}
if (!tokenModel) return undefined
return tokenInfo
}
-async function getUser (usernameOrEmail?: string, password?: string) {
- const res: express.Response = this.request.res
-
+async function getUser (usernameOrEmail?: string, password?: string, bypassLogin?: BypassLogin) {
// Special treatment coming from a plugin
- if (res.locals.bypassLogin && res.locals.bypassLogin.bypass === true) {
- const obj = res.locals.bypassLogin
- logger.info('Bypassing oauth login by plugin %s.', obj.pluginName)
+ if (bypassLogin && bypassLogin.bypass === true) {
+ logger.info('Bypassing oauth login by plugin %s.', bypassLogin.pluginName)
- let user = await UserModel.loadByEmail(obj.user.email)
- if (!user) user = await createUserFromExternal(obj.pluginName, obj.user)
+ let user = await UserModel.loadByEmail(bypassLogin.user.email)
+ if (!user) user = await createUserFromExternal(bypassLogin.pluginName, bypassLogin.user)
// Cannot create a user
if (!user) throw new AccessDeniedError('Cannot create such user: an actor with that name already exists.')
// Then we just go through a regular login process
if (user.pluginAuth !== null) {
// This user does not belong to this plugin, skip it
- if (user.pluginAuth !== obj.pluginName) return null
+ if (user.pluginAuth !== bypassLogin.pluginName) return null
checkUserValidityOrThrow(user)
return user
}
-async function revokeToken (tokenInfo: { refreshToken: string }): Promise<{ success: boolean, redirectUrl?: string }> {
- const res: express.Response = this.request.res
+async function revokeToken (
+ tokenInfo: { refreshToken: string },
+ options: {
+ req?: express.Request
+ explicitLogout?: boolean
+ } = {}
+): Promise<{ success: boolean, redirectUrl?: string }> {
+ const { req, explicitLogout } = options
+
const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken)
if (token) {
let redirectUrl: string
- if (res.locals.explicitLogout === true && token.User.pluginAuth && token.authName) {
- redirectUrl = await PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName, token.User, this.request)
+ if (explicitLogout === true && token.User.pluginAuth && token.authName) {
+ redirectUrl = await PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName, token.User, req)
}
- clearCacheByToken(token.accessToken)
+ TokensCache.Instance.clearCacheByToken(token.accessToken)
token.destroy()
.catch(err => logger.error('Cannot destroy token when revoking token.', { err }))
return { success: false }
}
-async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) {
- const res: express.Response = this.request.res
-
+async function saveToken (
+ token: TokenInfo,
+ client: MOAuthClient,
+ user: MUser,
+ options: {
+ refreshTokenAuthName?: string
+ bypassLogin?: BypassLogin
+ } = {}
+) {
+ const { refreshTokenAuthName, bypassLogin } = options
let authName: string = null
- if (res.locals.bypassLogin?.bypass === true) {
- authName = res.locals.bypassLogin.authName
- } else if (res.locals.refreshTokenAuthName) {
- authName = res.locals.refreshTokenAuthName
+
+ if (bypassLogin?.bypass === true) {
+ authName = bypassLogin.authName
+ } else if (refreshTokenAuthName) {
+ authName = refreshTokenAuthName
}
logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.')
refreshTokenExpiresAt: tokenCreated.refreshTokenExpiresAt,
client,
user,
- refresh_token_expires_in: Math.floor((tokenCreated.refreshTokenExpiresAt.getTime() - new Date().getTime()) / 1000)
+ accessTokenExpiresIn: buildExpiresIn(tokenCreated.accessTokenExpiresAt),
+ refreshTokenExpiresIn: buildExpiresIn(tokenCreated.refreshTokenExpiresAt)
}
}
-// ---------------------------------------------------------------------------
-
-// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
export {
- deleteUserToken,
- clearCacheByUserId,
- clearCacheByToken,
getAccessToken,
getClient,
getRefreshToken,
saveToken
}
+// ---------------------------------------------------------------------------
+
async function createUserFromExternal (pluginAuth: string, options: {
username: string
email: string
function checkUserValidityOrThrow (user: MUser) {
if (user.blocked) throw new AccessDeniedError('User is blocked.')
}
+
+function buildExpiresIn (expiresAt: Date) {
+ return Math.floor((expiresAt.getTime() - new Date().getTime()) / 1000)
+}
--- /dev/null
+import * as express from 'express'
+import {
+ InvalidClientError,
+ InvalidGrantError,
+ InvalidRequestError,
+ Request,
+ Response,
+ UnauthorizedClientError,
+ UnsupportedGrantTypeError
+} from 'oauth2-server'
+import { randomBytesPromise, sha1 } from '@server/helpers/core-utils'
+import { MOAuthClient } from '@server/types/models'
+import { OAUTH_LIFETIME } from '../../initializers/constants'
+import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model'
+
+/**
+ *
+ * Reimplement some functions of OAuth2Server to inject external auth methods
+ *
+ */
+
+const oAuthServer = new (require('oauth2-server'))({
+ accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN,
+ refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN,
+
+ // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
+ model: require('./oauth-model')
+})
+
+// ---------------------------------------------------------------------------
+
+async function handleOAuthToken (req: express.Request, options: { refreshTokenAuthName?: string, bypassLogin?: BypassLogin }) {
+ const request = new Request(req)
+ const { refreshTokenAuthName, bypassLogin } = options
+
+ if (request.method !== 'POST') {
+ throw new InvalidRequestError('Invalid request: method must be POST')
+ }
+
+ if (!request.is([ 'application/x-www-form-urlencoded' ])) {
+ throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded')
+ }
+
+ const clientId = request.body.client_id
+ const clientSecret = request.body.client_secret
+
+ if (!clientId || !clientSecret) {
+ throw new InvalidClientError('Invalid client: cannot retrieve client credentials')
+ }
+
+ const client = await getClient(clientId, clientSecret)
+ if (!client) {
+ throw new InvalidClientError('Invalid client: client is invalid')
+ }
+
+ const grantType = request.body.grant_type
+ if (!grantType) {
+ throw new InvalidRequestError('Missing parameter: `grant_type`')
+ }
+
+ if (![ 'password', 'refresh_token' ].includes(grantType)) {
+ throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid')
+ }
+
+ if (!client.grants.includes(grantType)) {
+ throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid')
+ }
+
+ if (grantType === 'password') {
+ return handlePasswordGrant({
+ request,
+ client,
+ bypassLogin
+ })
+ }
+
+ return handleRefreshGrant({
+ request,
+ client,
+ refreshTokenAuthName
+ })
+}
+
+async function handleOAuthAuthenticate (
+ req: express.Request,
+ res: express.Response,
+ authenticateInQuery = false
+) {
+ const options = authenticateInQuery
+ ? { allowBearerTokensInQueryString: true }
+ : {}
+
+ return oAuthServer.authenticate(new Request(req), new Response(res), options)
+}
+
+export {
+ handleOAuthToken,
+ handleOAuthAuthenticate
+}
+
+// ---------------------------------------------------------------------------
+
+async function handlePasswordGrant (options: {
+ request: Request
+ client: MOAuthClient
+ bypassLogin?: BypassLogin
+}) {
+ const { request, client, bypassLogin } = options
+
+ if (!request.body.username) {
+ throw new InvalidRequestError('Missing parameter: `username`')
+ }
+
+ if (!bypassLogin && !request.body.password) {
+ throw new InvalidRequestError('Missing parameter: `password`')
+ }
+
+ const user = await getUser(request.body.username, request.body.password, bypassLogin)
+ if (!user) throw new InvalidGrantError('Invalid grant: user credentials are invalid')
+
+ const token = await buildToken()
+
+ return saveToken(token, client, user, { bypassLogin })
+}
+
+async function handleRefreshGrant (options: {
+ request: Request
+ client: MOAuthClient
+ refreshTokenAuthName: string
+}) {
+ const { request, client, refreshTokenAuthName } = options
+
+ if (!request.body.refresh_token) {
+ throw new InvalidRequestError('Missing parameter: `refresh_token`')
+ }
+
+ const refreshToken = await getRefreshToken(request.body.refresh_token)
+
+ if (!refreshToken) {
+ throw new InvalidGrantError('Invalid grant: refresh token is invalid')
+ }
+
+ if (refreshToken.client.id !== client.id) {
+ throw new InvalidGrantError('Invalid grant: refresh token is invalid')
+ }
+
+ if (refreshToken.refreshTokenExpiresAt && refreshToken.refreshTokenExpiresAt < new Date()) {
+ throw new InvalidGrantError('Invalid grant: refresh token has expired')
+ }
+
+ await revokeToken({ refreshToken: refreshToken.refreshToken })
+
+ const token = await buildToken()
+
+ return saveToken(token, client, refreshToken.user, { refreshTokenAuthName })
+}
+
+function generateRandomToken () {
+ return randomBytesPromise(256)
+ .then(buffer => sha1(buffer))
+}
+
+function getTokenExpiresAt (type: 'access' | 'refresh') {
+ const lifetime = type === 'access'
+ ? OAUTH_LIFETIME.ACCESS_TOKEN
+ : OAUTH_LIFETIME.REFRESH_TOKEN
+
+ return new Date(Date.now() + lifetime * 1000)
+}
+
+async function buildToken () {
+ const [ accessToken, refreshToken ] = await Promise.all([ generateRandomToken(), generateRandomToken() ])
+
+ return {
+ accessToken,
+ refreshToken,
+ accessTokenExpiresAt: getTokenExpiresAt('access'),
+ refreshTokenExpiresAt: getTokenExpiresAt('refresh')
+ }
+}
--- /dev/null
+import * as LRUCache from 'lru-cache'
+import { MOAuthTokenUser } from '@server/types/models'
+import { LRU_CACHE } from '../../initializers/constants'
+
+export class TokensCache {
+
+ private static instance: TokensCache
+
+ private readonly accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
+ private readonly userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
+
+ private constructor () { }
+
+ static get Instance () {
+ return this.instance || (this.instance = new this())
+ }
+
+ hasToken (token: string) {
+ return this.accessTokenCache.has(token)
+ }
+
+ getByToken (token: string) {
+ return this.accessTokenCache.get(token)
+ }
+
+ setToken (token: MOAuthTokenUser) {
+ this.accessTokenCache.set(token.accessToken, token)
+ this.userHavingToken.set(token.userId, token.accessToken)
+ }
+
+ deleteUserToken (userId: number) {
+ this.clearCacheByUserId(userId)
+ }
+
+ clearCacheByUserId (userId: number) {
+ const token = this.userHavingToken.get(userId)
+
+ if (token !== undefined) {
+ this.accessTokenCache.del(token)
+ this.userHavingToken.del(userId)
+ }
+ }
+
+ clearCacheByToken (token: string) {
+ const tokenModel = this.accessTokenCache.get(token)
+
+ if (tokenModel !== undefined) {
+ this.userHavingToken.del(tokenModel.userId)
+ this.accessTokenCache.del(token)
+ }
+ }
+}
import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import'
import { SANITIZE_OPTIONS, TEXT_WITH_HTML_RULES } from '@shared/core-utils'
import { AbuseState, EmailPayload, UserAbuse } from '@shared/models'
-import { SendEmailOptions } from '../../shared/models/server/emailer.model'
+import { SendEmailDefaultOptions } from '../../shared/models/server/emailer.model'
import { isTestInstance, root } from '../helpers/core-utils'
import { bunyanLogger, logger } from '../helpers/logger'
import { CONFIG, isEmailEnabled } from '../initializers/config'
import { WEBSERVER } from '../initializers/constants'
-import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MUser } from '../types/models'
+import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MPlugin, MUser } from '../types/models'
import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video'
import { JobQueue } from './job-queue'
}
async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
- const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
+ const videoAutoBlacklistUrl = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
const channel = (await VideoChannelModel.loadByIdAndPopulateAccount(videoBlacklist.Video.channelId)).toFormattedSummaryJSON()
videoName: videoBlacklist.Video.name,
action: {
text: 'Review autoblacklist',
- url: VIDEO_AUTO_BLACKLIST_URL
+ url: videoAutoBlacklistUrl
}
}
}
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
+ addNewPeerTubeVersionNotification (to: string[], latestVersion: string) {
+ const emailPayload: EmailPayload = {
+ to,
+ template: 'peertube-version-new',
+ subject: `A new PeerTube version is available: ${latestVersion}`,
+ locals: {
+ latestVersion
+ }
+ }
+
+ return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+ }
+
+ addNewPlugionVersionNotification (to: string[], plugin: MPlugin) {
+ const pluginUrl = WEBSERVER.URL + '/admin/plugins/list-installed?pluginType=' + plugin.type
+
+ const emailPayload: EmailPayload = {
+ to,
+ template: 'plugin-version-new',
+ subject: `A new plugin/theme version is available: ${plugin.name}@${plugin.latestVersion}`,
+ locals: {
+ pluginName: plugin.name,
+ latestVersion: plugin.latestVersion,
+ pluginUrl
+ }
+ }
+
+ return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+ }
+
addPasswordResetEmailJob (username: string, to: string, resetPasswordUrl: string) {
const emailPayload: EmailPayload = {
template: 'password-reset',
})
for (const to of options.to) {
- await email
- .send(merge(
- {
- template: 'common',
- message: {
- to,
- from: options.from,
- subject: options.subject,
- replyTo: options.replyTo
- },
- locals: { // default variables available in all templates
- WEBSERVER,
- EMAIL: CONFIG.EMAIL,
- instanceName: CONFIG.INSTANCE.NAME,
- text: options.text,
- subject: options.subject
- }
- },
- options // overriden/new variables given for a specific template in the payload
- ) as SendEmailOptions)
+ const baseOptions: SendEmailDefaultOptions = {
+ template: 'common',
+ message: {
+ to,
+ from: options.from,
+ subject: options.subject,
+ replyTo: options.replyTo
+ },
+ locals: { // default variables available in all templates
+ WEBSERVER,
+ EMAIL: CONFIG.EMAIL,
+ instanceName: CONFIG.INSTANCE.NAME,
+ text: options.text,
+ subject: options.subject
+ }
+ }
+
+ // overriden/new variables given for a specific template in the payload
+ const sendOptions = merge(baseOptions, options)
+
+ await email.send(sendOptions)
.then(res => logger.debug('Sent email.', { res }))
.catch(err => logger.error('Error in email sender.', { err }))
}
--- /dev/null
+extends ../common/greetings
+
+block title
+ | New PeerTube version available
+
+block content
+ p
+ | A new version of PeerTube is available: #{latestVersion}.
+ | You can check the latest news on #[a(href="https://joinpeertube.org/news") JoinPeerTube].
--- /dev/null
+extends ../common/greetings
+
+block title
+ | New plugin version available
+
+block content
+ p
+ | A new version of the plugin/theme #{pluginName} is available: #{latestVersion}.
+ | You might want to upgrade it on #[a(href=pluginUrl) the PeerTube admin interface].
const remoteUrl = videoCaption.getFileUrl(video)
const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.filename)
- await doRequestAndSaveToFile({ uri: remoteUrl }, destPath)
+ await doRequestAndSaveToFile(remoteUrl, destPath)
return { isOwned: false, path: destPath }
}
const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, preview.filename)
const remoteUrl = preview.getFileUrl(video)
- await doRequestAndSaveToFile({ uri: remoteUrl }, destPath)
+ await doRequestAndSaveToFile(remoteUrl, destPath)
logger.debug('Fetched remote preview %s to %s.', remoteUrl, destPath)
import { FILES_CACHE } from '../../initializers/constants'
import { VideoModel } from '../../models/video/video'
import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
+import { MVideo, MVideoFile } from '@server/types/models'
class VideosTorrentCache extends AbstractVideoStaticFileCache <string> {
const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(filename)
if (!file) return undefined
- if (file.getVideo().isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.TORRENTS_DIR, file.torrentFilename) }
+ if (file.getVideo().isOwned()) {
+ const downloadName = this.buildDownloadName(file.getVideo(), file)
+
+ return { isOwned: true, path: join(CONFIG.STORAGE.TORRENTS_DIR, file.torrentFilename), downloadName }
+ }
return this.loadRemoteFile(filename)
}
const remoteUrl = file.getRemoteTorrentUrl(video)
const destPath = join(FILES_CACHE.TORRENTS.DIRECTORY, file.torrentFilename)
- await doRequestAndSaveToFile({ uri: remoteUrl }, destPath)
+ await doRequestAndSaveToFile(remoteUrl, destPath)
- const downloadName = `${video.name}-${file.resolution}p.torrent`
+ const downloadName = this.buildDownloadName(video, file)
return { isOwned: false, path: destPath, downloadName }
}
+
+ private buildDownloadName (video: MVideo, file: MVideoFile) {
+ return `${video.name}-${file.resolution}p.torrent`
+ }
}
export {
const destPath = join(tmpDirectory, basename(fileUrl))
const bodyKBLimit = 10 * 1000 * 1000 // 10GB
- await doRequestAndSaveToFile({ uri: fileUrl }, destPath, bodyKBLimit)
+ await doRequestAndSaveToFile(fileUrl, destPath, { bodyKBLimit })
}
clearTimeout(timer)
}
async function fetchUniqUrls (playlistUrl: string) {
- const { body } = await doRequest<string>({ uri: playlistUrl })
+ const { body } = await doRequest(playlistUrl)
if (!body) return []
import * as Bluebird from 'bluebird'
import * as Bull from 'bull'
import { checkUrlsSameHost } from '@server/helpers/activitypub'
-import { isDislikeActivityValid, isLikeActivityValid } from '@server/helpers/custom-validators/activitypub/rate'
-import { isShareActivityValid } from '@server/helpers/custom-validators/activitypub/share'
+import {
+ isAnnounceActivityValid,
+ isDislikeActivityValid,
+ isLikeActivityValid
+} from '@server/helpers/custom-validators/activitypub/activity'
import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments'
-import { doRequest } from '@server/helpers/requests'
+import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests'
import { AP_CLEANER_CONCURRENCY } from '@server/initializers/constants'
import { VideoModel } from '@server/models/video/video'
import { VideoCommentModel } from '@server/models/video/video-comment'
updater: (url: string, newUrl: string) => Promise<T>,
deleter: (url: string) => Promise<T>
): Promise<{ data: T, status: 'deleted' | 'updated' } | null> {
- // Fetch url
- const { response, body } = await doRequest<any>({
- uri: url,
- json: true,
- activityPub: true
- })
-
- // Does not exist anymore, remove entry
- if (response.statusCode === HttpStatusCode.NOT_FOUND_404) {
+ const on404OrTombstone = async () => {
logger.info('Removing remote AP object %s.', url)
const data = await deleter(url)
- return { status: 'deleted', data }
+ return { status: 'deleted' as 'deleted', data }
}
- // If not same id, check same host and update
- if (!body || !body.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`)
+ try {
+ const { body } = await doJSONRequest<any>(url, { activityPub: true })
- if (body.type === 'Tombstone') {
- logger.info('Removing remote AP object %s.', url)
- const data = await deleter(url)
+ // If not same id, check same host and update
+ if (!body || !body.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`)
- return { status: 'deleted', data }
- }
+ if (body.type === 'Tombstone') {
+ return on404OrTombstone()
+ }
- const newUrl = body.id
- if (newUrl !== url) {
- if (checkUrlsSameHost(newUrl, url) !== true) {
- throw new Error(`New url ${newUrl} has not the same host than old url ${url}`)
+ const newUrl = body.id
+ if (newUrl !== url) {
+ if (checkUrlsSameHost(newUrl, url) !== true) {
+ throw new Error(`New url ${newUrl} has not the same host than old url ${url}`)
+ }
+
+ logger.info('Updating remote AP object %s.', url)
+ const data = await updater(url, newUrl)
+
+ return { status: 'updated', data }
}
- logger.info('Updating remote AP object %s.', url)
- const data = await updater(url, newUrl)
+ return null
+ } catch (err) {
+ // Does not exist anymore, remove entry
+ if ((err as PeerTubeRequestError).statusCode === HttpStatusCode.NOT_FOUND_404) {
+ return on404OrTombstone()
+ }
- return { status: 'updated', data }
+ throw err
}
-
- return null
}
function rateOptionsFactory () {
function shareOptionsFactory () {
return {
- bodyValidator: (body: any) => isShareActivityValid(body),
+ bodyValidator: (body: any) => isAnnounceActivityValid(body),
updater: async (url: string, newUrl: string) => {
const share = await VideoShareModel.loadByUrl(url, undefined)
const httpSignatureOptions = await buildSignedRequestOptions(payload)
const options = {
- method: 'POST',
- uri: '',
+ method: 'POST' as 'POST',
json: body,
httpSignature: httpSignatureOptions,
timeout: REQUEST_TIMEOUT,
const goodUrls: string[] = []
await Bluebird.map(payload.uris, uri => {
- return doRequest(Object.assign({}, options, { uri }))
+ return doRequest(uri, options)
.then(() => goodUrls.push(uri))
.catch(() => badUrls.push(uri))
}, { concurrency: BROADCAST_CONCURRENCY })
const httpSignatureOptions = await buildSignedRequestOptions(payload)
const options = {
- method: 'POST',
- uri,
+ method: 'POST' as 'POST',
json: body,
httpSignature: httpSignatureOptions,
timeout: REQUEST_TIMEOUT,
}
try {
- await doRequest(options)
+ await doRequest(uri, options)
ActorFollowScoreCache.Instance.updateActorFollowsScore([ uri ], [])
} catch (err) {
ActorFollowScoreCache.Instance.updateActorFollowsScore([], [ uri ])
import { buildDigest } from '@server/helpers/peertube-crypto'
import { ContextType } from '@shared/models/activitypub/context'
-type Payload = { body: any, contextType?: ContextType, signatureActorId?: number }
+type Payload <T> = { body: T, contextType?: ContextType, signatureActorId?: number }
-async function computeBody (payload: Payload) {
+async function computeBody <T> (
+ payload: Payload<T>
+): Promise<T | T & { type: 'RsaSignature2017', creator: string, created: string }> {
let body = payload.body
if (payload.signatureActorId) {
const actorSignature = await ActorModel.load(payload.signatureActorId)
if (!actorSignature) throw new Error('Unknown signature actor id.')
+
body = await buildSignedActivity(actorSignature, payload.body, payload.contextType)
}
return body
}
-async function buildSignedRequestOptions (payload: Payload) {
+async function buildSignedRequestOptions (payload: Payload<any>) {
let actor: MActor | null
if (payload.signatureActorId) {
function buildGlobalHeaders (body: any) {
return {
- 'Digest': buildDigest(body),
- 'Content-Type': 'application/activity+json',
- 'Accept': ACTIVITY_PUB.ACCEPT_HEADER
+ 'digest': buildDigest(body),
+ 'content-type': 'application/activity+json',
+ 'accept': ACTIVITY_PUB.ACCEPT_HEADER
}
}
import { AccountBlocklistModel } from '../models/account/account-blocklist'
import { UserModel } from '../models/account/user'
import { UserNotificationModel } from '../models/account/user-notification'
-import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull } from '../types/models'
+import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull, MApplication, MPlugin } from '../types/models'
import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video'
import { isBlockedByServerOrAccount } from './blocklist'
import { Emailer } from './emailer'
})
}
+ notifyOfNewPeerTubeVersion (application: MApplication, latestVersion: string) {
+ this.notifyAdminsOfNewPeerTubeVersion(application, latestVersion)
+ .catch(err => {
+ logger.error('Cannot notify on new PeerTubeb version %s.', latestVersion, { err })
+ })
+ }
+
+ notifyOfNewPluginVersion (plugin: MPlugin) {
+ this.notifyAdminsOfNewPluginVersion(plugin)
+ .catch(err => {
+ logger.error('Cannot notify on new plugin version %s.', plugin.name, { err })
+ })
+ }
+
private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
// List all followers that are users
const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
}
+ private async notifyAdminsOfNewPeerTubeVersion (application: MApplication, latestVersion: string) {
+ // Use the debug right to know who is an administrator
+ const admins = await UserModel.listWithRight(UserRight.MANAGE_DEBUG)
+ if (admins.length === 0) return
+
+ logger.info('Notifying %s admins of new PeerTube version %s.', admins.length, latestVersion)
+
+ function settingGetter (user: MUserWithNotificationSetting) {
+ return user.NotificationSetting.newPeerTubeVersion
+ }
+
+ async function notificationCreator (user: MUserWithNotificationSetting) {
+ const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
+ type: UserNotificationType.NEW_PEERTUBE_VERSION,
+ userId: user.id,
+ applicationId: application.id
+ })
+ notification.Application = application
+
+ return notification
+ }
+
+ function emailSender (emails: string[]) {
+ return Emailer.Instance.addNewPeerTubeVersionNotification(emails, latestVersion)
+ }
+
+ return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
+ }
+
+ private async notifyAdminsOfNewPluginVersion (plugin: MPlugin) {
+ // Use the debug right to know who is an administrator
+ const admins = await UserModel.listWithRight(UserRight.MANAGE_DEBUG)
+ if (admins.length === 0) return
+
+ logger.info('Notifying %s admins of new plugin version %s@%s.', admins.length, plugin.name, plugin.latestVersion)
+
+ function settingGetter (user: MUserWithNotificationSetting) {
+ return user.NotificationSetting.newPluginVersion
+ }
+
+ async function notificationCreator (user: MUserWithNotificationSetting) {
+ const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
+ type: UserNotificationType.NEW_PLUGIN_VERSION,
+ userId: user.id,
+ pluginId: plugin.id
+ })
+ notification.Plugin = plugin
+
+ return notification
+ }
+
+ function emailSender (emails: string[]) {
+ return Emailer.Instance.addNewPlugionVersionNotification(emails, plugin)
+ }
+
+ return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
+ }
+
private async notify<T extends MUserWithNotificationSetting> (options: {
users: T[]
notificationCreator: (user: T) => Promise<UserNotificationModelForApi>
-import { doRequest } from '../../helpers/requests'
-import { CONFIG } from '../../initializers/config'
+import { sanitizeUrl } from '@server/helpers/core-utils'
+import { ResultList } from '../../../shared/models'
+import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model'
+import { PeerTubePluginIndex } from '../../../shared/models/plugins/peertube-plugin-index.model'
import {
PeertubePluginLatestVersionRequest,
PeertubePluginLatestVersionResponse
} from '../../../shared/models/plugins/peertube-plugin-latest-version.model'
-import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model'
-import { ResultList } from '../../../shared/models'
-import { PeerTubePluginIndex } from '../../../shared/models/plugins/peertube-plugin-index.model'
-import { PluginModel } from '../../models/server/plugin'
-import { PluginManager } from './plugin-manager'
import { logger } from '../../helpers/logger'
+import { doJSONRequest } from '../../helpers/requests'
+import { CONFIG } from '../../initializers/config'
import { PEERTUBE_VERSION } from '../../initializers/constants'
-import { sanitizeUrl } from '@server/helpers/core-utils'
+import { PluginModel } from '../../models/server/plugin'
+import { PluginManager } from './plugin-manager'
async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) {
const { start = 0, count = 20, search, sort = 'npmName', pluginType } = options
- const qs: PeertubePluginIndexList = {
+ const searchParams: PeertubePluginIndexList & Record<string, string | number> = {
start,
count,
sort,
const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins'
try {
- const { body } = await doRequest<any>({ uri, qs, json: true })
+ const { body } = await doJSONRequest<any>(uri, { searchParams })
logger.debug('Got result from PeerTube index.', { body })
const uri = sanitizeUrl(CONFIG.PLUGINS.INDEX.URL) + '/api/v1/plugins/latest-version'
- const { body } = await doRequest<any>({ uri, body: bodyRequest, json: true, method: 'POST' })
+ const options = {
+ json: bodyRequest,
+ method: 'POST' as 'POST'
+ }
+ const { body } = await doJSONRequest<PeertubePluginLatestVersionResponse>(uri, options)
return body
}
VIDEO_PLAYLIST_PRIVACIES,
VIDEO_PRIVACIES
} from '@server/initializers/constants'
-import { onExternalUserAuthenticated } from '@server/lib/auth'
+import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth'
import { PluginModel } from '@server/models/server/plugin'
import {
RegisterServerAuthExternalOptions,
import { chunk } from 'lodash'
-import { doRequest } from '@server/helpers/requests'
+import { doJSONRequest } from '@server/helpers/requests'
import { JobQueue } from '@server/lib/job-queue'
import { ActorFollowModel } from '@server/models/activitypub/actor-follow'
import { getServerActor } from '@server/models/application/application'
try {
const serverActor = await getServerActor()
- const qs = { count: 1000 }
- if (this.lastCheck) Object.assign(qs, { since: this.lastCheck.toISOString() })
+ const searchParams = { count: 1000 }
+ if (this.lastCheck) Object.assign(searchParams, { since: this.lastCheck.toISOString() })
this.lastCheck = new Date()
- const { body } = await doRequest<any>({ uri: indexUrl, qs, json: true })
+ const { body } = await doJSONRequest<any>(indexUrl, { searchParams })
if (!body.data || Array.isArray(body.data) === false) {
logger.error('Cannot auto follow instances of index %s. Please check the auto follow URL.', indexUrl, { body })
return
--- /dev/null
+
+import { doJSONRequest } from '@server/helpers/requests'
+import { ApplicationModel } from '@server/models/application/application'
+import { compareSemVer } from '@shared/core-utils'
+import { JoinPeerTubeVersions } from '@shared/models'
+import { logger } from '../../helpers/logger'
+import { CONFIG } from '../../initializers/config'
+import { PEERTUBE_VERSION, SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
+import { Notifier } from '../notifier'
+import { AbstractScheduler } from './abstract-scheduler'
+
+export class PeerTubeVersionCheckScheduler extends AbstractScheduler {
+
+ private static instance: AbstractScheduler
+
+ protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.checkPeerTubeVersion
+
+ private constructor () {
+ super()
+ }
+
+ protected async internalExecute () {
+ return this.checkLatestVersion()
+ }
+
+ private async checkLatestVersion () {
+ if (CONFIG.PEERTUBE.CHECK_LATEST_VERSION.ENABLED === false) return
+
+ logger.info('Checking latest PeerTube version.')
+
+ const { body } = await doJSONRequest<JoinPeerTubeVersions>(CONFIG.PEERTUBE.CHECK_LATEST_VERSION.URL)
+
+ if (!body?.peertube?.latestVersion) {
+ logger.warn('Cannot check latest PeerTube version: body is invalid.', { body })
+ return
+ }
+
+ const latestVersion = body.peertube.latestVersion
+ const application = await ApplicationModel.load()
+
+ // Already checked this version
+ if (application.latestPeerTubeVersion === latestVersion) return
+
+ if (compareSemVer(PEERTUBE_VERSION, latestVersion) < 0) {
+ application.latestPeerTubeVersion = latestVersion
+ await application.save()
+
+ Notifier.Instance.notifyOfNewPeerTubeVersion(application, latestVersion)
+ }
+ }
+
+ static get Instance () {
+ return this.instance || (this.instance = new this())
+ }
+}
import { chunk } from 'lodash'
import { getLatestPluginsVersion } from '../plugins/plugin-index'
import { compareSemVer } from '../../../shared/core-utils/miscs/miscs'
+import { Notifier } from '../notifier'
export class PluginsCheckScheduler extends AbstractScheduler {
plugin.latestVersion = result.latestVersion
await plugin.save()
+ // Notify if there is an higher plugin version available
+ if (compareSemVer(plugin.version, result.latestVersion) < 0) {
+ Notifier.Instance.notifyOfNewPluginVersion(plugin)
+ }
+
logger.info('Plugin %s has a new latest version %s.', result.npmName, plugin.latestVersion)
}
}
newInstanceFollower: UserNotificationSettingValue.WEB,
abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
- autoInstanceFollowing: UserNotificationSettingValue.WEB
+ autoInstanceFollowing: UserNotificationSettingValue.WEB,
+ newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+ newPluginVersion: UserNotificationSettingValue.WEB
}
return UserNotificationSettingModel.create(values, { transaction: t })
} from '@server/types/models'
import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../shared/models'
import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
-import { logger } from '../helpers/logger'
+import { logger, loggerTagsFactory } from '../helpers/logger'
import { CONFIG } from '../initializers/config'
import { VideoBlacklistModel } from '../models/video/video-blacklist'
import { sendDeleteVideo } from './activitypub/send'
import { Notifier } from './notifier'
import { Hooks } from './plugins/hooks'
+const lTags = loggerTagsFactory('blacklist')
+
async function autoBlacklistVideoIfNeeded (parameters: {
video: MVideoWithBlacklistLight
user?: MUser
})
}
- logger.info('Video %s auto-blacklisted.', video.uuid)
+ logger.info('Video %s auto-blacklisted.', video.uuid, lTags(video.uuid))
return true
}
import * as express from 'express'
import { Socket } from 'socket.io'
-import { oAuthServer } from '@server/lib/auth'
-import { logger } from '../helpers/logger'
-import { getAccessToken } from '../lib/oauth-model'
+import { getAccessToken } from '@server/lib/auth/oauth-model'
import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
+import { logger } from '../helpers/logger'
+import { handleOAuthAuthenticate } from '../lib/auth/oauth'
function authenticate (req: express.Request, res: express.Response, next: express.NextFunction, authenticateInQuery = false) {
- const options = authenticateInQuery ? { allowBearerTokensInQueryString: true } : {}
+ handleOAuthAuthenticate(req, res, authenticateInQuery)
+ .then((token: any) => {
+ res.locals.oauth = { token }
+ res.locals.authenticated = true
- oAuthServer.authenticate(options)(req, res, err => {
- if (err) {
+ return next()
+ })
+ .catch(err => {
logger.warn('Cannot authenticate.', { err })
return res.status(err.status)
error: 'Token is invalid.',
code: err.name
})
- .end()
- }
-
- res.locals.authenticated = true
-
- return next()
- })
+ })
}
function authenticateSocket (socket: Socket, next: (err?: any) => void) {
export * from './validators'
export * from './activitypub'
export * from './async'
-export * from './oauth'
+export * from './auth'
export * from './pagination'
export * from './servers'
export * from './sort'
.custom(isSignatureValueValid).withMessage('Should have a valid signature value'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking activitypub signature parameter', { parameters: { signature: req.body.signature } })
+ logger.debug('Checking Linked Data Signature parameter', { parameters: { signature: req.body.signature } })
if (areValidationErrors(req, res)) return
import * as express from 'express'
import { param, query } from 'express-validator'
import { isValidJobState, isValidJobType } from '../../helpers/custom-validators/jobs'
-import { logger } from '../../helpers/logger'
+import { logger, loggerTagsFactory } from '../../helpers/logger'
import { areValidationErrors } from './utils'
+const lTags = loggerTagsFactory('validators', 'jobs')
+
const listJobsValidator = [
param('state')
.optional()
.custom(isValidJobType).withMessage('Should have a valid job state'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking listJobsValidator parameters.', { parameters: req.params })
+ logger.debug('Checking listJobsValidator parameters.', { parameters: req.params, ...lTags() })
if (areValidationErrors(req, res)) return
import { areValidationErrors } from './utils'
import { PAGINATION } from '@server/initializers/constants'
-const paginationValidator = [
- query('start')
- .optional()
- .isInt({ min: 0 }).withMessage('Should have a number start'),
- query('count')
- .optional()
- .isInt({ min: 0, max: PAGINATION.GLOBAL.COUNT.MAX }).withMessage(`Should have a number count (max: ${PAGINATION.GLOBAL.COUNT.MAX})`),
+const paginationValidator = paginationValidatorBuilder()
- (req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking pagination parameters', { parameters: req.query })
+function paginationValidatorBuilder (tags: string[] = []) {
+ return [
+ query('start')
+ .optional()
+ .isInt({ min: 0 }).withMessage('Should have a number start'),
+ query('count')
+ .optional()
+ .isInt({ min: 0, max: PAGINATION.GLOBAL.COUNT.MAX }).withMessage(`Should have a number count (max: ${PAGINATION.GLOBAL.COUNT.MAX})`),
- if (areValidationErrors(req, res)) return
+ (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking pagination parameters', { parameters: req.query, tags })
- return next()
- }
-]
+ if (areValidationErrors(req, res)) return
+
+ return next()
+ }
+ ]
+}
// ---------------------------------------------------------------------------
export {
- paginationValidator
+ paginationValidator,
+ paginationValidatorBuilder
}
const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
-const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS)
+const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS, [ 'jobs' ])
const abusesSortValidator = checkSort(SORTABLE_ABUSES_COLUMNS)
const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS)
return false
}
-function checkSort (sortableColumns: string[]) {
+function checkSort (sortableColumns: string[], tags: string[] = []) {
return [
query('sort').optional().isIn(sortableColumns).withMessage('Should have correct sortable column'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking sort parameters', { parameters: req.query })
+ logger.debug('Checking sort parameters', { parameters: req.query, tags })
if (areValidationErrors(req, res)) return
if (!acceptedResult || acceptedResult.accepted !== true) {
logger.info('Refused local comment.', { acceptedResult, acceptParameters })
res.status(HttpStatusCode.FORBIDDEN_403)
- .json({ error: acceptedResult.errorMessage || 'Refused local comment' })
+ .json({ error: acceptedResult?.errorMessage || 'Refused local comment' })
return false
}
import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
import { MVideoPlaylist } from '../../../types/models/video/video-playlist'
-import { authenticatePromiseIfNeeded } from '../../oauth'
+import { authenticatePromiseIfNeeded } from '../../auth'
import { areValidationErrors } from '../utils'
const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
import { Hooks } from '../../../lib/plugins/hooks'
import { AccountModel } from '../../../models/account/account'
import { VideoModel } from '../../../models/video/video'
-import { authenticatePromiseIfNeeded } from '../../oauth'
+import { authenticatePromiseIfNeeded } from '../../auth'
import { areValidationErrors } from '../utils'
const videosAddValidator = getCommonVideoEditAttributes().concat([
Table,
UpdatedAt
} from 'sequelize-typescript'
+import { TokensCache } from '@server/lib/auth/tokens-cache'
import { MNotificationSettingFormattable } from '@server/types/models'
import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
-import { clearCacheByUserId } from '../../lib/oauth-model'
import { throwIfNotValid } from '../utils'
import { UserModel } from './user'
@Column
abuseNewMessage: UserNotificationSettingValue
+ @AllowNull(false)
+ @Default(null)
+ @Is(
+ 'UserNotificationSettingNewPeerTubeVersion',
+ value => throwIfNotValid(value, isUserNotificationSettingValid, 'newPeerTubeVersion')
+ )
+ @Column
+ newPeerTubeVersion: UserNotificationSettingValue
+
+ @AllowNull(false)
+ @Default(null)
+ @Is(
+ 'UserNotificationSettingNewPeerPluginVersion',
+ value => throwIfNotValid(value, isUserNotificationSettingValid, 'newPluginVersion')
+ )
+ @Column
+ newPluginVersion: UserNotificationSettingValue
+
@ForeignKey(() => UserModel)
@Column
userId: number
@AfterUpdate
@AfterDestroy
static removeTokenCache (instance: UserNotificationSettingModel) {
- return clearCacheByUserId(instance.userId)
+ return TokensCache.Instance.clearCacheByUserId(instance.userId)
}
toFormattedJSON (this: MNotificationSettingFormattable): UserNotificationSetting {
newInstanceFollower: this.newInstanceFollower,
autoInstanceFollowing: this.autoInstanceFollowing,
abuseNewMessage: this.abuseNewMessage,
- abuseStateChange: this.abuseStateChange
+ abuseStateChange: this.abuseStateChange,
+ newPeerTubeVersion: this.newPeerTubeVersion,
+ newPluginVersion: this.newPluginVersion
}
}
}
import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
import { ActorModel } from '../activitypub/actor'
import { ActorFollowModel } from '../activitypub/actor-follow'
+import { ApplicationModel } from '../application/application'
import { AvatarModel } from '../avatar/avatar'
+import { PluginModel } from '../server/plugin'
import { ServerModel } from '../server/server'
import { getSort, throwIfNotValid } from '../utils'
import { VideoModel } from '../video/video'
attributes: [ 'id' ],
model: VideoAbuseModel.unscoped(),
required: false,
- include: [ buildVideoInclude(true) ]
+ include: [ buildVideoInclude(false) ]
},
{
attributes: [ 'id' ],
{
attributes: [ 'id', 'originCommentId' ],
model: VideoCommentModel.unscoped(),
- required: true,
+ required: false,
include: [
{
attributes: [ 'id', 'name', 'uuid' ],
model: VideoModel.unscoped(),
- required: true
+ required: false
}
]
}
{
model: AccountModel,
as: 'FlaggedAccount',
- required: true,
+ required: false,
include: [ buildActorWithAvatarInclude() ]
}
]
include: [ buildVideoInclude(false) ]
},
+ {
+ attributes: [ 'id', 'name', 'type', 'latestVersion' ],
+ model: PluginModel.unscoped(),
+ required: false
+ },
+
+ {
+ attributes: [ 'id', 'latestPeerTubeVersion' ],
+ model: ApplicationModel.unscoped(),
+ required: false
+ },
+
{
attributes: [ 'id', 'state' ],
model: ActorFollowModel.unscoped(),
[Op.ne]: null
}
}
+ },
+ {
+ fields: [ 'pluginId' ],
+ where: {
+ pluginId: {
+ [Op.ne]: null
+ }
+ }
+ },
+ {
+ fields: [ 'applicationId' ],
+ where: {
+ applicationId: {
+ [Op.ne]: null
+ }
+ }
}
] as (ModelIndexesOptions & { where?: WhereOptions })[]
})
})
ActorFollow: ActorFollowModel
+ @ForeignKey(() => PluginModel)
+ @Column
+ pluginId: number
+
+ @BelongsTo(() => PluginModel, {
+ foreignKey: {
+ allowNull: true
+ },
+ onDelete: 'cascade'
+ })
+ Plugin: PluginModel
+
+ @ForeignKey(() => ApplicationModel)
+ @Column
+ applicationId: number
+
+ @BelongsTo(() => ApplicationModel, {
+ foreignKey: {
+ allowNull: true
+ },
+ onDelete: 'cascade'
+ })
+ Application: ApplicationModel
+
static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
const where = { userId }
}
: undefined
+ const plugin = this.Plugin
+ ? {
+ name: this.Plugin.name,
+ type: this.Plugin.type,
+ latestVersion: this.Plugin.latestVersion
+ }
+ : undefined
+
+ const peertube = this.Application
+ ? { latestVersion: this.Application.latestPeerTubeVersion }
+ : undefined
+
return {
id: this.id,
type: this.type,
videoBlacklist,
account,
actorFollow,
+ plugin,
+ peertube,
createdAt: this.createdAt.toISOString(),
updatedAt: this.updatedAt.toISOString()
}
? {
threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
- video: {
- id: abuse.VideoCommentAbuse.VideoComment.Video.id,
- name: abuse.VideoCommentAbuse.VideoComment.Video.name,
- uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
- }
+ video: abuse.VideoCommentAbuse.VideoComment.Video
+ ? {
+ id: abuse.VideoCommentAbuse.VideoComment.Video.id,
+ name: abuse.VideoCommentAbuse.VideoComment.Video.name,
+ uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
+ }
+ : undefined
}
: undefined
const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined
- const accountAbuse = (!commentAbuse && !videoAbuse) ? this.formatActor(abuse.FlaggedAccount) : undefined
+ const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount) ? this.formatActor(abuse.FlaggedAccount) : undefined
return {
id: abuse.id,
Table,
UpdatedAt
} from 'sequelize-typescript'
+import { TokensCache } from '@server/lib/auth/tokens-cache'
import {
MMyUserFormattable,
MUser,
} from '../../helpers/custom-validators/users'
import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants'
-import { clearCacheByUserId } from '../../lib/oauth-model'
import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
import { ActorModel } from '../activitypub/actor'
import { ActorFollowModel } from '../activitypub/actor-follow'
@AfterUpdate
@AfterDestroy
static removeTokenCache (instance: UserModel) {
- return clearCacheByUserId(instance.id)
+ return TokensCache.Instance.clearCacheByUserId(instance.id)
}
static countTotal () {
@Column
migrationVersion: number
+ @AllowNull(true)
+ @Column
+ latestPeerTubeVersion: string
+
@HasOne(() => AccountModel, {
foreignKey: {
allowNull: true
Table,
UpdatedAt
} from 'sequelize-typescript'
+import { TokensCache } from '@server/lib/auth/tokens-cache'
+import { MUserAccountId } from '@server/types/models'
import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token'
import { logger } from '../../helpers/logger'
-import { clearCacheByToken } from '../../lib/oauth-model'
import { AccountModel } from '../account/account'
import { UserModel } from '../account/user'
import { ActorModel } from '../activitypub/actor'
client: {
id: number
}
- user: {
- id: number
- }
+ user: MUserAccountId
token: MOAuthTokenUser
}
@AfterUpdate
@AfterDestroy
static removeTokenCache (token: OAuthTokenModel) {
- return clearCacheByToken(token.accessToken)
+ return TokensCache.Instance.clearCacheByToken(token.accessToken)
}
static loadByRefreshToken (refreshToken: string) {
}
static deleteUserToken (userId: number, t?: Transaction) {
+ TokensCache.Instance.deleteUserToken(userId)
+
const query = {
where: {
userId
cleanupTests,
closeAllSequelize,
flushAndRunMultipleServers,
+ killallServers,
+ reRunServer,
ServerInfo,
setActorField,
wait
const expect = chai.expect
function setKeysOfServer (onServer: ServerInfo, ofServer: ServerInfo, publicKey: string, privateKey: string) {
+ const url = 'http://localhost:' + ofServer.port + '/accounts/peertube'
+
return Promise.all([
- setActorField(onServer.internalServerNumber, 'http://localhost:' + ofServer.port + '/accounts/peertube', 'publicKey', publicKey),
- setActorField(onServer.internalServerNumber, 'http://localhost:' + ofServer.port + '/accounts/peertube', 'privateKey', privateKey)
+ setActorField(onServer.internalServerNumber, url, 'publicKey', publicKey),
+ setActorField(onServer.internalServerNumber, url, 'privateKey', privateKey)
])
}
-function getAnnounceWithoutContext (server2: ServerInfo) {
+function setUpdatedAtOfServer (onServer: ServerInfo, ofServer: ServerInfo, updatedAt: string) {
+ const url = 'http://localhost:' + ofServer.port + '/accounts/peertube'
+
+ return Promise.all([
+ setActorField(onServer.internalServerNumber, url, 'createdAt', updatedAt),
+ setActorField(onServer.internalServerNumber, url, 'updatedAt', updatedAt)
+ ])
+}
+
+function getAnnounceWithoutContext (server: ServerInfo) {
const json = require('./json/peertube/announce-without-context.json')
const result: typeof json = {}
for (const key of Object.keys(json)) {
if (Array.isArray(json[key])) {
- result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`))
+ result[key] = json[key].map(v => v.replace(':9002', `:${server.port}`))
} else {
- result[key] = json[key].replace(':9002', `:${server2.port}`)
+ result[key] = json[key].replace(':9002', `:${server.port}`)
}
}
url = servers[0].url + '/inbox'
- await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
+ await setKeysOfServer(servers[0], servers[1], keys.publicKey, null)
+ await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
const to = { url: 'http://localhost:' + servers[0].port + '/accounts/peertube' }
const by = { url: 'http://localhost:' + servers[1].port + '/accounts/peertube', privateKey: keys.privateKey }
Digest: buildDigest({ hello: 'coucou' })
}
- const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ try {
+ await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
+ expect(true, 'Did not throw').to.be.false
+ } catch (err) {
+ expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ }
})
it('Should fail with an invalid date', async function () {
const headers = buildGlobalHeaders(body)
headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
- const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ try {
+ await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
+ expect(true, 'Did not throw').to.be.false
+ } catch (err) {
+ expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ }
})
it('Should fail with bad keys', async function () {
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
const headers = buildGlobalHeaders(body)
- const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ try {
+ await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
+ expect(true, 'Did not throw').to.be.false
+ } catch (err) {
+ expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ }
})
it('Should reject requests without appropriate signed headers', async function () {
for (const badHeaders of badHeadersMatrix) {
signatureOptions.headers = badHeaders
- const { response } = await makePOSTAPRequest(url, body, signatureOptions, headers)
- expect(response.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ try {
+ await makePOSTAPRequest(url, body, signatureOptions, headers)
+ expect(true, 'Did not throw').to.be.false
+ } catch (err) {
+ expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ }
}
})
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
const headers = buildGlobalHeaders(body)
- const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
+ const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
+ expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
})
it('Should refresh the actor keys', async function () {
this.timeout(20000)
- // Wait refresh invalidation
- await wait(10000)
-
// Update keys of server 2 to invalid keys
// Server 1 should refresh the actor and fail
await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
+ await setUpdatedAtOfServer(servers[0], servers[1], '2015-07-17 22:00:00+00')
+
+ // Invalid peertube actor cache
+ killallServers([ servers[1] ])
+ await reRunServer(servers[1])
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
const headers = buildGlobalHeaders(body)
- const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ try {
+ await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
+ expect(true, 'Did not throw').to.be.false
+ } catch (err) {
+ console.error(err)
+ expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ }
})
})
const headers = buildGlobalHeaders(signedBody)
- const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ try {
+ await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
+ expect(true, 'Did not throw').to.be.false
+ } catch (err) {
+ expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ }
})
it('Should fail with an altered body', async function () {
const headers = buildGlobalHeaders(signedBody)
- const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ try {
+ await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
+ expect(true, 'Did not throw').to.be.false
+ } catch (err) {
+ expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ }
})
it('Should succeed with a valid signature', async function () {
const headers = buildGlobalHeaders(signedBody)
- const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
+ const { statusCode } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
+ expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
})
it('Should refresh the actor keys', async function () {
const headers = buildGlobalHeaders(signedBody)
- const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
-
- expect(response.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ try {
+ await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
+ expect(true, 'Did not throw').to.be.false
+ } catch (err) {
+ expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
+ }
})
})
newInstanceFollower: UserNotificationSettingValue.WEB,
autoInstanceFollowing: UserNotificationSettingValue.WEB,
abuseNewMessage: UserNotificationSettingValue.WEB,
- abuseStateChange: UserNotificationSettingValue.WEB
+ abuseStateChange: UserNotificationSettingValue.WEB,
+ newPeerTubeVersion: UserNotificationSettingValue.WEB,
+ newPluginVersion: UserNotificationSettingValue.WEB
}
it('Should fail with missing fields', async function () {
})
it('Should succeed with no password on a server with smtp enabled', async function () {
- this.timeout(10000)
+ this.timeout(20000)
killallServers([ server ])
--- /dev/null
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
+import 'mocha'
+import { expect } from 'chai'
+import { MockJoinPeerTubeVersions } from '@shared/extra-utils/mock-servers/joinpeertube-versions'
+import { cleanupTests, installPlugin, setPluginLatestVersion, setPluginVersion, wait } from '../../../../shared/extra-utils'
+import { ServerInfo } from '../../../../shared/extra-utils/index'
+import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
+import {
+ CheckerBaseParams,
+ checkNewPeerTubeVersion,
+ checkNewPluginVersion,
+ prepareNotificationsTest
+} from '../../../../shared/extra-utils/users/user-notifications'
+import { UserNotification, UserNotificationType } from '../../../../shared/models/users'
+import { PluginType } from '@shared/models'
+
+describe('Test admin notifications', function () {
+ let server: ServerInfo
+ let userNotifications: UserNotification[] = []
+ let adminNotifications: UserNotification[] = []
+ let emails: object[] = []
+ let baseParams: CheckerBaseParams
+ let joinPeerTubeServer: MockJoinPeerTubeVersions
+
+ before(async function () {
+ this.timeout(120000)
+
+ const config = {
+ peertube: {
+ check_latest_version: {
+ enabled: true,
+ url: 'http://localhost:42102/versions.json'
+ }
+ },
+ plugins: {
+ index: {
+ enabled: true,
+ check_latest_versions_interval: '5 seconds'
+ }
+ }
+ }
+
+ const res = await prepareNotificationsTest(1, config)
+ emails = res.emails
+ server = res.servers[0]
+
+ userNotifications = res.userNotifications
+ adminNotifications = res.adminNotifications
+
+ baseParams = {
+ server: server,
+ emails,
+ socketNotifications: adminNotifications,
+ token: server.accessToken
+ }
+
+ await installPlugin({
+ url: server.url,
+ accessToken: server.accessToken,
+ npmName: 'peertube-plugin-hello-world'
+ })
+
+ await installPlugin({
+ url: server.url,
+ accessToken: server.accessToken,
+ npmName: 'peertube-theme-background-red'
+ })
+
+ joinPeerTubeServer = new MockJoinPeerTubeVersions()
+ await joinPeerTubeServer.initialize()
+ })
+
+ describe('Latest PeerTube version notification', function () {
+
+ it('Should not send a notification to admins if there is not a new version', async function () {
+ this.timeout(30000)
+
+ joinPeerTubeServer.setLatestVersion('1.4.2')
+
+ await wait(3000)
+ await checkNewPeerTubeVersion(baseParams, '1.4.2', 'absence')
+ })
+
+ it('Should send a notification to admins on new plugin version', async function () {
+ this.timeout(30000)
+
+ joinPeerTubeServer.setLatestVersion('15.4.2')
+
+ await wait(3000)
+ await checkNewPeerTubeVersion(baseParams, '15.4.2', 'presence')
+ })
+
+ it('Should not send the same notification to admins', async function () {
+ this.timeout(30000)
+
+ await wait(3000)
+ expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(1)
+ })
+
+ it('Should not have sent a notification to users', async function () {
+ this.timeout(30000)
+
+ expect(userNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(0)
+ })
+
+ it('Should send a new notification after a new release', async function () {
+ this.timeout(30000)
+
+ joinPeerTubeServer.setLatestVersion('15.4.3')
+
+ await wait(3000)
+ await checkNewPeerTubeVersion(baseParams, '15.4.3', 'presence')
+ expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
+ })
+ })
+
+ describe('Latest plugin version notification', function () {
+
+ it('Should not send a notification to admins if there is no new plugin version', async function () {
+ this.timeout(30000)
+
+ await wait(6000)
+ await checkNewPluginVersion(baseParams, PluginType.PLUGIN, 'hello-world', 'absence')
+ })
+
+ it('Should send a notification to admins on new plugin version', async function () {
+ this.timeout(30000)
+
+ await setPluginVersion(server.internalServerNumber, 'hello-world', '0.0.1')
+ await setPluginLatestVersion(server.internalServerNumber, 'hello-world', '0.0.1')
+ await wait(6000)
+
+ await checkNewPluginVersion(baseParams, PluginType.PLUGIN, 'hello-world', 'presence')
+ })
+
+ it('Should not send the same notification to admins', async function () {
+ this.timeout(30000)
+
+ await wait(6000)
+
+ expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PLUGIN_VERSION)).to.have.lengthOf(1)
+ })
+
+ it('Should not have sent a notification to users', async function () {
+ expect(userNotifications.filter(n => n.type === UserNotificationType.NEW_PLUGIN_VERSION)).to.have.lengthOf(0)
+ })
+
+ it('Should send a new notification after a new plugin release', async function () {
+ this.timeout(30000)
+
+ await setPluginVersion(server.internalServerNumber, 'hello-world', '0.0.1')
+ await setPluginLatestVersion(server.internalServerNumber, 'hello-world', '0.0.1')
+ await wait(6000)
+
+ expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
+ })
+ })
+
+ after(async function () {
+ MockSmtpServer.Instance.kill()
+
+ await cleanupTests([ server ])
+ })
+})
+import './admin-notifications'
import './comments-notifications'
import './moderation-notifications'
import './notifications-api'
for (let i = 0; i < 3; i++) {
await getVideo(servers[1].url, videoIdsServer1[i])
- await wait(1000)
await waitJobs([ servers[1] ])
+ await wait(1500)
}
for (const id of videoIdsServer1) {
import * as chai from 'chai'
import { AbuseState, AbuseUpdate, MyUser, User, UserRole, Video, VideoPlaylistType } from '@shared/models'
import { CustomConfig } from '@shared/models/server'
+import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
import {
addVideoCommentThread,
blockUser,
cleanupTests,
+ closeAllSequelize,
createUser,
deleteMe,
flushAndRunServer,
getVideoChannel,
getVideosList,
installPlugin,
+ killallServers,
login,
makePutBodyRequest,
rateVideo,
removeUser,
removeVideo,
reportAbuse,
+ reRunServer,
ServerInfo,
+ setTokenField,
testImage,
unblockUser,
updateAbuse,
waitJobs
} from '../../../../shared/extra-utils'
import { follow } from '../../../../shared/extra-utils/server/follows'
-import { logout, serverLogin, setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
+import { logout, refreshToken, setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
import { getMyVideos } from '../../../../shared/extra-utils/videos/videos'
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
-import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
const expect = chai.expect
const client = { id: 'client', secret: server.client.secret }
const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400)
+ expect(res.body.code).to.equal('invalid_client')
expect(res.body.error).to.contain('client is invalid')
})
const client = { id: server.client.id, secret: 'coucou' }
const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400)
+ expect(res.body.code).to.equal('invalid_client')
expect(res.body.error).to.contain('client is invalid')
})
})
const user = { username: 'captain crochet', password: server.user.password }
const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400)
+ expect(res.body.code).to.equal('invalid_grant')
expect(res.body.error).to.contain('credentials are invalid')
})
const user = { username: server.user.username, password: 'mew_three' }
const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400)
+ expect(res.body.code).to.equal('invalid_grant')
expect(res.body.error).to.contain('credentials are invalid')
})
})
it('Should be able to login again', async function () {
- server.accessToken = await serverLogin(server)
+ const res = await login(server.url, server.client, server.user)
+ server.accessToken = res.body.access_token
+ server.refreshToken = res.body.refresh_token
+ })
+
+ it('Should be able to get my user information again', async function () {
+ await getMyUserInformation(server.url, server.accessToken)
+ })
+
+ it('Should have an expired access token', async function () {
+ this.timeout(15000)
+
+ await setTokenField(server.internalServerNumber, server.accessToken, 'accessTokenExpiresAt', new Date().toISOString())
+ await setTokenField(server.internalServerNumber, server.accessToken, 'refreshTokenExpiresAt', new Date().toISOString())
+
+ killallServers([ server ])
+ await reRunServer(server)
+
+ await getMyUserInformation(server.url, server.accessToken, 401)
+ })
+
+ it('Should not be able to refresh an access token with an expired refresh token', async function () {
+ await refreshToken(server, server.refreshToken, 400)
})
- it('Should have an expired access token')
+ it('Should refresh the token', async function () {
+ this.timeout(15000)
+
+ const futureDate = new Date(new Date().getTime() + 1000 * 60).toISOString()
+ await setTokenField(server.internalServerNumber, server.accessToken, 'refreshTokenExpiresAt', futureDate)
- it('Should refresh the token')
+ killallServers([ server ])
+ await reRunServer(server)
+
+ const res = await refreshToken(server, server.refreshToken)
+ server.accessToken = res.body.access_token
+ server.refreshToken = res.body.refresh_token
+ })
it('Should be able to get my user information again', async function () {
await getMyUserInformation(server.url, server.accessToken)
})
after(async function () {
+ await closeAllSequelize([ server ])
await cleanupTests([ server ])
})
})
import './plugins'
import './print-transcode-command'
import './prune-storage'
+import './regenerate-thumbnails'
import './reset-password'
import './update-host'
--- /dev/null
+import 'mocha'
+import { expect } from 'chai'
+import { writeFile } from 'fs-extra'
+import { basename, join } from 'path'
+import { Video } from '@shared/models'
+import {
+ buildServerDirectory,
+ cleanupTests,
+ doubleFollow,
+ execCLI,
+ flushAndRunMultipleServers,
+ getEnvCli,
+ getVideo,
+ makeRawRequest,
+ ServerInfo,
+ setAccessTokensToServers,
+ uploadVideoAndGetId,
+ waitJobs
+} from '../../../shared/extra-utils'
+import { HttpStatusCode } from '@shared/core-utils'
+
+describe('Test regenerate thumbnails script', function () {
+ let servers: ServerInfo[]
+
+ let video1: Video
+ let video2: Video
+ let remoteVideo: Video
+
+ let thumbnail1Path: string
+ let thumbnailRemotePath: string
+
+ before(async function () {
+ this.timeout(60000)
+
+ servers = await flushAndRunMultipleServers(2)
+ await setAccessTokensToServers(servers)
+
+ await doubleFollow(servers[0], servers[1])
+
+ {
+ const videoUUID1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 1' })).uuid
+ video1 = await (getVideo(servers[0].url, videoUUID1).then(res => res.body))
+
+ thumbnail1Path = join(buildServerDirectory(servers[0], 'thumbnails'), basename(video1.thumbnailPath))
+
+ const videoUUID2 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 2' })).uuid
+ video2 = await (getVideo(servers[0].url, videoUUID2).then(res => res.body))
+ }
+
+ {
+ const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 3' })).uuid
+ await waitJobs(servers)
+
+ remoteVideo = await (getVideo(servers[0].url, videoUUID).then(res => res.body))
+
+ thumbnailRemotePath = join(buildServerDirectory(servers[0], 'thumbnails'), basename(remoteVideo.thumbnailPath))
+ }
+
+ await writeFile(thumbnail1Path, '')
+ await writeFile(thumbnailRemotePath, '')
+ })
+
+ it('Should have empty thumbnails', async function () {
+ {
+ const res = await makeRawRequest(join(servers[0].url, video1.thumbnailPath), HttpStatusCode.OK_200)
+ expect(res.body).to.have.lengthOf(0)
+ }
+
+ {
+ const res = await makeRawRequest(join(servers[0].url, video2.thumbnailPath), HttpStatusCode.OK_200)
+ expect(res.body).to.not.have.lengthOf(0)
+ }
+
+ {
+ const res = await makeRawRequest(join(servers[0].url, remoteVideo.thumbnailPath), HttpStatusCode.OK_200)
+ expect(res.body).to.have.lengthOf(0)
+ }
+ })
+
+ it('Should regenerate thumbnails from the CLI', async function () {
+ this.timeout(15000)
+
+ const env = getEnvCli(servers[0])
+ await execCLI(`${env} npm run regenerate-thumbnails`)
+ })
+
+ it('Should have regenerated thumbbnails', async function () {
+ {
+ const res1 = await makeRawRequest(join(servers[0].url, video1.thumbnailPath), HttpStatusCode.OK_200)
+ expect(res1.body).to.not.have.lengthOf(0)
+
+ const res2 = await makeRawRequest(join(servers[0].url, video1.previewPath), HttpStatusCode.OK_200)
+ expect(res2.body).to.not.have.lengthOf(0)
+ }
+
+ {
+ const res = await makeRawRequest(join(servers[0].url, video2.thumbnailPath), HttpStatusCode.OK_200)
+ expect(res.body).to.not.have.lengthOf(0)
+ }
+
+ {
+ const res = await makeRawRequest(join(servers[0].url, remoteVideo.thumbnailPath), HttpStatusCode.OK_200)
+ expect(res.body).to.have.lengthOf(0)
+ }
+ })
+
+ after(async function () {
+ await cleanupTests(servers)
+ })
+})
return result
}
})
+
+ registerHook({
+ target: 'filter:api.download.torrent.allowed.result',
+ handler: (result, params) => {
+ if (params && params.downloadName.includes('bad torrent')) {
+ return { allowed: false, errorMessage: 'Liu Bei' }
+ }
+
+ return result
+ }
+ })
+
+ registerHook({
+ target: 'filter:api.download.video.allowed.result',
+ handler: (result, params) => {
+ if (params && !params.streamingPlaylist && params.video.name.includes('bad file')) {
+ return { allowed: false, errorMessage: 'Cao Cao' }
+ }
+
+ if (params && params.streamingPlaylist && params.video.name.includes('bad playlist file')) {
+ return { allowed: false, errorMessage: 'Sun Jian' }
+ }
+
+ return result
+ }
+ })
+
+ registerHook({
+ target: 'filter:html.embed.video.allowed.result',
+ handler: (result, params) => {
+ return {
+ allowed: false,
+ html: 'Lu Bu'
+ }
+ }
+ })
+
+ registerHook({
+ target: 'filter:html.embed.video-playlist.allowed.result',
+ handler: (result, params) => {
+ return {
+ allowed: false,
+ html: 'Diao Chan'
+ }
+ }
+ })
+
+ {
+ const searchHooks = [
+ 'filter:api.search.videos.local.list.params',
+ 'filter:api.search.videos.local.list.result',
+ 'filter:api.search.videos.index.list.params',
+ 'filter:api.search.videos.index.list.result',
+ 'filter:api.search.video-channels.local.list.params',
+ 'filter:api.search.video-channels.local.list.result',
+ 'filter:api.search.video-channels.index.list.params',
+ 'filter:api.search.video-channels.index.list.result',
+ ]
+
+ for (const h of searchHooks) {
+ registerHook({
+ target: h,
+ handler: (obj) => {
+ peertubeHelpers.logger.debug('Run hook %s.', h)
+
+ return obj
+ }
+ })
+ }
+ }
}
async function unregister () {
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import 'mocha'
-import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
-import { get4KFileUrl, root, wait } from '../../../shared/extra-utils'
-import { join } from 'path'
-import { pathExists, remove } from 'fs-extra'
import { expect } from 'chai'
+import { pathExists, remove } from 'fs-extra'
+import { join } from 'path'
+import { get4KFileUrl, root, wait } from '../../../shared/extra-utils'
+import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
describe('Request helpers', function () {
const destPath1 = join(root(), 'test-output-1.txt')
it('Should throw an error when the bytes limit is exceeded for request', async function () {
try {
- await doRequest({ uri: get4KFileUrl() }, 3)
+ await doRequest(get4KFileUrl(), { bodyKBLimit: 3 })
} catch {
return
}
it('Should throw an error when the bytes limit is exceeded for request and save file', async function () {
try {
- await doRequestAndSaveToFile({ uri: get4KFileUrl() }, destPath1, 3)
+ await doRequestAndSaveToFile(get4KFileUrl(), destPath1, { bodyKBLimit: 3 })
} catch {
await wait(500)
})
it('Should succeed if the file is below the limit', async function () {
- await doRequest({ uri: get4KFileUrl() }, 5)
- await doRequestAndSaveToFile({ uri: get4KFileUrl() }, destPath2, 5)
+ await doRequest(get4KFileUrl(), { bodyKBLimit: 5 })
+ await doRequestAndSaveToFile(get4KFileUrl(), destPath2, { bodyKBLimit: 5 })
expect(await pathExists(destPath2)).to.be.true
})
await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
- await waitUntilLog(server, 'expired external auth token')
+ await waitUntilLog(server, 'expired external auth token', 2)
})
it('Should auto login Cyan, create the user and use the token', async function () {
import 'mocha'
import * as chai from 'chai'
+import { advancedVideoChannelSearch } from '@shared/extra-utils/search/video-channels'
import { ServerConfig } from '@shared/models'
+import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
import {
addVideoCommentReply,
addVideoCommentThread,
+ advancedVideosSearch,
createLive,
+ createVideoPlaylist,
doubleFollow,
getAccountVideos,
getConfig,
getVideo,
getVideoChannelVideos,
getVideoCommentThreads,
+ getVideoPlaylist,
getVideosList,
getVideosListPagination,
getVideoThreadComments,
getVideoWithToken,
installPlugin,
+ makeRawRequest,
registerUser,
setAccessTokensToServers,
setDefaultVideoChannel,
updateCustomSubConfig,
updateVideo,
uploadVideo,
+ uploadVideoAndGetId,
waitJobs
} from '../../../shared/extra-utils'
-import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers'
+import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers'
import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports'
-import { VideoDetails, VideoImport, VideoImportState, VideoPrivacy } from '../../../shared/models/videos'
+import {
+ VideoDetails,
+ VideoImport,
+ VideoImportState,
+ VideoPlaylist,
+ VideoPlaylistPrivacy,
+ VideoPrivacy
+} from '../../../shared/models/videos'
import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
-import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
const expect = chai.expect
})
})
+ describe('Download hooks', function () {
+ const downloadVideos: VideoDetails[] = []
+
+ before(async function () {
+ this.timeout(60000)
+
+ await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
+ transcoding: {
+ webtorrent: {
+ enabled: true
+ },
+ hls: {
+ enabled: true
+ }
+ }
+ })
+
+ const uuids: string[] = []
+
+ for (const name of [ 'bad torrent', 'bad file', 'bad playlist file' ]) {
+ const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: name })).uuid
+ uuids.push(uuid)
+ }
+
+ await waitJobs(servers)
+
+ for (const uuid of uuids) {
+ const res = await getVideo(servers[0].url, uuid)
+ downloadVideos.push(res.body)
+ }
+ })
+
+ it('Should run filter:api.download.torrent.allowed.result', async function () {
+ const res = await makeRawRequest(downloadVideos[0].files[0].torrentDownloadUrl, 403)
+ expect(res.body.error).to.equal('Liu Bei')
+
+ await makeRawRequest(downloadVideos[1].files[0].torrentDownloadUrl, 200)
+ await makeRawRequest(downloadVideos[2].files[0].torrentDownloadUrl, 200)
+ })
+
+ it('Should run filter:api.download.video.allowed.result', async function () {
+ {
+ const res = await makeRawRequest(downloadVideos[1].files[0].fileDownloadUrl, 403)
+ expect(res.body.error).to.equal('Cao Cao')
+
+ await makeRawRequest(downloadVideos[0].files[0].fileDownloadUrl, 200)
+ await makeRawRequest(downloadVideos[2].files[0].fileDownloadUrl, 200)
+ }
+
+ {
+ const res = await makeRawRequest(downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl, 403)
+ expect(res.body.error).to.equal('Sun Jian')
+
+ await makeRawRequest(downloadVideos[2].files[0].fileDownloadUrl, 200)
+
+ await makeRawRequest(downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl, 200)
+ await makeRawRequest(downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl, 200)
+ }
+ })
+ })
+
+ describe('Embed filters', function () {
+ const embedVideos: VideoDetails[] = []
+ const embedPlaylists: VideoPlaylist[] = []
+
+ before(async function () {
+ this.timeout(60000)
+
+ await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
+ transcoding: {
+ enabled: false
+ }
+ })
+
+ for (const name of [ 'bad embed', 'good embed' ]) {
+ {
+ const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: name })).uuid
+ const res = await getVideo(servers[0].url, uuid)
+ embedVideos.push(res.body)
+ }
+
+ {
+ const playlistAttrs = { displayName: name, videoChannelId: servers[0].videoChannel.id, privacy: VideoPlaylistPrivacy.PUBLIC }
+ const res = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
+
+ const resPlaylist = await getVideoPlaylist(servers[0].url, res.body.videoPlaylist.id)
+ embedPlaylists.push(resPlaylist.body)
+ }
+ }
+ })
+
+ it('Should run filter:html.embed.video.allowed.result', async function () {
+ const res = await makeRawRequest(servers[0].url + embedVideos[0].embedPath, 200)
+ expect(res.text).to.equal('Lu Bu')
+ })
+
+ it('Should run filter:html.embed.video-playlist.allowed.result', async function () {
+ const res = await makeRawRequest(servers[0].url + embedPlaylists[0].embedPath, 200)
+ expect(res.text).to.equal('Diao Chan')
+ })
+ })
+
+ describe('Search filters', function () {
+
+ before(async function () {
+ await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
+ search: {
+ searchIndex: {
+ enabled: true,
+ isDefaultSearch: false,
+ disableLocalSearch: false
+ }
+ }
+ })
+ })
+
+ it('Should run filter:api.search.videos.local.list.{params,result}', async function () {
+ await advancedVideosSearch(servers[0].url, {
+ search: 'Sun Quan'
+ })
+
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.local.list.params', 1)
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.local.list.result', 1)
+ })
+
+ it('Should run filter:api.search.videos.index.list.{params,result}', async function () {
+ await advancedVideosSearch(servers[0].url, {
+ search: 'Sun Quan',
+ searchTarget: 'search-index'
+ })
+
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.local.list.params', 1)
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.local.list.result', 1)
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.index.list.params', 1)
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.videos.index.list.result', 1)
+ })
+
+ it('Should run filter:api.search.video-channels.local.list.{params,result}', async function () {
+ await advancedVideoChannelSearch(servers[0].url, {
+ search: 'Sun Ce'
+ })
+
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.local.list.params', 1)
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.local.list.result', 1)
+ })
+
+ it('Should run filter:api.search.video-channels.index.list.{params,result}', async function () {
+ await advancedVideoChannelSearch(servers[0].url, {
+ search: 'Sun Ce',
+ searchTarget: 'search-index'
+ })
+
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.local.list.params', 1)
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.local.list.result', 1)
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.index.list.params', 1)
+ await waitUntilLog(servers[0], 'Run hook filter:api.search.video-channels.index.list.result', 1)
+ })
+ })
+
after(async function () {
await cleanupTests(servers)
})
if (videoInfo.thumbnail) {
thumbnailfile = join(cwd, sha256(videoInfo.thumbnail) + '.jpg')
- await doRequestAndSaveToFile({
- method: 'GET',
- uri: videoInfo.thumbnail
- }, thumbnailfile)
+ await doRequestAndSaveToFile(videoInfo.thumbnail, thumbnailfile)
}
const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo)
--- /dev/null
+import { ApplicationModel } from '@server/models/application/application'
+
+// ############################################################################
+
+export type MApplication = Omit<ApplicationModel, 'Account'>
--- /dev/null
+export * from './application'
export * from './account'
+export * from './application'
export * from './moderation'
export * from './oauth'
export * from './server'
import { VideoAbuseModel } from '@server/models/abuse/video-abuse'
import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse'
+import { ApplicationModel } from '@server/models/application/application'
+import { PluginModel } from '@server/models/server/plugin'
import { PickWith, PickWithOpt } from '@shared/core-utils'
import { AbuseModel } from '../../../models/abuse/abuse'
import { AccountModel } from '../../../models/account/account'
Pick<ActorFollowModel, 'id' | 'state'> &
PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> &
PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing>
+
+ export type PluginInclude =
+ Pick<PluginModel, 'id' | 'name' | 'type' | 'latestVersion'>
+
+ export type ApplicationInclude =
+ Pick<ApplicationModel, 'latestPeerTubeVersion'>
}
// ############################################################################
export type MUserNotification =
Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'Abuse' | 'VideoBlacklist' |
- 'VideoImport' | 'Account' | 'ActorFollow'>
+ 'VideoImport' | 'Account' | 'ActorFollow' | 'Plugin' | 'Application'>
// ############################################################################
Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> &
Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> &
Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> &
+ Use<'Plugin', UserNotificationIncludes.PluginInclude> &
+ Use<'Application', UserNotificationIncludes.ApplicationInclude> &
Use<'Account', UserNotificationIncludes.AccountIncludeActor>
import { MVideoImportDefault } from '@server/types/models/video/video-import'
import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/types/models/video/video-playlist-element'
import { MAccountVideoRateAccountVideo } from '@server/types/models/video/video-rate'
-import { UserRole } from '@shared/models'
import { RegisteredPlugin } from '../../lib/plugins/plugin-manager'
import {
MAccountDefault,
}
interface PeerTubeLocals {
- bypassLogin?: {
- bypass: boolean
- pluginName: string
- authName?: string
- user: {
- username: string
- email: string
- displayName: string
- role: UserRole
- }
- }
-
- refreshTokenAuthName?: string
-
- explicitLogout?: boolean
-
videoAll?: MVideoFullLight
onlyImmutableVideo?: MVideoImmutable
onlyVideo?: MVideoThumbnail
export * from './bulk/bulk'
export * from './cli/cli'
export * from './feeds/feeds'
-export * from './instances-index/mock-instances-index'
+export * from './mock-servers/mock-instances-index'
export * from './miscs/miscs'
export * from './miscs/sql'
export * from './miscs/stubs'
}
}
-function setPluginVersion (internalServerNumber: number, pluginName: string, newVersion: string) {
+function setPluginField (internalServerNumber: number, pluginName: string, field: string, value: string) {
const seq = getSequelize(internalServerNumber)
const options = { type: QueryTypes.UPDATE }
- return seq.query(`UPDATE "plugin" SET "version" = '${newVersion}' WHERE "name" = '${pluginName}'`, options)
+ return seq.query(`UPDATE "plugin" SET "${field}" = '${value}' WHERE "name" = '${pluginName}'`, options)
+}
+
+function setPluginVersion (internalServerNumber: number, pluginName: string, newVersion: string) {
+ return setPluginField(internalServerNumber, pluginName, 'version', newVersion)
+}
+
+function setPluginLatestVersion (internalServerNumber: number, pluginName: string, newVersion: string) {
+ return setPluginField(internalServerNumber, pluginName, 'latestVersion', newVersion)
}
function setActorFollowScores (internalServerNumber: number, newScore: number) {
return seq.query(`UPDATE "actorFollow" SET "score" = ${newScore}`, options)
}
+function setTokenField (internalServerNumber: number, accessToken: string, field: string, value: string) {
+ const seq = getSequelize(internalServerNumber)
+
+ const options = { type: QueryTypes.UPDATE }
+
+ return seq.query(`UPDATE "oAuthToken" SET "${field}" = '${value}' WHERE "accessToken" = '${accessToken}'`, options)
+}
+
export {
setVideoField,
setPlaylistField,
setActorField,
countVideoViewsOf,
setPluginVersion,
+ setPluginLatestVersion,
selectQuery,
deleteAll,
+ setTokenField,
updateQuery,
setActorFollowScores,
closeAllSequelize,
--- /dev/null
+import * as express from 'express'
+
+export class MockJoinPeerTubeVersions {
+ private latestVersion: string
+
+ initialize () {
+ return new Promise<void>(res => {
+ const app = express()
+
+ app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url)
+
+ return next()
+ })
+
+ app.get('/versions.json', (req: express.Request, res: express.Response) => {
+ return res.json({
+ peertube: {
+ latestVersion: this.latestVersion
+ }
+ })
+ })
+
+ app.listen(42102, () => res())
+ })
+ }
+
+ setLatestVersion (latestVersion: string) {
+ this.latestVersion = latestVersion
+ }
+}
function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) {
const options = {
- method: 'POST',
- uri: url,
+ method: 'POST' as 'POST',
json: body,
httpSignature,
headers
}
- return doRequest(options)
+ return doRequest(url, options)
}
async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) {
const follow = {
type: 'Follow',
- id: by.url + '/toto',
+ id: by.url + '/' + new Date().getTime(),
actor: by.url,
object: to.url
}
}
const headers = buildGlobalHeaders(body)
- return makePOSTAPRequest(to.url, body, httpSignature, headers)
+ return makePOSTAPRequest(to.url + '/inbox', body, httpSignature, headers)
}
export {
customConfigFile?: string
accessToken?: string
+ refreshToken?: string
videoChannel?: VideoChannel
video?: {
import { expect } from 'chai'
import { inspect } from 'util'
-import { AbuseState } from '@shared/models'
+import { AbuseState, PluginType } from '@shared/models'
+import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users'
import { MockSmtpServer } from '../miscs/email'
import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
import { getUserNotificationSocket } from '../socket/socket-io'
import { setAccessTokensToServers, userLogin } from './login'
import { createUser, getMyUserInformation } from './users'
-import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
function updateMyNotificationSettings (
url: string,
await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence')
}
-function getAllNotificationsSettings () {
+async function checkNewPeerTubeVersion (base: CheckerBaseParams, latestVersion: string, type: CheckerType) {
+ const notificationType = UserNotificationType.NEW_PEERTUBE_VERSION
+
+ function notificationChecker (notification: UserNotification, type: CheckerType) {
+ if (type === 'presence') {
+ expect(notification).to.not.be.undefined
+ expect(notification.type).to.equal(notificationType)
+
+ expect(notification.peertube).to.exist
+ expect(notification.peertube.latestVersion).to.equal(latestVersion)
+ } else {
+ expect(notification).to.satisfy((n: UserNotification) => {
+ return n === undefined || n.peertube === undefined || n.peertube.latestVersion !== latestVersion
+ })
+ }
+ }
+
+ function emailNotificationFinder (email: object) {
+ const text = email['text']
+
+ return text.includes(latestVersion)
+ }
+
+ await checkNotification(base, notificationChecker, emailNotificationFinder, type)
+}
+
+async function checkNewPluginVersion (base: CheckerBaseParams, pluginType: PluginType, pluginName: string, type: CheckerType) {
+ const notificationType = UserNotificationType.NEW_PLUGIN_VERSION
+
+ function notificationChecker (notification: UserNotification, type: CheckerType) {
+ if (type === 'presence') {
+ expect(notification).to.not.be.undefined
+ expect(notification.type).to.equal(notificationType)
+
+ expect(notification.plugin.name).to.equal(pluginName)
+ expect(notification.plugin.type).to.equal(pluginType)
+ } else {
+ expect(notification).to.satisfy((n: UserNotification) => {
+ return n === undefined || n.plugin === undefined || n.plugin.name !== pluginName
+ })
+ }
+ }
+
+ function emailNotificationFinder (email: object) {
+ const text = email['text']
+
+ return text.includes(pluginName)
+ }
+
+ await checkNotification(base, notificationChecker, emailNotificationFinder, type)
+}
+
+function getAllNotificationsSettings (): UserNotificationSetting {
return {
newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
- autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
- } as UserNotificationSetting
+ autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+ newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+ newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
+ }
}
-async function prepareNotificationsTest (serversCount = 3) {
+async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: any = {}) {
const userNotifications: UserNotification[] = []
const adminNotifications: UserNotification[] = []
const adminNotificationsServer2: UserNotification[] = []
limit: 20
}
}
- const servers = await flushAndRunMultipleServers(serversCount, overrideConfig)
+ const servers = await flushAndRunMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg))
await setAccessTokensToServers(servers)
checkNewInstanceFollower,
prepareNotificationsTest,
checkNewCommentAbuseForModerators,
- checkNewAccountAbuseForModerators
+ checkNewAccountAbuseForModerators,
+ checkNewPeerTubeVersion,
+ checkNewPluginVersion
}
export * from './users'
export * from './videos'
export * from './feeds'
+export * from './joinpeertube'
export * from './overviews'
export * from './plugins'
export * from './search'
--- /dev/null
+export * from './versions.model'
--- /dev/null
+export interface JoinPeerTubeVersions {
+ peertube: {
+ latestVersion: string
+ }
+}
// Fired when the registration page is being initialized
'action:signup.register.init': true,
+ // Fired when the video upload page is being initalized
+ 'action:video-upload.init': true,
+ // Fired when the video import by URL page is being initalized
+ 'action:video-url-import.init': true,
+ // Fired when the video import by torrent/magnet URI page is being initalized
+ 'action:video-torrent-import.init': true,
+ // Fired when the "Go Live" page is being initalized
+ 'action:go-live.init': true,
+
+ // Fired when the user explicitely logged in/logged out
+ 'action:auth-user.logged-in': true,
+ 'action:auth-user.logged-out': true,
+ // Fired when the application loaded user information (using tokens from the local storage or after a successful login)
+ 'action:auth-user.information-loaded': true,
+
+ // Fired when the modal to download a video/caption is shown
+ 'action:modal.video-download.shown': true,
+
// ####### Embed hooks #######
- // In embed scope, peertube helpers are not available
+ // /!\ In embed scope, peertube helpers are not available
+ // ###########################
// Fired when the embed loaded the player
'action:embed.player.loaded': true
'filter:api.user.me.videos.list.params': true,
'filter:api.user.me.videos.list.result': true,
+ // Filter params/results to search videos/channels in the DB or on the remote index
+ 'filter:api.search.videos.local.list.params': true,
+ 'filter:api.search.videos.local.list.result': true,
+ 'filter:api.search.videos.index.list.params': true,
+ 'filter:api.search.videos.index.list.result': true,
+ 'filter:api.search.video-channels.local.list.params': true,
+ 'filter:api.search.video-channels.local.list.result': true,
+ 'filter:api.search.video-channels.index.list.params': true,
+ 'filter:api.search.video-channels.index.list.result': true,
+
// Filter the result of the get function
// Used to get detailed video information (video watch page for example)
'filter:api.video.get.result': true,
'filter:video.auto-blacklist.result': true,
// Filter result used to check if a user can register on the instance
- 'filter:api.user.signup.allowed.result': true
+ 'filter:api.user.signup.allowed.result': true,
+
+ // Filter result used to check if video/torrent download is allowed
+ 'filter:api.download.video.allowed.result': true,
+ 'filter:api.download.torrent.allowed.result': true,
+
+ // Filter result to check if the embed is allowed for a particular request
+ 'filter:html.embed.video.allowed.result': true,
+ 'filter:html.embed.video-playlist.allowed.result': true
}
export type ServerFilterHookName = keyof typeof serverFilterHookObject
-export type SendEmailOptions = {
- to: string[]
+type From = string | { name?: string, address: string }
- template?: string
+interface Base extends Partial<SendEmailDefaultMessageOptions> {
+ to: string[] | string
+}
+
+interface MailTemplate extends Base {
+ template: string
locals?: { [key: string]: any }
+ text?: undefined
+}
+
+interface MailText extends Base {
+ text: string
- // override defaults
- subject?: string
- text?: string
- from?: string | { name?: string, address: string }
- replyTo?: string
+ locals?: Partial<SendEmailDefaultLocalsOptions> & {
+ title?: string
+ action?: {
+ url: string
+ text: string
+ }
+ }
}
+
+interface SendEmailDefaultLocalsOptions {
+ instanceName: string
+ text: string
+ subject: string
+}
+
+interface SendEmailDefaultMessageOptions {
+ to: string[] | string
+ from: From
+ subject: string
+ replyTo: string
+}
+
+export type SendEmailDefaultOptions = {
+ template: 'common'
+
+ message: SendEmailDefaultMessageOptions
+
+ locals: SendEmailDefaultLocalsOptions & {
+ WEBSERVER: any
+ EMAIL: any
+ }
+}
+
+export type SendEmailOptions = MailTemplate | MailText
export type ActivitypubHttpUnicastPayload = {
uri: string
signatureActorId?: number
- body: any
+ body: object
contextType?: ContextType
}
abuseStateChange: UserNotificationSettingValue
abuseNewMessage: UserNotificationSettingValue
+
+ newPeerTubeVersion: UserNotificationSettingValue
+ newPluginVersion: UserNotificationSettingValue
}
import { FollowState } from '../actors'
import { AbuseState } from '../moderation'
+import { PluginType } from '../plugins'
-export enum UserNotificationType {
+export const enum UserNotificationType {
NEW_VIDEO_FROM_SUBSCRIPTION = 1,
NEW_COMMENT_ON_MY_VIDEO = 2,
NEW_ABUSE_FOR_MODERATORS = 3,
ABUSE_STATE_CHANGE = 15,
- ABUSE_NEW_MESSAGE = 16
+ ABUSE_NEW_MESSAGE = 16,
+
+ NEW_PLUGIN_VERSION = 17,
+ NEW_PEERTUBE_VERSION = 18
}
export interface VideoInfo {
}
}
+ plugin?: {
+ name: string
+ type: PluginType
+ latestVersion: string
+ }
+
+ peertube?: {
+ latestVersion: string
+ }
+
createdAt: string
updatedAt: string
}
* Check the release is okay: https://github.com/Chocobozzz/PeerTube/releases
* Update https://peertube3.cpy.re and check it works correctly
* Update all other instances and check it works correctly
- * Communicate
+ * After a couple of days, update https://joinpeertube.org/api/v1/versions.json
- [Custom Modal](#custom-modal)
- [Translate](#translate)
- [Get public settings](#get-public-settings)
+ - [Get server config](#get-server-config)
- [Add custom fields to video form](#add-custom-fields-to-video-form)
- [Publishing](#publishing)
- [Write a plugin/theme](#write-a-plugintheme)
})
```
+#### Get server config
+
+```js
+peertubeHelpers.getServerConfig()
+ .then(config => {
+ console.log('Fetched server config.', config)
+ })
+```
+
#### Add custom fields to video form
To add custom fields in the video form (in *Plugin settings* tab):
- [peertube-redundancy.js](#peertube-redundancyjs)
- [Server tools](#server-tools)
- [parse-log](#parse-log)
+ - [regenerate-thumbnails.js](#regenerate-thumbnailsjs)
- [create-transcoding-job.js](#create-transcoding-jobjs)
- [create-import-video-file-job.js](#create-import-video-file-jobjs)
- [prune-storage.js](#prune-storagejs)
`--level` is optional and could be `info`/`warn`/`error`
+You can also remove SQL or HTTP logs using `--not-tags`:
+
+```
+$ cd /var/www/peertube/peertube-latest
+$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run parse-log -- --level debug --not-tags http sql
+```
+
+### regenerate-thumbnails.js
+
+Regenerating local video thumbnails could be useful because new PeerTube releases may increase thumbnail sizes:
+
+```
+$ cd /var/www/peertube/peertube-latest
+$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run regenerate-thumbnails
+```
+
### create-transcoding-job.js
You can use this script to force transcoding of an existing video. PeerTube needs to be running.
Environment=NODE_CONFIG_DIR=/var/www/peertube/config
User=peertube
Group=peertube
-ExecStart=/usr/bin/npm start
+ExecStart=/usr/bin/node dist/server
WorkingDirectory=/var/www/peertube/peertube-latest
StandardOutput=syslog
StandardError=syslog
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13":
- version "7.13.8"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.8.tgz#10b2dac78526424dfc1f47650d0e415dfd9dc481"
- integrity sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1"
+ integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.6.0", "@babel/parser@^7.9.6":
- version "7.13.9"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.9.tgz#ca34cb95e1c2dd126863a84465ae8ef66114be99"
- integrity sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw==
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.10.tgz#8f8f9bf7b3afa3eabd061f7a5bcdf4fec3c48409"
+ integrity sha512-0s7Mlrw9uTWkYua7xWr99Wpk2bnGa0ANleKfksYAES8LpWH4gW1OUr42vqKNf0us5UQNfru2wPqMqRITzq/SIQ==
"@babel/runtime@^7.7.2":
- version "7.13.9"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.9.tgz#97dbe2116e2630c489f22e0656decd60aaa1fcee"
- integrity sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
+ integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
dependencies:
regenerator-runtime "^0.13.4"
tlds "^1.218.0"
"@mapbox/node-pre-gyp@^1.0.0":
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.0.tgz#2b809e701da0f6729b47fe78ad4b9dc187a7d2e5"
- integrity sha512-mEaiD1CURETR/dBIiJAwz0M0Q0mH3gCW4pPMaIlNt97mdzYUVeqGcTJSamgJpS6Tg4tBHDrOJpjdh5fJTLnyNQ==
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.1.tgz#1b23a8decb5e6356b04770d586067d2bff2703dd"
+ integrity sha512-CUBdThIZMoLEQQxACwhLsPg/puxBca0abTH3ixuvBQkhjJ80Hdp99jmVjxFCOa52/tZqN9d70IbGUf+OuKDHGA==
dependencies:
detect-libc "^1.0.3"
http-proxy-agent "^4.0.1"
- mkdirp "^1.0.4"
+ make-dir "^3.1.0"
node-fetch "^2.6.1"
nopt "^5.0.0"
npmlog "^4.1.2"
semver "^7.3.4"
tar "^6.1.0"
-"@nestjs/common@7.6.13":
- version "7.6.13"
- resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-7.6.13.tgz#597558afedfddeb5021fe8a154327ee082279ab8"
- integrity sha512-xijw6so4yA8Ywi8mnrA7Kz97ZC36u20Eyb5/XvmokdLcgTcTyHVdE39r44JYnjHPf8SKZoJ965zdu/fKl4s4GQ==
+"@nestjs/common@7.6.14":
+ version "7.6.14"
+ resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-7.6.14.tgz#abdad360ef107482345b111eeee74fbef00620c9"
+ integrity sha512-XJrGoGttCsIOvG2+EXl09pl9iCmYXnhPjx3ndPPigMRdXQGLVpF38OdzroWTD7aYU5rHo3Co21G9cYl8aqdt2Q==
dependencies:
axios "0.21.1"
iterare "1.2.1"
tslib "2.1.0"
uuid "8.3.2"
-"@nestjs/core@7.6.13":
- version "7.6.13"
- resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-7.6.13.tgz#b7518dceb436e6ed2c1fad2cff86ddf69b143e73"
- integrity sha512-8oY8yZSgri2DngqmvBMtwYw1GIAaXbUXS7Y0mp/iSZ6jP7CQqYCybdcMPneunrt5PG8rtJsq6n+4JNRvxXrVmA==
+"@nestjs/core@7.6.14":
+ version "7.6.14"
+ resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-7.6.14.tgz#b3be15506aee33b847abce993a7371439b292dd9"
+ integrity sha512-iAeQIsC79xcLTpga3he48ROX4g561VFsfbksicqotrFy0k9czKxVtHxevsnwo8KzFsYXQqOCO6XYI8MsuAjMcg==
dependencies:
"@nuxtjs/opencollective" "0.3.2"
fast-safe-stringify "2.0.7"
node-fetch "^2.6.1"
"@openapitools/openapi-generator-cli@^2.1.4":
- version "2.1.26"
- resolved "https://registry.yarnpkg.com/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.1.26.tgz#69108458c0c1a0a3964d9b3e2f0360b195c8ea5f"
- integrity sha512-wr4LHQCoZCvLhf0/UY/9AZYTVi3nWvvOT+/JFjZYWDA/TIqC4eWxPjzM5tnSzGed6gBTuNHEh8gUonDz6WOZDw==
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.2.2.tgz#12b2171a0404731e35aa89a2e0c146186480f51c"
+ integrity sha512-Hl0/5bvv/ETYFuPpTPXqAtChHE2+lLrH0ATl8MtNDxtdXRLoQGCeT8jdT600VvCqJToRkNvQ1JPHbcg/hehyBw==
dependencies:
- "@nestjs/common" "7.6.13"
- "@nestjs/core" "7.6.13"
+ "@nestjs/common" "7.6.14"
+ "@nestjs/core" "7.6.14"
"@nuxtjs/opencollective" "0.3.2"
chalk "4.1.0"
commander "6.2.1"
"@types/express" "*"
"@types/node@*", "@types/node@>=10.0.0", "@types/node@^14.14.28", "@types/node@^14.14.31":
- version "14.14.31"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
- integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
+ version "14.14.34"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.34.tgz#07935194fc049069a1c56c0c274265abeddf88da"
+ integrity sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA==
"@types/nodemailer@^6.2.0":
version "6.4.0"
"@types/node" "*"
"@types/qs@*":
- version "6.9.5"
- resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b"
- integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==
+ version "6.9.6"
+ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1"
+ integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==
"@types/range-parser@*":
version "1.2.3"
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^4.8.1":
- version "4.16.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.16.1.tgz#2caf6a79dd19c3853b8d39769a27fccb24e4e651"
- integrity sha512-SK777klBdlkUZpZLC1mPvyOWk9yAFCWmug13eAjVQ4/Q1LATE/NbcQL1xDHkptQkZOLnPmLUA1Y54m8dqYwnoQ==
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.17.0.tgz#6f856eca4e6a52ce9cf127dfd349096ad936aa2d"
+ integrity sha512-/fKFDcoHg8oNan39IKFOb5WmV7oWhQe1K6CDaAVfJaNWEhmfqlA24g+u1lqU5bMH7zuNasfMId4LaYWC5ijRLw==
dependencies:
- "@typescript-eslint/experimental-utils" "4.16.1"
- "@typescript-eslint/scope-manager" "4.16.1"
+ "@typescript-eslint/experimental-utils" "4.17.0"
+ "@typescript-eslint/scope-manager" "4.17.0"
debug "^4.1.1"
functional-red-black-tree "^1.0.1"
lodash "^4.17.15"
semver "^7.3.2"
tsutils "^3.17.1"
-"@typescript-eslint/experimental-utils@4.16.1":
- version "4.16.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.16.1.tgz#da7a396dc7d0e01922acf102b76efff17320b328"
- integrity sha512-0Hm3LSlMYFK17jO4iY3un1Ve9x1zLNn4EM50Lia+0EV99NdbK+cn0er7HC7IvBA23mBg3P+8dUkMXy4leL33UQ==
+"@typescript-eslint/experimental-utils@4.17.0":
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.17.0.tgz#762c44aaa1a6a3c05b6d63a8648fb89b89f84c80"
+ integrity sha512-ZR2NIUbnIBj+LGqCFGQ9yk2EBQrpVVFOh9/Kd0Lm6gLpSAcCuLLe5lUCibKGCqyH9HPwYC0GIJce2O1i8VYmWA==
dependencies:
"@types/json-schema" "^7.0.3"
- "@typescript-eslint/scope-manager" "4.16.1"
- "@typescript-eslint/types" "4.16.1"
- "@typescript-eslint/typescript-estree" "4.16.1"
+ "@typescript-eslint/scope-manager" "4.17.0"
+ "@typescript-eslint/types" "4.17.0"
+ "@typescript-eslint/typescript-estree" "4.17.0"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"
"@typescript-eslint/parser@^4.0.0":
- version "4.16.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.16.1.tgz#3bbd3234dd3c5b882b2bcd9899bc30e1e1586d2a"
- integrity sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.17.0.tgz#141b647ffc72ebebcbf9b0fe6087f65b706d3215"
+ integrity sha512-KYdksiZQ0N1t+6qpnl6JeK9ycCFprS9xBAiIrw4gSphqONt8wydBw4BXJi3C11ywZmyHulvMaLjWsxDjUSDwAw==
dependencies:
- "@typescript-eslint/scope-manager" "4.16.1"
- "@typescript-eslint/types" "4.16.1"
- "@typescript-eslint/typescript-estree" "4.16.1"
+ "@typescript-eslint/scope-manager" "4.17.0"
+ "@typescript-eslint/types" "4.17.0"
+ "@typescript-eslint/typescript-estree" "4.17.0"
debug "^4.1.1"
-"@typescript-eslint/scope-manager@4.16.1":
- version "4.16.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz#244e2006bc60cfe46987e9987f4ff49c9e3f00d5"
- integrity sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==
+"@typescript-eslint/scope-manager@4.17.0":
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.17.0.tgz#f4edf94eff3b52a863180f7f89581bf963e3d37d"
+ integrity sha512-OJ+CeTliuW+UZ9qgULrnGpPQ1bhrZNFpfT/Bc0pzNeyZwMik7/ykJ0JHnQ7krHanFN9wcnPK89pwn84cRUmYjw==
dependencies:
- "@typescript-eslint/types" "4.16.1"
- "@typescript-eslint/visitor-keys" "4.16.1"
+ "@typescript-eslint/types" "4.17.0"
+ "@typescript-eslint/visitor-keys" "4.17.0"
-"@typescript-eslint/types@4.16.1":
- version "4.16.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.16.1.tgz#5ba2d3e38b1a67420d2487519e193163054d9c15"
- integrity sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==
+"@typescript-eslint/types@4.17.0":
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.17.0.tgz#f57d8fc7f31b348db946498a43050083d25f40ad"
+ integrity sha512-RN5z8qYpJ+kXwnLlyzZkiJwfW2AY458Bf8WqllkondQIcN2ZxQowAToGSd9BlAUZDB5Ea8I6mqL2quGYCLT+2g==
-"@typescript-eslint/typescript-estree@4.16.1":
- version "4.16.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz#c2fc46b05a48fbf8bbe8b66a63f0a9ba04b356f1"
- integrity sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==
+"@typescript-eslint/typescript-estree@4.17.0":
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.17.0.tgz#b835d152804f0972b80dbda92477f9070a72ded1"
+ integrity sha512-lRhSFIZKUEPPWpWfwuZBH9trYIEJSI0vYsrxbvVvNyIUDoKWaklOAelsSkeh3E2VBSZiNe9BZ4E5tYBZbUczVQ==
dependencies:
- "@typescript-eslint/types" "4.16.1"
- "@typescript-eslint/visitor-keys" "4.16.1"
+ "@typescript-eslint/types" "4.17.0"
+ "@typescript-eslint/visitor-keys" "4.17.0"
debug "^4.1.1"
globby "^11.0.1"
is-glob "^4.0.1"
semver "^7.3.2"
tsutils "^3.17.1"
-"@typescript-eslint/visitor-keys@4.16.1":
- version "4.16.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz#d7571fb580749fae621520deeb134370bbfc7293"
- integrity sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==
+"@typescript-eslint/visitor-keys@4.17.0":
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.17.0.tgz#9c304cfd20287c14a31d573195a709111849b14d"
+ integrity sha512-WfuMN8mm5SSqXuAr9NM+fItJ0SVVphobWYkWOwQ1odsfC014Vdxk/92t4JwS1Q6fCA/ABfCKpa3AVtpUKTNKGQ==
dependencies:
- "@typescript-eslint/types" "4.16.1"
+ "@typescript-eslint/types" "4.17.0"
eslint-visitor-keys "^2.0.0"
"@ungap/promise-all-settled@1.1.2":
uri-js "^4.2.2"
ajv@^7.0.2:
- version "7.1.1"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.1.1.tgz#1e6b37a454021fa9941713f38b952fc1c8d32a84"
- integrity sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==
+ version "7.2.1"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.2.1.tgz#a5ac226171912447683524fa2f1248fcf8bac83d"
+ integrity sha512-+nu0HDv7kNSOua9apAVc979qd932rrZeb3WOvoiD31A/p1mIE5/9bN2027pE2rOPYEdS3UHzsvof4hY+lM9/WQ==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
autocannon@^7.0.4:
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/autocannon/-/autocannon-7.0.4.tgz#c812c11af283254bff4bd75cce8383e79550c882"
- integrity sha512-+A+kSsVrx9F9fFbPAD7YytGQfCKgkaCIut4KrnYBbY2hmboAT065ClxqBsVqstokvFfdBAfSMPh0VSc6ktiimg==
+ version "7.0.5"
+ resolved "https://registry.yarnpkg.com/autocannon/-/autocannon-7.0.5.tgz#7c195ba09ae3b299d6f7532950d1e07041538b29"
+ integrity sha512-VMOfWf0e9EB5Crr7/snXTb64oC7I3lofpAjBcPWvHGet94DKjHCsbj05iIt2WTenPKub++6PETb/H9qleV9yJg==
dependencies:
chalk "^4.1.0"
+ char-spinner "^1.0.1"
cli-table3 "^0.6.0"
clone "^2.1.2"
color-support "^1.1.1"
manage-path "^2.0.0"
minimist "^1.2.0"
on-net-listen "^1.1.1"
- ora "^5.1.0"
pretty-bytes "^5.4.1"
progress "^2.0.3"
reinterval "^1.1.0"
- retimer "^2.0.0"
+ retimer "^3.0.0"
semver "^7.3.2"
timestring "^6.0.0"
resolved "https://registry.yarnpkg.com/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz#fdb0b43962ca7b40456a7c2bb48fe173da2d2122"
integrity sha1-/bC0OWLKe0BFanwrtI/hc9otISI=
-basic-auth@^2.0.0, basic-auth@~2.0.1:
+basic-auth@2.0.1, basic-auth@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
bufferutil "^4.0.1"
utf-8-validate "^5.0.2"
-bl@^4.0.3:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
- integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
- dependencies:
- buffer "^5.5.0"
- inherits "^2.0.4"
- readable-stream "^3.4.0"
-
blob-to-buffer@^1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz#a17fd6c1c564011408f8971e451544245daaa84a"
dependencies:
readable-stream "^3.4.0"
+bluebird@3.7.2, bluebird@^3.5.0, bluebird@^3.7.2:
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
+ integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
+
bluebird@^2.10.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=
-bluebird@^3.0.5, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.7.2:
- version "3.7.2"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
- integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
-
bmp-js@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233"
resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04"
integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==
-buffer@^5.2.0, buffer@^5.5.0:
+buffer@^5.2.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
xml2js "^0.4.23"
chai@^4.1.1:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.1.tgz#6fc6af447610709818e5c45116207d60b8a49cfd"
- integrity sha512-JClPZFGRcSl7X8dYzlCJY7v+X1fBA+9Y339Y8EqhBVfp0QC1hTnaf7nMfR+XZ74clkBC64b0iEw2cWKHt3EVqA==
+ version "4.3.3"
+ resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.3.tgz#f2b2ad9736999d07a7ff95cf1e7086c43a76f72d"
+ integrity sha512-MPSLOZwxxnA0DhLE84klnGPojWFK5KuhP7/j5dTsxpr2S3XlkqJP5WbyYl1gCTWvG2Z5N+HD4F472WsbEZL6Pw==
dependencies:
assertion-error "^1.1.0"
check-error "^1.0.2"
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+char-spinner@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/char-spinner/-/char-spinner-1.0.1.tgz#e6ea67bd247e107112983b7ab0479ed362800081"
+ integrity sha1-5upnvSR+EHESmDt6sEee02KAAIE=
+
character-parser@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0"
inherits "^2.0.1"
chunk-store-stream@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/chunk-store-stream/-/chunk-store-stream-4.2.0.tgz#18f673c495946c4cdcf14124a3ebd5f31eb0ea35"
- integrity sha512-90iueoPoqT2isnmy1fyqwzgFy5FokuaxQuijOQG1VgC/6DaXRfeYN0da8iWENkzqElWhqLxo8pWc7pH9dmxlcA==
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/chunk-store-stream/-/chunk-store-stream-4.3.0.tgz#3de5f4dfe19729366c29bb7ed52d139f9af29f0e"
+ integrity sha512-qby+/RXoiMoTVtPiylWZt7KFF1jy6M829TzMi2hxZtBIH9ptV19wxcft6zGiXLokJgCbuZPGNGab6DWHqiSEKw==
dependencies:
block-stream2 "^2.0.0"
readable-stream "^3.6.0"
dependencies:
restore-cursor "^3.1.0"
-cli-spinners@^2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.5.0.tgz#12763e47251bf951cb75c201dfa58ff1bcb2d047"
- integrity sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==
-
cli-table3@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.5.2:
- version "1.5.4"
- resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6"
- integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014"
+ integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
color-convert "^1.9.1"
color-string "^1.5.2"
-colorette@^1.2.1:
+colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
yargs "^16.2.0"
config@^3.0.0:
- version "3.3.4"
- resolved "https://registry.yarnpkg.com/config/-/config-3.3.4.tgz#55811abc2752b38a7b806cbdbc2da79c428312b7"
- integrity sha512-URO0m6z+rtENGHqtzO7W7C35iF+H9KVe7JJFps+3TIqZEOHl83NqTAgp5h8ah96m4NPQnx08nPBfbtDU+PgjVA==
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/config/-/config-3.3.6.tgz#b87799db7399cc34988f55379b5f43465b1b065c"
+ integrity sha512-Hj5916C5HFawjYJat1epbyY2PlAgLpBtDUlr0MxGLgo3p5+7kylyvnRY18PqJHgnNWXcdd0eWDemT7eYWuFgwg==
dependencies:
json5 "^2.1.1"
assert-plus "^1.0.0"
date-fns@^2.0.1, date-fns@^2.16.1:
- version "2.18.0"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.18.0.tgz#08e50aee300ad0d2c5e054e3f0d10d8f9cdfe09e"
- integrity sha512-NYyAg4wRmGVU4miKq5ivRACOODdZRY3q5WLmOJSq8djyzftYphU7dTHLcEtLqEvfqMKQ0jVv91P4BAwIjsXIcw==
+ version "2.19.0"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1"
+ integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==
dateformat@^3.0.3:
version "3.0.3"
domelementtype "^2.1.0"
domutils@^2.0.0, domutils@^2.4.3, domutils@^2.4.4:
- version "2.4.4"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
- integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.0.tgz#42f49cffdabb92ad243278b331fd761c1c2d3039"
+ integrity sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.0.1"
dependencies:
is-arrayish "^0.2.1"
-es-abstract@^1.17.0-next.0:
- version "1.17.7"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
- integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
- dependencies:
- es-to-primitive "^1.2.1"
- function-bind "^1.1.1"
- has "^1.0.3"
- has-symbols "^1.0.1"
- is-callable "^1.2.2"
- is-regex "^1.1.1"
- object-inspect "^1.8.0"
- object-keys "^1.1.1"
- object.assign "^4.1.1"
- string.prototype.trimend "^1.0.1"
- string.prototype.trimstart "^1.0.1"
-
-es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2:
- version "1.18.0-next.3"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.3.tgz#56bc8b5cc36b2cca25a13be07f3c02c2343db6b7"
- integrity sha512-VMzHx/Bczjg59E6jZOQjHeN3DEoptdhejpARgflAViidlqSpjdq9zA6lKwlhRRs/lOw1gHJv2xkkSFRgvEwbQg==
+es-abstract@^1.17.0-next.0, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2:
+ version "1.18.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4"
+ integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==
dependencies:
call-bind "^1.0.2"
es-to-primitive "^1.2.1"
resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922"
integrity sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=
-express-oauth-server@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/express-oauth-server/-/express-oauth-server-2.0.0.tgz#57b08665c1201532f52c4c02f19709238b99a48d"
- integrity sha1-V7CGZcEgFTL1LEwC8ZcJI4uZpI0=
- dependencies:
- bluebird "^3.0.5"
- express "^4.13.3"
- oauth2-server "3.0.0"
-
express-rate-limit@^5.0.0:
version "5.2.6"
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.2.6.tgz#b454e1be8a252081bda58460e0a25bf43ee0f7b0"
lodash "^4.17.20"
validator "^13.5.2"
-express@^4.12.4, express@^4.13.3, express@^4.16.4, express@^4.17.1:
+express@^4.12.4, express@^4.16.4, express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
omggif "^1.0.10"
glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
- integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
merge2 "^1.3.0"
slash "^3.0.0"
+got@^11.8.2, got@~11.8.1:
+ version "11.8.2"
+ resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
+ integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
+ dependencies:
+ "@sindresorhus/is" "^4.0.0"
+ "@szmarczak/http-timer" "^4.0.5"
+ "@types/cacheable-request" "^6.0.1"
+ "@types/responselike" "^1.0.0"
+ cacheable-lookup "^5.0.3"
+ cacheable-request "^7.0.1"
+ decompress-response "^6.0.0"
+ http2-wrapper "^1.0.0-beta.5.2"
+ lowercase-keys "^2.0.0"
+ p-cancelable "^2.0.0"
+ responselike "^2.0.0"
+
got@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
-got@~11.8.1:
- version "11.8.2"
- resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
- integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
- dependencies:
- "@sindresorhus/is" "^4.0.0"
- "@szmarczak/http-timer" "^4.0.5"
- "@types/cacheable-request" "^6.0.1"
- "@types/responselike" "^1.0.0"
- cacheable-lookup "^5.0.3"
- cacheable-request "^7.0.1"
- decompress-response "^6.0.0"
- http2-wrapper "^1.0.0-beta.5.2"
- lowercase-keys "^2.0.0"
- p-cancelable "^2.0.0"
- responselike "^2.0.0"
-
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
entities "^2.0.0"
htmlparser2@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.0.tgz#c2da005030390908ca4c91e5629e418e0665ac01"
- integrity sha512-numTQtDZMoh78zJpaNdJ9MXb2cv5G3jwUoe3dMQODubZvLoGvTE/Ofp6sHvH8OGKcN/8A47pGLi/k58xHP/Tfw==
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.1.tgz#422521231ef6d42e56bd411da8ba40aa36e91446"
+ integrity sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
agent-base "6"
debug "4"
-http-signature@1.3.5, http-signature@~1.2.0:
+http-signature@1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.5.tgz#9f19496ffbf3227298d7b5f156e0e1a948678683"
integrity sha512-NwoTQYSJoFt34jSBbwzDHDofoA61NGXzu6wXh95o1Ry62EnmKjXb/nR/RknLeZ3G/uGwrlKNY2z7uPt+Cdl7Tw==
jsprim "^1.2.2"
sshpk "^1.14.1"
+http-signature@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+ integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
+ dependencies:
+ assert-plus "^1.0.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
http2-wrapper@^1.0.0-beta.5.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
-is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.2, is-callable@^1.2.3:
+is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
global-dirs "^2.0.1"
is-path-inside "^3.0.1"
-is-interactive@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
- integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
-
is-nan@^1.3.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
is-path-inside@^3.0.1:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017"
- integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
-is-regex@^1.0.3, is-regex@^1.1.1, is-regex@^1.1.2:
+is-regex@^1.0.3, is-regex@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+lodash@4.17.19:
+ version "4.17.19"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
+ integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
+
lodash@4.17.21, lodash@>=4.17.13, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-log-symbols@4.0.0, log-symbols@^4.0.0:
+log-symbols@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
libmime "5.0.0"
libqp "1.1.0"
-make-dir@^3.0.0:
+make-dir@^3.0.0, make-dir@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea"
integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==
-mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4:
+mkdirp@^1.0.3, mkdirp@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
mocha@^8.0.1:
- version "8.3.0"
- resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.3.0.tgz#a83a7432d382ae1ca29686062d7fdc2c36f63fe5"
- integrity sha512-TQqyC89V1J/Vxx0DhJIXlq9gbbL9XFNdeLQ1+JsnZsVaSOV1z3tWfw0qZmQJGQRIfkvZcs7snQnZnOCKoldq1Q==
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.3.2.tgz#53406f195fa86fbdebe71f8b1c6fb23221d69fcc"
+ integrity sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==
dependencies:
"@ungap/promise-all-settled" "1.1.2"
ansi-colors "4.1.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
-nanoid@3.1.20, nanoid@^3.1.20:
+nanoid@3.1.20:
version "3.1.20"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
+nanoid@^3.1.20:
+ version "3.1.21"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.21.tgz#25bfee7340ac4185866fbfb2c9006d299da1be7f"
+ integrity sha512-A6oZraK4DJkAOICstsGH98dvycPr/4GGDH7ZWKmMdd3vGcOurZ6JmWFUt0DA5bzrrn2FrUjmv6mFNWvv8jpppA==
+
napi-macros@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-oauth2-server@3.0.0, oauth2-server@3.1.0-beta.1:
- version "3.1.0-beta.1"
- resolved "https://registry.yarnpkg.com/oauth2-server/-/oauth2-server-3.1.0-beta.1.tgz#159ee4d32d148c2dc7a39f7b1ce872e039b91a41"
- integrity sha512-FWLl/YC5NGvGzAtclhmlY9fG0nKwDP7xPiPOi5fZ4APO34BmF/vxsEp22spJNuSOrGEsp9W7jKtFCI3UBSvx5w==
+oauth2-server@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/oauth2-server/-/oauth2-server-3.1.1.tgz#be291da840a307a50368736ab766bd68f2eeb3a9"
+ integrity sha512-4dv+fE9hrK+xTaCygOLh/kQeFzbFr7UqSyHvBDbrQq8Hg52sAkV2vTsyH3Z42hoeaKpbhM7udhL8Y4GYbl6TGQ==
dependencies:
- basic-auth "^2.0.0"
- bluebird "^3.5.1"
- lodash "^4.17.10"
- promisify-any "^2.0.1"
- statuses "^1.5.0"
- type-is "^1.6.16"
+ basic-auth "2.0.1"
+ bluebird "3.7.2"
+ lodash "4.17.19"
+ promisify-any "2.0.1"
+ statuses "1.5.0"
+ type-is "1.6.18"
object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09"
integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==
-object-inspect@^1.8.0, object-inspect@^1.9.0:
+object-inspect@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
-object.assign@^4.1.1, object.assign@^4.1.2:
+object.assign@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
type-check "^0.4.0"
word-wrap "^1.2.3"
-ora@^5.1.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f"
- integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==
- dependencies:
- bl "^4.0.3"
- chalk "^4.1.0"
- cli-cursor "^3.1.0"
- cli-spinners "^2.5.0"
- is-interactive "^1.0.0"
- log-symbols "^4.0.0"
- strip-ansi "^6.0.0"
- wcwidth "^1.0.1"
-
os-homedir@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
p-cancelable@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
- integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.0.tgz#4d51c3b91f483d02a0d300765321fca393d758dd"
+ integrity sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==
p-finally@^1.0.0:
version "1.0.0"
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
postcss@^8.0.2:
- version "8.2.6"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe"
- integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg==
+ version "8.2.8"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
+ integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
dependencies:
- colorette "^1.2.1"
+ colorette "^1.2.2"
nanoid "^3.1.20"
source-map "^0.6.1"
dependencies:
asap "~2.0.3"
-promisify-any@^2.0.1:
+promisify-any@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/promisify-any/-/promisify-any-2.0.1.tgz#403e00a8813f175242ab50fe33a69f8eece47305"
integrity sha1-QD4AqIE/F1JCq1D+M6afjuzkcwU=
stream-to-blob-url "^3.0.2"
videostream "^3.2.2"
-request@^2.81.0, request@^2.88.0:
+request@^2.88.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
onetime "^5.1.0"
signal-exit "^3.0.2"
-retimer@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/retimer/-/retimer-2.0.0.tgz#e8bd68c5e5a8ec2f49ccb5c636db84c04063bbca"
- integrity sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg==
+retimer@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/retimer/-/retimer-3.0.0.tgz#98b751b1feaf1af13eb0228f8ea68b8f9da530df"
+ integrity sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==
retry-as-promised@^3.2.0:
version "3.2.0"
to-array "0.1.4"
socket.io-client@^3.0.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-3.1.2.tgz#77be8c180cef29121970856e8f48e5463631020a"
- integrity sha512-fXhF8plHrd7U14A7K0JPOmZzpmGkLpIS6623DzrBZqYzI/yvlP4fA3LnxwthEVgiHmn2uJ4KjdnQD8A03PuBWQ==
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-3.1.3.tgz#57ddcefea58cfab71f0e94c21124de8e3c5aa3e2"
+ integrity sha512-4sIGOGOmCg3AOgGi7EEr6ZkTZRkrXwub70bBB/F0JSkMOUFpA77WsL87o34DffQQ31PkbMUIadGOk+3tx1KGbw==
dependencies:
"@types/component-emitter" "^1.2.10"
backo2 "~1.0.2"
through2 "^0.6.3"
to-utf-8 "^1.2.0"
-sshpk@^1.14.1:
+sshpk@^1.14.1, sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.0.1.tgz#ed8bb25648e15831759b6023bdb87e6b60b38126"
integrity sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==
-"statuses@>= 1.5.0 < 2", statuses@^1.5.0, statuses@~1.5.0:
+statuses@1.5.0, "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
-string.prototype.trimend@^1.0.1, string.prototype.trimend@^1.0.4:
+string.prototype.trimend@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
call-bind "^1.0.2"
define-properties "^1.1.3"
-string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.4:
+string.prototype.trimstart@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tsutils@^3.17.1:
- version "3.20.0"
- resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.20.0.tgz#ea03ea45462e146b53d70ce0893de453ff24f698"
- integrity sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==
+ version "3.21.0"
+ resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
+ integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
dependencies:
tslib "^1.8.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
-type-is@^1.6.16, type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18:
+type-is@1.6.18, type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
type@^2.0.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/type/-/type-2.3.0.tgz#ada7c045f07ead08abf9e2edd29be1a0c0661132"
- integrity sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg==
+ 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.0.0, typedarray-to-buffer@^3.1.5:
version "3.1.5"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^4.0.5:
- version "4.2.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.2.tgz#1450f020618f872db0ea17317d16d8da8ddb8c4c"
- integrity sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
+ integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
v8-compile-cache@^2.0.3:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
- integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
+ integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
valid-data-url@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
-wcwidth@>=1.0.1, wcwidth@^1.0.1:
+wcwidth@>=1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
xhr2 "^0.1.4"
webtorrent@^0.115.1:
- version "0.115.1"
- resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-0.115.1.tgz#3984e6b17fdb8ad68b5cdbd42c46a2288e1b3bb2"
- integrity sha512-8kq498EMUjYu18wlfoZ42wvz9oUAJrobJbHQGRHl0sbrPVBt17H4FVoAc502XSMCbFzhMx5Vqd7Wz4JTTCPvuQ==
+ version "0.115.3"
+ resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-0.115.3.tgz#2d0a53b65326ffd0124b3592950c4c75e299730a"
+ integrity sha512-DNryTNoAHse+zxArBZg25U8B97KNPeVjGzrjRB+oDnGROuKfQcvLh8/9K79FDfQTYVpInMmr9l0ksIsEjz/L2g==
dependencies:
addr-to-ip-port "^1.5.1"
bitfield "^4.0.0"
async-limiter "~1.0.0"
ws@^7.0.0, ws@^7.3.0, ws@^7.4.2, ws@~7.4.2:
- version "7.4.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz#1f9643de34a543b8edb124bdcbc457ae55a6e5cd"
- integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59"
+ integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==
ws@~6.1.0:
version "6.1.4"
decamelize "^1.2.0"
yargs-parser@^20.2.2:
- version "20.2.6"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.6.tgz#69f920addf61aafc0b8b89002f5d66e28f2d8b20"
- integrity sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==
+ version "20.2.7"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
+ integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
yargs-unparser@2.0.0:
version "2.0.0"