use new runtime api

This commit is contained in:
SrGooglo 2025-03-06 03:51:05 +00:00
parent 3e24dfec7a
commit 0c49d7fcf8
4 changed files with 223 additions and 446 deletions

View File

@ -4,38 +4,35 @@ import NotificationUI from "./ui"
import NotificationFeedback from "./feedback"
export default class NotificationCore extends Core {
static namespace = "notifications"
static depenpencies = [
"api",
"settings",
]
static namespace = "notifications"
static depenpencies = ["api", "settings"]
listenSockets = {
"notifications": {
"notification.new": (data) => {
this.new(data)
},
"notification.broadcast": (data) => {
this.new(data)
},
}
}
listenSockets = {
notifications: {
"notification.new": (data) => {
this.new(data)
},
"notification.broadcast": (data) => {
this.new(data)
},
},
}
public = {
new: this.new,
close: this.close,
}
public = {
new: this.new,
close: this.close,
}
async onInitialize() {
this.ctx.CORES.api.registerSocketListeners(this.listenSockets)
}
async onInitialize() {
this.ctx.cores.get("api").registerSocketListeners(this.listenSockets)
}
async new(notification) {
NotificationUI.notify(notification)
NotificationFeedback.feedback(notification)
}
async new(notification) {
NotificationUI.notify(notification)
NotificationFeedback.feedback(notification)
}
async close(id) {
NotificationUI.close(id)
}
}
async close(id) {
NotificationUI.close(id)
}
}

View File

@ -1,140 +1,141 @@
import AudioPlayerStorage from "../player.storage"
export default class Presets {
constructor({
storage_key,
defaultPresetValue,
onApplyValues,
}) {
if (!storage_key) {
throw new Error("storage_key is required")
}
constructor({ storage_key, defaultPresetValue, onApplyValues }) {
if (!storage_key) {
throw new Error("storage_key is required")
}
this.storage_key = storage_key
this.defaultPresetValue = defaultPresetValue
this.onApplyValues = onApplyValues
this.storage_key = storage_key
this.defaultPresetValue = defaultPresetValue
this.onApplyValues = onApplyValues
return this
}
return this
}
get presets() {
return AudioPlayerStorage.get(`${this.storage_key}_presets`) ?? {
default: this.defaultPresetValue
}
}
get presets() {
return (
AudioPlayerStorage.get(`${this.storage_key}_presets`) ?? {
default: this.defaultPresetValue,
}
)
}
set presets(presets) {
AudioPlayerStorage.set(`${this.storage_key}_presets`, presets)
set presets(presets) {
AudioPlayerStorage.set(`${this.storage_key}_presets`, presets)
return presets
}
return presets
}
set currentPresetKey(key) {
AudioPlayerStorage.set(`${this.storage_key}_current-key`, key)
set currentPresetKey(key) {
AudioPlayerStorage.set(`${this.storage_key}_current-key`, key)
return key
}
return key
}
get currentPresetKey() {
return AudioPlayerStorage.get(`${this.storage_key}_current-key`) ?? "default"
}
get currentPresetKey() {
return (
AudioPlayerStorage.get(`${this.storage_key}_current-key`) ??
"default"
)
}
get currentPresetValues() {
if (!this.presets || !this.presets[this.currentPresetKey]) {
return this.defaultPresetValue
}
get currentPresetValues() {
if (!this.presets || !this.presets[this.currentPresetKey]) {
return this.defaultPresetValue
}
return this.presets[this.currentPresetKey]
}
return this.presets[this.currentPresetKey]
}
set currentPresetValues(values) {
const newData = this.presets
set currentPresetValues(values) {
const newData = this.presets
newData[this.currentPresetKey] = values
newData[this.currentPresetKey] = values
this.presets = newData
}
this.presets = newData
}
applyValues() {
if (typeof this.onApplyValues === "function") {
this.onApplyValues(this.presets)
}
}
applyValues() {
if (typeof this.onApplyValues === "function") {
this.onApplyValues(this.presets)
}
}
deletePreset(key) {
if (key === "default") {
app.message.error("Cannot delete default preset")
return false
}
deletePreset(key) {
if (key === "default") {
app.message.error("Cannot delete default preset")
return false
}
// if current preset is deleted, change to default
if (this.currentPresetKey === key) {
this.changePreset("default")
}
// if current preset is deleted, change to default
if (this.currentPresetKey === key) {
this.changePreset("default")
}
let newData = this.presets
let newData = this.presets
delete newData[key]
delete newData[key]
this.presets = newData
this.presets = newData
this.applyValues()
this.applyValues()
return newData
}
return newData
}
createPreset(key, values) {
if (this.presets[key]) {
app.message.error("Preset already exists")
return false
}
createPreset(key, values) {
if (this.presets[key]) {
app.message.error("Preset already exists")
return false
}
let newData = this.presets
let newData = this.presets
newData[key] = values ?? this.defaultPresetValue
newData[key] = values ?? this.defaultPresetValue
this.applyValues()
this.applyValues()
this.presets = newData
this.presets = newData
return newData
}
return newData
}
changePreset(key) {
// create new one
if (!this.presets[key]) {
this.presets[key] = this.defaultPresetValue
}
changePreset(key) {
// create new one
if (!this.presets[key]) {
this.presets[key] = this.defaultPresetValue
}
this.currentPresetKey = key
this.currentPresetKey = key
this.applyValues()
this.applyValues()
return this.presets[key]
}
return this.presets[key]
}
setToCurrent(values) {
this.currentPresetValues = {
...this.currentPresetValues,
...values,
}
setToCurrent(values) {
this.currentPresetValues = {
...this.currentPresetValues,
...values,
}
this.applyValues()
this.applyValues()
return this.currentPresetValues
}
return this.currentPresetValues
}
async setCurrentPresetToDefault() {
return await new Promise((resolve) => {
app.layout.modal.confirm.confirm({
title: "Reset to default values?",
content: "Are you sure you want to reset to default values?",
onOk: () => {
this.setToCurrent(this.defaultPresetValue)
async setCurrentPresetToDefault() {
return await new Promise((resolve) => {
app.layout.modal.confirm({
title: "Reset to default values?",
content: "Are you sure you want to reset to default values?",
onConfirm: () => {
this.setToCurrent(this.defaultPresetValue)
resolve(this.currentPresetValues)
}
})
})
}
}
resolve(this.currentPresetValues)
},
})
})
}
}

