Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload Pipeline v2 #5905

Merged
merged 97 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
8852c42
Add the data structure for file chunks
dtdesign Dec 25, 2023
bc1f7d7
Add the basic directory structure for the data storage
dtdesign Nov 26, 2023
fbb135f
Add support for blob requests
dtdesign Dec 25, 2023
aa91e8b
Add PoC implementation for chunked uploads
dtdesign Dec 25, 2023
f3ce767
Implement a naive chunked upload
dtdesign Dec 26, 2023
17a4f2a
Use buffers to write uploaded files
dtdesign Dec 26, 2023
06a7cdb
Add SHA-256 checksums to the uploaded data
dtdesign Dec 27, 2023
532ecee
Remove the unnecessary table `wcf1_file_chunk`
dtdesign Dec 27, 2023
0a901eb
Dynamically calculate the chunk size
dtdesign Dec 28, 2023
8aea8b3
Add a proper DBO for the handling of temporary files
dtdesign Dec 28, 2023
924bbee
Use a single source of truth for temporary filenames
dtdesign Dec 28, 2023
08e27ac
Create the basic data structure for a persistent file
dtdesign Dec 28, 2023
325b7ec
Convert a temporary file into a persistent file
dtdesign Dec 28, 2023
b663a5f
Track the number of uploaded chunks
dtdesign Jan 16, 2024
79fa298
Write the chunks into the file directly
dtdesign Jan 16, 2024
bde14a0
Add basic support for file processors
dtdesign Jan 26, 2024
ff3e554
Simplify the handling of context data
dtdesign Jan 27, 2024
da614dc
Improve the error handling of the preflight request
dtdesign Jan 28, 2024
103425d
Add support for an extension based filter
dtdesign Jan 28, 2024
ff080a3
Prototype for the delegation of attachments to the file API
dtdesign Jan 31, 2024
1718e6a
Delegate attachments to the file upload system
dtdesign Feb 15, 2024
60a101c
Add the ability to attach custom response data
dtdesign Feb 15, 2024
df0a39e
Add basic support for thumbnails
dtdesign Feb 15, 2024
52574f6
Add support for image thumbnails
dtdesign Feb 16, 2024
1cace1e
Add the `woltlab-core-file` element to represent uploads
dtdesign Feb 29, 2024
8b9796e
Add a lifecycle behavior for uploaded files
dtdesign Mar 1, 2024
384f73d
Add basic support for thumbnails for the file element
dtdesign Mar 2, 2024
68470a8
Persistently track the mime type of uploaded files
dtdesign Mar 2, 2024
03af9ec
Implement a button to insert the `[attach]` BBCode into the editor
dtdesign Mar 2, 2024
ae0dda7
Forward the extra data from the file processor
dtdesign Mar 3, 2024
73693da
Add the button to insert an image’s thumbnail
dtdesign Mar 3, 2024
ec4ceaa
Add the link to the uploaded file
dtdesign Mar 3, 2024
4bd1844
Migrate the file upload preflight to the new API
dtdesign Mar 21, 2024
302725f
Remove the old controller for the preflight request
dtdesign Mar 21, 2024
f2fe03e
Migrate the chunk upload to the new API
dtdesign Mar 22, 2024
542134b
Migrate the generation of thumbnails to the new API
dtdesign Mar 22, 2024
83a5e84
Add an API endpoint to delete files
dtdesign Mar 24, 2024
9a41582
Fix the handling of validation errors
dtdesign Mar 25, 2024
fb7a189
Add support for attachment thumbnail
dtdesign Mar 27, 2024
e9de281
Always load the file and thumbnails for attachments
dtdesign Mar 28, 2024
31d2fc9
Prototype to render files as HTML elements
dtdesign Mar 28, 2024
5551082
Unify the handling of the attachment context
dtdesign Apr 7, 2024
f98b096
Fix the error handling of failed context validations
dtdesign Apr 13, 2024
54c18a0
Always create thumbnails using WebP
dtdesign Apr 13, 2024
4452c76
Clean up some TODOs
dtdesign Apr 13, 2024
539a12f
Cache the width and height of images
dtdesign Apr 13, 2024
46ced27
Add `getPathname()` to simplify the file access
dtdesign Apr 14, 2024
7c0079b
Use `File::getSourceName()` to avoid accidental leaks
dtdesign Apr 14, 2024
f5c45fd
Skip thumbnails for non-image files
dtdesign Apr 15, 2024
c539148
Use file extensions other than `.bin` for safe types
dtdesign Apr 15, 2024
26218ee
Add a secret to upload files
dtdesign Apr 15, 2024
5dec8aa
Fix the loading of the files of attachments
dtdesign Apr 15, 2024
8e64b80
Unify the styling of attachments below a message
dtdesign Apr 15, 2024
b91c564
Prevent the generation of thumbnails for small images
dtdesign Apr 25, 2024
252a03d
Add an automated resizing for images exceeding the limits
dtdesign Apr 25, 2024
2421398
Bind uploaded attachments to the current user
dtdesign Apr 26, 2024
8c14409
Initialize existing attachments
dtdesign Apr 26, 2024
d18e35e
Add the link to the full version to files
dtdesign Apr 26, 2024
2c973be
Block drag & drop for illegal file extensions
dtdesign Apr 26, 2024
890bd83
Add support for uploads through drag and drop on the editor
dtdesign Apr 29, 2024
e7dbb6b
Add some basic styling for uploaded attachments
dtdesign Apr 29, 2024
3fd7cc2
Improve the UI of uploaded attachments
dtdesign May 3, 2024
b8429d6
Remove `downloads` and `lastDownloadTime`
dtdesign May 3, 2024
b592df1
Implement a cleanup on file delete
dtdesign May 3, 2024
592d1e3
Add support for the image viewer for attachments
dtdesign May 4, 2024
e3a8ab4
Use `objectTypeID` instead of `typeName`
dtdesign May 4, 2024
af4cc68
Initialize the image viewer for dynamic attachments
dtdesign May 4, 2024
6b92fb3
Reorganize the action buttons for attachments
dtdesign May 4, 2024
917c456
Move the attachment logic into a separate file
dtdesign May 5, 2024
e473f93
Clean up the error and editor id handling
dtdesign May 5, 2024
f286fa8
Add button phrases and simplify the button creation
dtdesign May 5, 2024
056d063
Add basic error messages
dtdesign May 5, 2024
46b238d
Add a default implementation for file processors
dtdesign May 5, 2024
aa9b209
Add the documentation for the file processor interface
dtdesign May 5, 2024
57bcc1e
Minor code improvements
dtdesign May 8, 2024
736dc12
Add support for download tracking
dtdesign May 8, 2024
b2b49e5
Clean up files on delete
dtdesign May 8, 2024
75d5a40
Add caching support to file downloads
dtdesign May 11, 2024
1d6a66a
Delete orphaned files
dtdesign May 11, 2024
2b72743
Add checksums for thumbnails and a rebuild worker
dtdesign May 12, 2024
170883c
Fix the call to a removed helper function
dtdesign May 18, 2024
53a09d5
Add support for limiting the number of uploaded files
dtdesign May 18, 2024
7cb38db
Move the ETag handling and switch to weak comparisons
dtdesign May 18, 2024
8cfc9eb
Fix the `expires` header and force lowercased http headers
dtdesign May 18, 2024
49133ba
Increase the length of the secret to 32 characzers
dtdesign May 18, 2024
08b4d44
Fix the ETag handling
dtdesign May 18, 2024
4178a69
Validate the file size before querying the server
dtdesign May 18, 2024
86f6303
Add an option to limit the maximum file size
dtdesign May 19, 2024
5bc49c7
Add a new worker implementation with a linear, predictable runtime
dtdesign May 25, 2024
bd7c35d
Migrate attachments to the new file upload API
dtdesign May 25, 2024
5652914
Improve the eTag handling of file downloads
dtdesign May 25, 2024
ee69c65
Create a WebP variant of the source file
dtdesign Jun 1, 2024
74c7827
Add the SQL structure to the DDL PIP
dtdesign Jun 2, 2024
7cb952f
Replace references to the old JS for attachments
dtdesign Jun 7, 2024
29f2187
Remove legacy methods for attachment handling
dtdesign Jun 7, 2024
34bc25c
Add support for copying files
dtdesign Jun 7, 2024
82fe21f
Mark the legacy file system methods as deprecated
dtdesign Jun 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions com.woltlab.wcf/cronjob.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
<description language="de">Löscht verwaiste Dateianhänge</description>
<expression>0 2 * * *</expression>
</cronjob>
<cronjob name="com.woltlab.wcf.fileCleanUp">
<classname>wcf\system\cronjob\FileCleanUpCronjob</classname>
<description>Deletes orphaned files</description>
<description language="de">Löscht verwaiste Dateien</description>
<expression>0 3 * * *</expression>
</cronjob>
<cronjob name="com.woltlab.wcf.backgroundQueueCleanUp">
<classname>wcf\system\cronjob\BackgroundQueueCleanUpCronjob</classname>
<description>Requeues stuck queue items</description>
Expand Down
5 changes: 5 additions & 0 deletions com.woltlab.wcf/objectType.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1739,6 +1739,11 @@
<name>com.woltlab.wcf.rescueMode</name>
<definitionname>com.woltlab.wcf.floodControl</definitionname>
</type>
<type>
<name>com.woltlab.wcf.attachment</name>
<definitionname>com.woltlab.wcf.file</definitionname>
<classname>wcf\system\file\processor\AttachmentFileProcessor</classname>
</type>
<!-- deprecated -->
<type>
<name>com.woltlab.wcf.page.controller</name>
Expand Down
4 changes: 4 additions & 0 deletions com.woltlab.wcf/objectTypeDefinition.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,5 +225,9 @@
<name>com.woltlab.wcf.multifactor</name>
<interfacename>wcf\system\user\multifactor\IMultifactorMethod</interfacename>
</definition>
<definition>
<name>com.woltlab.wcf.file</name>
<interfacename>wcf\system\file\processor\IFileProcessor</interfacename>
</definition>
</import>
</data>
4 changes: 0 additions & 4 deletions com.woltlab.wcf/templates/attachments.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@
{icon name='up-right-and-down-left-from-center'}
{#$attachment->width} × {#$attachment->height}
</li>
<li>
{icon name='eye'}
{#$attachment->downloads}
</li>
</ul>
</a>
</li>
Expand Down
1 change: 0 additions & 1 deletion com.woltlab.wcf/templates/headIncludeJavaScript.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ window.addEventListener('pageshow', function(event) {
);
</script>

{js application='wcf' file='WCF.Attachment' bundle='WCF.Combined' hasTiny=true}
{js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined' hasTiny=true}
{js application='wcf' file='WCF.ImageViewer' bundle='WCF.Combined' hasTiny=true}
{js application='wcf' file='WCF.Label' bundle='WCF.Combined' hasTiny=true}
Expand Down
90 changes: 19 additions & 71 deletions com.woltlab.wcf/templates/shared_messageFormAttachments.tpl
Original file line number Diff line number Diff line change
@@ -1,81 +1,29 @@
<div class="jsOnly formAttachmentContent messageTabMenuContent" id="attachments_{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}">
<ul class="formAttachmentList clearfix jsObjectActionContainer" data-object-action-class-name="wcf\data\attachment\AttachmentAction"{if !$attachmentHandler->getAttachmentList()|count} style="display: none"{/if}>
{foreach from=$attachmentHandler->getAttachmentList() item=$attachment}
<li class="box64 formAttachmentListItem jsObjectActionObject" data-object-id="{@$attachment->getObjectID()}" data-height="{@$attachment->height}" data-width="{@$attachment->width}" data-is-image="{@$attachment->isImage}">
{if $attachment->tinyThumbnailType}
<img src="{$attachment->getThumbnailLink('tiny')}" alt="" class="attachmentTinyThumbnail">
{else}
{icon size=64 name=$attachment->getIconName()}
{/if}

<div>
<div>
<p><a href="{$attachment->getLink()}" target="_blank"{if $attachment->isImage} title="{$attachment->filename}" class="jsImageViewer"{/if}>{$attachment->filename}</a></p>
<small>{@$attachment->filesize|filesize}</small>
</div>

<ul class="buttonGroup">
<li><button type="button" class="button small jsObjectAction" data-object-action="delete" data-confirm-message="{lang}wcf.attachment.delete.sure{/lang}">{lang}wcf.global.button.delete{/lang}</button></li>
{if $attachment->isImage}
{if $attachment->thumbnailType}<li><button type="button" class="button small jsButtonAttachmentInsertThumbnail" data-object-id="{@$attachment->attachmentID}" data-url="{$attachment->getThumbnailLink('thumbnail')}">{lang}wcf.attachment.insertThumbnail{/lang}</button></li>{/if}
<li><button type="button" class="button small jsButtonAttachmentInsertFull" data-object-id="{@$attachment->attachmentID}" data-url="{$attachment->getLink()}">{lang}wcf.attachment.insertFull{/lang}</button></li>
{else}
<li><button type="button" class="button small jsButtonInsertAttachment" data-object-id="{@$attachment->attachmentID}">{lang}wcf.attachment.insert{/lang}</button></li>
{/if}
</ul>
</div>
</li>
<div class="messageTabMenuContent" id="attachments_{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}">
{unsafe:$attachmentHandler->getHtmlElement()}

<div class="attachment__list__existingFiles">
{foreach from=$attachmentHandler->getAttachmentList() item=attachment}
{unsafe:$attachment->toHtmlElement()}
{/foreach}
</ul>
</div>

<dl class="wide">
<dt></dt>
<dd>
<div data-max-size="{@$attachmentHandler->getMaxSize()}"></div>
<div data-max-size="{$attachmentHandler->getMaxSize()}"></div>
<small>{lang}wcf.attachment.upload.limits{/lang}</small>
</dd>
</dl>

<script data-relocate="true">
{jsphrase name='wcf.attachment.insert'}
{jsphrase name='wcf.attachment.insertFull'}
{jsphrase name='wcf.attachment.moreOptions'}

require(["WoltLabSuite/Core/Component/Attachment/List"], ({ setup }) => {
setup("{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}");
});
</script>

{event name='fields'}
</div>

<script data-relocate="true">
$(function() {
WCF.Language.addObject({
'wcf.attachment.upload.error.invalidExtension': '{jslang}wcf.attachment.upload.error.invalidExtension{/jslang}',
'wcf.attachment.upload.error.tooLarge': '{jslang}wcf.attachment.upload.error.tooLarge{/jslang}',
'wcf.attachment.upload.error.reachedLimit': '{jslang}wcf.attachment.upload.error.reachedLimit{/jslang}',
'wcf.attachment.upload.error.reachedRemainingLimit': '{jslang}wcf.attachment.upload.error.reachedRemainingLimit{/jslang}',
'wcf.attachment.upload.error.uploadFailed': '{jslang}wcf.attachment.upload.error.uploadFailed{/jslang}',
'wcf.attachment.upload.error.http413': '{jslang}wcf.attachment.upload.error.http413{/jslang}',
'wcf.attachment.upload.error.uploadPhpLimit': '{jslang}wcf.attachment.upload.error.uploadPhpLimit{/jslang}',
'wcf.attachment.insert': '{jslang}wcf.attachment.insert{/jslang}',
'wcf.attachment.insertAll': '{jslang}wcf.attachment.insertAll{/jslang}',
'wcf.attachment.insertFull': '{jslang}wcf.attachment.insertFull{/jslang}',
'wcf.attachment.insertThumbnail': '{jslang}wcf.attachment.insertThumbnail{/jslang}',
'wcf.attachment.delete.sure': '{jslang}wcf.attachment.delete.sure{/jslang}'
});

new WCF.Attachment.Upload(
$('#attachments_{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if} > dl > dd > div'),
$('#attachments_{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if} > ul'),
'{@$attachmentObjectType}',
'{@$attachmentObjectID}',
'{$tmpHash|encodeJS}',
'{@$attachmentParentObjectID}',
{@$attachmentHandler->getMaxCount()},
'{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}',
{
autoScale: {
enable: {if ATTACHMENT_IMAGE_AUTOSCALE}true{else}false{/if},
maxWidth: {ATTACHMENT_IMAGE_AUTOSCALE_MAX_WIDTH},
maxHeight: {ATTACHMENT_IMAGE_AUTOSCALE_MAX_HEIGHT},
fileType: '{ATTACHMENT_IMAGE_AUTOSCALE_FILE_TYPE}',
quality: {ATTACHMENT_IMAGE_AUTOSCALE_QUALITY / 100}
}
}
);
});
</script>

<input type="hidden" name="tmpHash" value="{$tmpHash}">
95 changes: 25 additions & 70 deletions com.woltlab.wcf/templates/shared_wysiwygAttachmentFormField.tpl
Original file line number Diff line number Diff line change
@@ -1,72 +1,27 @@
<ul id="{$field->getPrefixedID()}_attachmentList" {*
*}class="formAttachmentList jsObjectActionContainer" {*
*}data-object-action-class-name="wcf\data\attachment\AttachmentAction"{*
*}{if !$field->getAttachmentHandler()->getAttachmentList()|count} style="display: none"{/if}{*
*}>
{foreach from=$field->getAttachmentHandler()->getAttachmentList() item=$attachment}
<li class="box64 jsObjectActionObject" {*
*}data-object-id="{@$attachment->getObjectID()}" {*
*}data-height="{@$attachment->height}" {*
*}data-width="{@$attachment->width}" {*
*}data-is-image="{@$attachment->isImage}"{*
*}>
{if $attachment->tinyThumbnailType}
<img src="{$attachment->getThumbnailLink('tiny')}" alt="" class="attachmentTinyThumbnail">
{else}
{icon size=64 name=$attachment->getIconName()}
{/if}

<div>
<div>
<p><a href="{$attachment->getLink()}" target="_blank"{if $attachment->isImage} title="{$attachment->filename}" class="jsImageViewer"{/if}>{$attachment->filename}</a></p>
<small>{@$attachment->filesize|filesize}</small>
</div>

<ul class="buttonGroup">
<li><button type="button" class="button small jsObjectAction" data-object-action="delete" data-confirm-message="{lang}wcf.attachment.delete.sure{/lang}">{lang}wcf.global.button.delete{/lang}</button></li>
{if $attachment->isImage}
{if $attachment->thumbnailType}
<li><button type="button" class="button small jsButtonAttachmentInsertThumbnail" data-object-id="{@$attachment->attachmentID}" data-url="{$attachment->getThumbnailLink('thumbnail')}">{lang}wcf.attachment.insertThumbnail{/lang}</button></li>
{/if}
<li><button type="button" class="button small jsButtonAttachmentInsertFull" data-object-id="{@$attachment->attachmentID}" data-url="{$attachment->getLink()}">{lang}wcf.attachment.insertFull{/lang}</button></li>
{else}
<li><button type="button" class="button small jsButtonInsertAttachment" data-object-id="{@$attachment->attachmentID}">{lang}wcf.attachment.insert{/lang}</button></li>
{/if}
</ul>
</div>
</li>
{/foreach}
</ul>
<div id="{$field->getPrefixedID()}_uploadButton" class="formAttachmentButtons" data-max-size="{@$field->getAttachmentHandler()->getMaxSize()}"></div>
<div class="messageTabMenuContent" id="attachments_{$field->getPrefixedWysiwygId()}">
{unsafe:$field->getAttachmentHandler()->getHtmlElement()}

<script data-relocate="true">
$(function() {
WCF.Language.addObject({
'wcf.attachment.upload.error.invalidExtension': '{jslang}wcf.attachment.upload.error.invalidExtension{/jslang}',
'wcf.attachment.upload.error.tooLarge': '{jslang}wcf.attachment.upload.error.tooLarge{/jslang}',
'wcf.attachment.upload.error.reachedLimit': '{jslang}wcf.attachment.upload.error.reachedLimit{/jslang}',
'wcf.attachment.upload.error.reachedRemainingLimit': '{jslang}wcf.attachment.upload.error.reachedRemainingLimit{/jslang}',
'wcf.attachment.upload.error.uploadFailed': '{jslang}wcf.attachment.upload.error.uploadFailed{/jslang}',
'wcf.attachment.upload.error.http413': '{jslang}wcf.attachment.upload.error.http413{/jslang}',
'wcf.attachment.upload.error.uploadPhpLimit': '{jslang}wcf.attachment.upload.error.uploadPhpLimit{/jslang}',
'wcf.attachment.insert': '{jslang}wcf.attachment.insert{/jslang}',
'wcf.attachment.insertAll': '{jslang}wcf.attachment.insertAll{/jslang}',
'wcf.attachment.insertFull': '{jslang}wcf.attachment.insertFull{/jslang}',
'wcf.attachment.insertThumbnail': '{jslang}wcf.attachment.insertThumbnail{/jslang}',
'wcf.attachment.delete.sure': '{jslang}wcf.attachment.delete.sure{/jslang}'
});

new WCF.Attachment.Upload(
$('#{@$field->getPrefixedID()|encodeJS}_uploadButton'),
$('#{@$field->getPrefixedID()|encodeJS}_attachmentList'),
'{@$field->getAttachmentHandler()->getObjectType()->objectType}',
'{@$field->getAttachmentHandler()->getObjectID()}',
'{$field->getAttachmentHandler()->getTmpHashes()[0]|encodeJS}',
'{@$field->getAttachmentHandler()->getParentObjectID()}',
{@$field->getAttachmentHandler()->getMaxCount()},
'{@$field->getPrefixedWysiwygId()}'
);
});
</script>
<div class="attachment__list__existingFiles">
{foreach from=$field->getAttachmentHandler()->getAttachmentList() item=attachment}
{unsafe:$attachment->toHtmlElement()}
{/foreach}
</div>

<dl class="wide">
<dt></dt>
<dd>
<div data-max-size="{$field->getAttachmentHandler()->getMaxSize()}"></div>
<small>{lang}wcf.attachment.upload.limits{/lang}</small>
</dd>
</dl>

<input type="hidden" id="{$field->getPrefixedID()}_tmpHash" name="{$field->getPrefixedID()}_tmpHash" value="{$field->getAttachmentHandler()->getTmpHashes()[0]}">
<script data-relocate="true">
{jsphrase name='wcf.attachment.insert'}
{jsphrase name='wcf.attachment.insertFull'}
{jsphrase name='wcf.attachment.moreOptions'}

require(["WoltLabSuite/Core/Component/Attachment/List"], ({ setup }) => {
setup("{$field->getPrefixedWysiwygId()}");
});
</script>
</div>
2 changes: 1 addition & 1 deletion com.woltlab.wcf/userGroupOption.xml
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@
<!-- /admin.management -->
<option name="user.attachment.maxSize">
<categoryname>user.message.attachment</categoryname>
<optiontype>fileSize</optiontype>
<optiontype>uploadLimit</optiontype>
<defaultvalue>2000000</defaultvalue>
<minvalue>10000</minvalue>
</option>
Expand Down
25 changes: 19 additions & 6 deletions ts/WoltLabSuite/Core/Ajax/Backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const enum RequestType {
POST,
}

type Payload = FormData | Record<string, unknown>;
type Payload = Blob | FormData | Record<string, unknown>;

class SetupRequest {
private readonly url: string;
Expand All @@ -50,6 +50,7 @@ let ignoreConnectionErrors = false;
window.addEventListener("beforeunload", () => (ignoreConnectionErrors = true));

class BackendRequest {
readonly #headers = new Map<string, string>();
readonly #url: string;
readonly #type: RequestType;
readonly #payload?: Payload;
Expand Down Expand Up @@ -77,6 +78,12 @@ class BackendRequest {
return this;
}

withHeader(key: string, value: string): this {
this.#headers.set(key, value);

return this;
}

protected allowCaching(): this {
this.#allowCaching = true;

Expand Down Expand Up @@ -117,12 +124,13 @@ class BackendRequest {
async #fetch(requestOptions: RequestInit = {}): Promise<Response | undefined> {
registerGlobalRejectionHandler();

this.#headers.set("X-Requested-With", "XMLHttpRequest");
this.#headers.set("X-XSRF-TOKEN", getXsrfToken());
const headers = Object.fromEntries(this.#headers);

const init: RequestInit = extend(
{
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-XSRF-TOKEN": getXsrfToken(),
},
headers,
mode: "same-origin",
credentials: "same-origin",
cache: this.#allowCaching ? "default" : "no-store",
Expand All @@ -135,14 +143,19 @@ class BackendRequest {
init.method = "POST";

if (this.#payload) {
if (this.#payload instanceof FormData) {
if (this.#payload instanceof Blob) {
init.headers!["Content-Type"] = "application/octet-stream";
dtdesign marked this conversation as resolved.
Show resolved Hide resolved
init.body = this.#payload;
} else if (this.#payload instanceof FormData) {
init.headers!["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8";
init.body = this.#payload;
} else {
init.headers!["Content-Type"] = "application/json; charset=UTF-8";
init.body = JSON.stringify(this.#payload);
}
}
} else if (this.#type === RequestType.DELETE) {
init.method = "DELETE";
} else {
init.method = "GET";
}
Expand Down
2 changes: 1 addition & 1 deletion ts/WoltLabSuite/Core/Api/Error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class ApiError {
}
}

class ValidationError {
export class ValidationError {
constructor(
public readonly code: string,
public readonly message: string,
Expand Down
38 changes: 38 additions & 0 deletions ts/WoltLabSuite/Core/Api/Files/Chunk/Chunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
import { ApiResult, apiResultFromError, apiResultFromValue } from "../../Result";

export type ResponseIncomplete = {
completed: false;
};
export type ResponseCompleted = {
completed: true;
generateThumbnails: boolean;
fileID: number;
objectTypeID: number | null;
mimeType: string;
link: string;
data: Record<string, unknown>;
};

export type Response = ResponseIncomplete | ResponseCompleted;

export async function uploadChunk(
identifier: string,
sequenceNo: number,
checksum: string,
payload: Blob,
): Promise<ApiResult<Response>> {
const url = new URL(`${window.WSC_API_URL}index.php?api/rpc/core/files/upload/${identifier}/chunk/${sequenceNo}`);

let response: Response;
try {
response = (await prepareRequest(url)
.post(payload)
.withHeader("chunk-checksum-sha256", checksum)
.fetchAsJson()) as Response;
} catch (e) {
return apiResultFromError(e);
}

return apiResultFromValue(response);
}
12 changes: 12 additions & 0 deletions ts/WoltLabSuite/Core/Api/Files/DeleteFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result";

export async function deleteFile(fileId: number): Promise<ApiResult<[]>> {
try {
await prepareRequest(`${window.WSC_API_URL}index.php?api/rpc/core/files/${fileId}`).delete().fetchAsJson();
} catch (e) {
return apiResultFromError(e);
}

return apiResultFromValue([]);
}
Loading
Loading