</section>
<script data-relocate="true">
- require(['Language', 'WoltLabSuite/Core/Ui/User/Session/Delete'], function(Language, UserSessionDelete) {
- Language.addObject({
- 'wcf.user.security.deleteSession.confirmMessage': '{jslang}wcf.user.security.deleteSession.confirmMessage{/jslang}',
- });
+ require(['WoltLabSuite/Core/Ui/User/Session/Delete'], ({ setup }) => {
+ {jsphrase name='wcf.user.security.deleteSession.confirmMessage'}
- new (UserSessionDelete.default)();
+ setup();
});
</script>
import { extend, getXsrfToken } from "../Core";
const enum RequestType {
+ DELETE,
GET,
POST,
}
this.url = url;
}
+ delete(): BackendRequest {
+ return new BackendRequest(this.url, RequestType.DELETE);
+ }
+
get(): GetRequest {
return new GetRequest(this.url, RequestType.GET);
}
--- /dev/null
+type RequestFailureType = "api_error" | "invalid_request_error";
+
+export class ApiError {
+ constructor(
+ public readonly type: RequestFailureType,
+ public readonly code: string,
+ public readonly message: string,
+ public readonly param: string,
+ ) {}
+}
--- /dev/null
+import { StatusNotOk } from "../Ajax/Error";
+import { isPlainObject } from "../Core";
+import { ApiError } from "./Error";
+
+export type ApiResult<T> =
+ | {
+ ok: true;
+ value: T;
+ unwrap(): T;
+ }
+ | {
+ ok: false;
+ error: ApiError;
+ unwrap(): never;
+ };
+
+export function apiResultFromValue<T>(value: T): ApiResult<T> {
+ return {
+ ok: true,
+ value,
+ unwrap() {
+ return value;
+ },
+ };
+}
+
+export function apiResultFromError(error: ApiError): ApiResult<never> {
+ return {
+ ok: false,
+ error,
+ unwrap() {
+ throw error;
+ },
+ };
+}
+
+export async function apiResultFromStatusNotOk(e: StatusNotOk): Promise<ApiResult<never>> {
+ const { response } = e;
+
+ if (response === undefined) {
+ // Aborted requests do not have a return value.
+ throw e;
+ }
+
+ const contentType = response.headers.get("content-type");
+ if (!contentType || !contentType.includes("application/json")) {
+ throw e;
+ }
+
+ let json: unknown;
+ try {
+ json = await response.json();
+ } catch {
+ throw e;
+ }
+
+ if (
+ isPlainObject(json) &&
+ Object.hasOwn(json, "type") &&
+ (json.type === "api_error" || json.type === "invalid_request_error") &&
+ typeof json.code === "string" &&
+ typeof json.message === "string" &&
+ typeof json.param === "string"
+ ) {
+ return apiResultFromError(new ApiError(json.type, json.code, json.message, json.param));
+ }
+
+ throw e;
+}
--- /dev/null
+import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
+import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result";
+
+export async function deleteSession(sessionId: string): Promise<ApiResult<[]>> {
+ try {
+ await prepareRequest(`${window.WSC_API_URL}index.php?api/rpc/core/sessions/${sessionId}`).delete().fetchAsJson();
+ } catch (e) {
+ return apiResultFromError(e);
+ }
+
+ return apiResultFromValue([]);
+}
/**
* Returns true if `obj` is an object literal.
*/
-export function isPlainObject(obj: unknown): boolean {
+export function isPlainObject(obj: unknown): obj is Record<string, unknown> {
if (typeof obj !== "object" || obj === null) {
return false;
}
const data = JSON.parse(target.dataset.parameters || "");
if (Core.isPlainObject(data)) {
Object.keys(data).forEach((key) => {
- parameters.set(key, data[key]);
+ parameters.set(key, data[key] as string);
});
}
} catch (e) {
* @woltlabExcludeBundle all
*/
-import * as Ajax from "../../../Ajax";
-import { AjaxCallbackObject, AjaxCallbackSetup, DatabaseObjectActionResponse } from "../../../Ajax/Data";
import * as UiNotification from "../../Notification";
import * as UiConfirmation from "../../Confirmation";
import * as Language from "../../../Language";
-import * as Core from "../../../Core";
+import { deleteSession } from "WoltLabSuite/Core/Api/Sessions/DeleteSession";
-export class UiUserSessionDelete implements AjaxCallbackObject {
- private readonly knownElements = new Map<string, HTMLElement>();
+function onClick(button: HTMLElement): void {
+ UiConfirmation.show({
+ message: Language.get("wcf.user.security.deleteSession.confirmMessage"),
+ confirm: async (_parameters) => {
+ (await deleteSession(button.dataset.sessionId!)).unwrap();
- /**
- * Initializes the session delete buttons.
- */
- constructor() {
- document.querySelectorAll(".sessionDeleteButton").forEach((element: HTMLElement) => {
- if (!element.dataset.sessionId) {
- throw new Error(`No sessionId for session delete button given.`);
- }
+ button.closest("li")?.remove();
- if (!this.knownElements.has(element.dataset.sessionId)) {
- element.addEventListener("click", (ev) => this.delete(element, ev));
-
- this.knownElements.set(element.dataset.sessionId, element);
- }
- });
- }
-
- /**
- * Opens the user trophy list for a specific user.
- */
- private delete(element: HTMLElement, event: MouseEvent): void {
- event.preventDefault();
-
- UiConfirmation.show({
- message: Language.get("wcf.user.security.deleteSession.confirmMessage"),
- confirm: (_parameters) => {
- Ajax.api(this, {
- t: Core.getXsrfToken(),
- sessionID: element.dataset.sessionId,
- });
- },
- });
- }
-
- _ajaxSuccess(data: AjaxResponse): void {
- const element = this.knownElements.get(data.sessionID);
-
- if (element !== undefined) {
- const sessionItem = element.closest("li");
-
- if (sessionItem !== null) {
- sessionItem.remove();
- }
- }
-
- UiNotification.show();
- }
-
- _ajaxSetup(): ReturnType<AjaxCallbackSetup> {
- return {
- url: "index.php?delete-session/",
- };
- }
+ UiNotification.show();
+ },
+ });
}
-export default UiUserSessionDelete;
-
-interface AjaxResponse extends DatabaseObjectActionResponse {
- sessionID: string;
+export function setup() {
+ document.querySelectorAll(".sessionDeleteButton").forEach((element: HTMLElement) => {
+ element.addEventListener("click", () => onClick(element));
+ });
}
constructor(url) {
this.url = url;
}
+ delete() {
+ return new BackendRequest(this.url, 0 /* RequestType.DELETE */);
+ }
get() {
- return new GetRequest(this.url, 0 /* RequestType.GET */);
+ return new GetRequest(this.url, 1 /* RequestType.GET */);
}
post(payload) {
- return new BackendRequest(this.url, 1 /* RequestType.POST */, payload);
+ return new BackendRequest(this.url, 2 /* RequestType.POST */, payload);
}
}
let ignoreConnectionErrors = false;
cache: this.#allowCaching ? "default" : "no-store",
redirect: "error",
}, requestOptions);
- if (this.#type === 1 /* RequestType.POST */) {
+ if (this.#type === 2 /* RequestType.POST */) {
init.method = "POST";
if (this.#payload) {
if (this.#payload instanceof FormData) {
--- /dev/null
+define(["require", "exports"], function (require, exports) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.ApiError = void 0;
+ class ApiError {
+ type;
+ code;
+ message;
+ param;
+ constructor(type, code, message, param) {
+ this.type = type;
+ this.code = code;
+ this.message = message;
+ this.param = param;
+ }
+ }
+ exports.ApiError = ApiError;
+});
--- /dev/null
+define(["require", "exports", "../Core", "./Error"], function (require, exports, Core_1, Error_1) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.apiResultFromStatusNotOk = exports.apiResultFromError = exports.apiResultFromValue = void 0;
+ function apiResultFromValue(value) {
+ return {
+ ok: true,
+ value,
+ unwrap() {
+ return value;
+ },
+ };
+ }
+ exports.apiResultFromValue = apiResultFromValue;
+ function apiResultFromError(error) {
+ return {
+ ok: false,
+ error,
+ unwrap() {
+ throw error;
+ },
+ };
+ }
+ exports.apiResultFromError = apiResultFromError;
+ async function apiResultFromStatusNotOk(e) {
+ const { response } = e;
+ if (response === undefined) {
+ // Aborted requests do not have a return value.
+ throw e;
+ }
+ const contentType = response.headers.get("content-type");
+ if (!contentType || !contentType.includes("application/json")) {
+ throw e;
+ }
+ let json;
+ try {
+ json = await response.json();
+ }
+ catch {
+ throw e;
+ }
+ if ((0, Core_1.isPlainObject)(json) &&
+ Object.hasOwn(json, "type") &&
+ (json.type === "api_error" || json.type === "invalid_request_error") &&
+ typeof json.code === "string" &&
+ typeof json.message === "string" &&
+ typeof json.param === "string") {
+ return apiResultFromError(new Error_1.ApiError(json.type, json.code, json.message, json.param));
+ }
+ throw e;
+ }
+ exports.apiResultFromStatusNotOk = apiResultFromStatusNotOk;
+});
--- /dev/null
+define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], function (require, exports, Backend_1, Result_1) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.deleteSession = void 0;
+ async function deleteSession(sessionId) {
+ try {
+ await (0, Backend_1.prepareRequest)(`${window.WSC_API_URL}index.php?api/rpc/core/sessions/${sessionId}`).delete().fetchAsJson();
+ }
+ catch (e) {
+ return (0, Result_1.apiResultFromError)(e);
+ }
+ return (0, Result_1.apiResultFromValue)([]);
+ }
+ exports.deleteSession = deleteSession;
+});
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @woltlabExcludeBundle all
*/
-define(["require", "exports", "tslib", "../../../Ajax", "../../Notification", "../../Confirmation", "../../../Language", "../../../Core"], function (require, exports, tslib_1, Ajax, UiNotification, UiConfirmation, Language, Core) {
+define(["require", "exports", "tslib", "../../Notification", "../../Confirmation", "../../../Language", "WoltLabSuite/Core/Api/Sessions/DeleteSession"], function (require, exports, tslib_1, UiNotification, UiConfirmation, Language, DeleteSession_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
- exports.UiUserSessionDelete = void 0;
- Ajax = tslib_1.__importStar(Ajax);
+ exports.setup = void 0;
UiNotification = tslib_1.__importStar(UiNotification);
UiConfirmation = tslib_1.__importStar(UiConfirmation);
Language = tslib_1.__importStar(Language);
- Core = tslib_1.__importStar(Core);
- class UiUserSessionDelete {
- knownElements = new Map();
- /**
- * Initializes the session delete buttons.
- */
- constructor() {
- document.querySelectorAll(".sessionDeleteButton").forEach((element) => {
- if (!element.dataset.sessionId) {
- throw new Error(`No sessionId for session delete button given.`);
- }
- if (!this.knownElements.has(element.dataset.sessionId)) {
- element.addEventListener("click", (ev) => this.delete(element, ev));
- this.knownElements.set(element.dataset.sessionId, element);
- }
- });
- }
- /**
- * Opens the user trophy list for a specific user.
- */
- delete(element, event) {
- event.preventDefault();
- UiConfirmation.show({
- message: Language.get("wcf.user.security.deleteSession.confirmMessage"),
- confirm: (_parameters) => {
- Ajax.api(this, {
- t: Core.getXsrfToken(),
- sessionID: element.dataset.sessionId,
- });
- },
- });
- }
- _ajaxSuccess(data) {
- const element = this.knownElements.get(data.sessionID);
- if (element !== undefined) {
- const sessionItem = element.closest("li");
- if (sessionItem !== null) {
- sessionItem.remove();
- }
- }
- UiNotification.show();
- }
- _ajaxSetup() {
- return {
- url: "index.php?delete-session/",
- };
- }
+ function onClick(button) {
+ UiConfirmation.show({
+ message: Language.get("wcf.user.security.deleteSession.confirmMessage"),
+ confirm: async (_parameters) => {
+ (await (0, DeleteSession_1.deleteSession)(button.dataset.sessionId)).unwrap();
+ button.closest("li")?.remove();
+ UiNotification.show();
+ },
+ });
}
- exports.UiUserSessionDelete = UiUserSessionDelete;
- exports.default = UiUserSessionDelete;
+ function setup() {
+ document.querySelectorAll(".sessionDeleteButton").forEach((element) => {
+ element.addEventListener("click", () => onClick(element));
+ });
+ }
+ exports.setup = setup;
});
$eventHandler->register(ControllerCollecting::class, static function (ControllerCollecting $event) {
$event->register(new \wcf\system\endpoint\controller\core\messages\MentionSuggestions);
+ $event->register(new \wcf\system\endpoint\controller\core\sessions\DeleteSession);
});
try {
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\sessions;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\system\endpoint\DeleteRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\session\SessionHandler;
+use wcf\system\WCF;
+
+#[DeleteRequest('/core/sessions/{id}')]
+final class DeleteSession implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $sessionID = $variables['id'];
+
+ if (!$this->isOwnSessionID($sessionID)) {
+ throw new IllegalLinkException();
+ }
+
+ SessionHandler::getInstance()->deleteUserSession($sessionID);
+
+ return new JsonResponse([]);
+ }
+
+ private function isOwnSessionID(string $sessionID): bool
+ {
+ foreach (SessionHandler::getInstance()->getUserSessions(WCF::getUser()) as $session) {
+ if ($session->getSessionID() === $sessionID) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}