View File

@ -1,230 +0,0 @@
import { EventBus } from "@ragestudio/vessel"
export default class ChunkedUpload {
constructor(params) {
const {
endpoint,
file,
headers = {},
splitChunkSize = 1024 * 1024 * 10,
maxRetries = 3,
delayBeforeRetry = 5,
} = params
if (!endpoint) {
throw new Error("Missing endpoint")
}
if ((!file) instanceof File) {
throw new Error("Invalid or missing file")
}
if (typeof headers !== "object") {
throw new Error("Invalid headers")
}
if (splitChunkSize <= 0) {
throw new Error("Invalid splitChunkSize")
}
this.chunkCount = 0
this.retriesCount = 0
this.splitChunkSize = splitChunkSize
this.totalChunks = Math.ceil(file.size / splitChunkSize)
this.maxRetries = maxRetries
this.delayBeforeRetry = delayBeforeRetry
this.offline = this.paused = false
this.endpoint = endpoint
this.file = file
this.headers = {
...headers,
"uploader-original-name": encodeURIComponent(file.name),
"uploader-file-id": this.getFileUID(file),
"uploader-chunks-total": this.totalChunks,
"chunk-size": splitChunkSize,
Connection: "keep-alive",
"Cache-Control": "no-cache",
}
this.setupListeners()
this.nextSend()
console.debug("[Uploader] Created", {
splitChunkSize: splitChunkSize,
totalChunks: this.totalChunks,
totalSize: file.size,
})
}
_reader = new FileReader()
events = new EventBus()
setupListeners() {
window.addEventListener(
"online",
() =>
!this.offline &&
((this.offline = false),
this.events.emit("online"),
this.nextSend()),
)
window.addEventListener(
"offline",
() => ((this.offline = true), this.events.emit("offline")),
)
}
getFileUID(file) {
return (
Math.floor(Math.random() * 100000000) +
Date.now() +
file.size +
"_tmp"
)
}
loadChunk() {
return new Promise((resolve) => {
const start = this.chunkCount * this.splitChunkSize
const end = Math.min(start + this.splitChunkSize, this.file.size)
this._reader.onload = () =>
resolve(
new Blob([this._reader.result], {
type: "application/octet-stream",
}),
)
this._reader.readAsArrayBuffer(this.file.slice(start, end))
})
}
async sendChunk() {
const form = new FormData()
form.append("file", this.chunk)
this.headers["uploader-chunk-number"] = this.chunkCount
console.log(`[UPLOADER] Sending chunk ${this.chunkCount}`, {
currentChunk: this.chunkCount,
totalChunks: this.totalChunks,
})
try {
const res = await fetch(this.endpoint, {
method: "POST",
headers: this.headers,
body: form,
})
return res
} catch (error) {
this.manageRetries()
}
}
manageRetries() {
if (++this.retriesCount < this.maxRetries) {
setTimeout(() => this.nextSend(), this.delayBeforeRetry * 1000)
this.events.emit("fileRetry", {
message: `Retrying chunk ${this.chunkCount}`,
chunk: this.chunkCount,
retriesLeft: this.retries - this.retriesCount,
})
} else {
this.events.emit("error", {
message: `No more retries for chunk ${this.chunkCount}`,
})
}
}
async nextSend() {
if (this.paused || this.offline) {
return null
}
this.chunk = await this.loadChunk()
const res = await this.sendChunk()
const data = await res.json()
if ([200, 201, 204].includes(res.status)) {
console.log(`[UPLOADER] Chunk ${this.chunkCount} sent`)
this.chunkCount = this.chunkCount + 1
if (this.chunkCount < this.totalChunks) {
this.nextSend()
}
// check if is the last chunk, if so, handle sse events
if (this.chunkCount === this.totalChunks) {
if (data.eventChannelURL) {
console.log(
`[UPLOADER] Connecting to SSE channel >`,
data.eventChannelURL,
)
const eventSource = new EventSource(data.eventChannelURL)
eventSource.onerror = (error) => {
this.events.emit("error", error)
}
eventSource.onopen = () => {
console.log(`[UPLOADER] SSE channel opened`)
}
eventSource.onmessage = (event) => {
// parse json
const messageData = JSON.parse(event.data)
console.log(`[UPLOADER] SSE Event >`, messageData)
if (messageData.status === "done") {
this.events.emit("finish", messageData.result)
eventSource.close()
}
if (messageData.status === "error") {
this.events.emit("error", messageData.result)
}
if (messageData.status === "progress") {
this.events.emit("progress", {
percentProgress: messageData.progress,
})
}
}
} else {
this.events.emit("finish", data)
}
}
this.events.emit("progress", {
percentProgress: Math.round(
(100 / this.totalChunks) * this.chunkCount,
),
})
} else if ([408, 502, 503, 504].includes(res.status)) {
this.manageRetries()
} else {
this.events.emit("error", {
message: `[${res.status}] ${data.error ?? data.message}`,
})
}
}
togglePause() {
this.paused = !this.paused
if (!this.paused) {
return this.nextSend()
}
}
}

View File

@ -1,108 +1,117 @@
import { Core } from "@ragestudio/vessel"
import ChunkedUpload from "./chunkedUpload"
import ChunkedUpload from "@classes/ChunkedUpload"
import SessionModel from "@models/session"
export default class RemoteStorage extends Core {
static namespace = "remoteStorage"
static depends = ["api", "tasksQueue"]
static namespace = "remoteStorage"
static depends = ["api", "tasksQueue"]
public = {
uploadFile: this.uploadFile,
getFileHash: this.getFileHash,
binaryArrayToFile: this.binaryArrayToFile,
}
public = {
uploadFile: this.uploadFile,
getFileHash: this.getFileHash,
binaryArrayToFile: this.binaryArrayToFile,
}
binaryArrayToFile(bin, filename) {
const { format, data } = bin
binaryArrayToFile(bin, filename) {
const { format, data } = bin
const filenameExt = format.split("/")[1]
filename = `${filename}.${filenameExt}`
const filenameExt = format.split("/")[1]
filename = `${filename}.${filenameExt}`
const byteArray = new Uint8Array(data)
const blob = new Blob([byteArray], { type: data.type })
const byteArray = new Uint8Array(data)
const blob = new Blob([byteArray], { type: data.type })
return new File([blob], filename, {
type: format,
})
}
return new File([blob], filename, {
type: format,
})
}
async getFileHash(file) {
const buffer = await file.arrayBuffer()
const hash = await crypto.subtle.digest("SHA-256", buffer)
const hashArray = Array.from(new Uint8Array(hash))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("")
async getFileHash(file) {
const buffer = await file.arrayBuffer()
const hash = await crypto.subtle.digest("SHA-256", buffer)
const hashArray = Array.from(new Uint8Array(hash))
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join("")
return hashHex
}
return hashHex
}
async uploadFile(
file,
{
onProgress = () => { },
onFinish = () => { },
onError = () => { },
service = "standard",
headers = {},
} = {},
) {
return await new Promise((_resolve, _reject) => {
const fn = async () => new Promise((resolve, reject) => {
const uploader = new ChunkedUpload({
endpoint: `${app.cores.api.client().mainOrigin}/upload/chunk`,
splitChunkSize: 5 * 1024 * 1024,
file: file,
service: service,
headers: {
...headers,
"provider-type": service,
"Authorization": `Bearer ${SessionModel.token}`,
},
})
async uploadFile(
file,
{
onProgress = () => {},
onFinish = () => {},
onError = () => {},
service = "standard",
headers = {},
} = {},
) {
return await new Promise((_resolve, _reject) => {
const fn = async () =>
new Promise((resolve, reject) => {
const uploader = new ChunkedUpload({
endpoint: `${app.cores.api.client().mainOrigin}/upload/chunk`,
splitChunkSize: 5 * 1024 * 1024,
file: file,
service: service,
headers: {
...headers,
"provider-type": service,
Authorization: `Bearer ${SessionModel.token}`,
},
})
uploader.events.on("error", ({ message }) => {
this.console.error("[Uploader] Error", message)
uploader.events.on("error", ({ message }) => {
this.console.error("[Uploader] Error", message)
app.cores.notifications.new({
title: "Could not upload file",
description: message
}, {
type: "error"
})
app.cores.notifications.new(
{
title: "Could not upload file",
description: message,
},
{
type: "error",
},
)
if (typeof onError === "function") {
onError(file, message)
}
if (typeof onError === "function") {
onError(file, message)
}
reject(message)
_reject(message)
})
reject(message)
_reject(message)
})
uploader.events.on("progress", ({ percentProgress }) => {
if (typeof onProgress === "function") {
onProgress(file, percentProgress)
}
})
uploader.events.on("progress", ({ percentProgress }) => {
if (typeof onProgress === "function") {
onProgress(file, percentProgress)
}
})
uploader.events.on("finish", (data) => {
this.console.debug("[Uploader] Finish", data)
uploader.events.on("finish", (data) => {
this.console.debug("[Uploader] Finish", data)
app.cores.notifications.new({
title: "File uploaded",
}, {
type: "success"
})
app.cores.notifications.new(
{
title: "File uploaded",
},
{
type: "success",
},
)
if (typeof onFinish === "function") {
onFinish(file, data)
}
if (typeof onFinish === "function") {
onFinish(file, data)
}
resolve(data)
_resolve(data)
})
})
resolve(data)
_resolve(data)
})
})
app.cores.tasksQueue.appendToQueue(`upload_${file.name}`, fn)
})
}
}
app.cores.tasksQueue.appendToQueue(`upload_${file.name}`, fn)
})
}
}