initial v1.0-beta commit

This commit is contained in:
Reese Norris
2025-05-12 17:21:16 -07:00
commit cde3128509
68 changed files with 12495 additions and 0 deletions

3
web/javascript/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
.DS_Store
dist

View File

@@ -0,0 +1 @@
Typescript source for frontend UI application

3935
web/javascript/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
{
"name": "openfsd-frontend",
"version": "1.0.0",
"description": "Frontend javascript for openfsd web interface",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/jquery": "^3.5.32",
"ts-loader": "^9.5.2",
"typescript": "^5.8.3",
"webpack": "^5.99.7",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.1"
},
"dependencies": {
"jose": "^6.0.10",
"jquery": "^3.7.1"
}
}

View File

@@ -0,0 +1,84 @@
import $ from "jquery";
import jqXHR = JQuery.jqXHR;
import { decodeJwt } from "jose";
interface APIV1Response {
version: string,
err: string | null,
data: any | null,
}
export async function doAPIRequest(method: string, url: string, withAuth: boolean, data: any): Promise<APIV1Response> {
return new Promise(async (resolve, reject) => {
let accessToken = "";
if (withAuth) {
accessToken = await getAccessToken();
}
$.ajax({
url: url,
method: method,
headers: withAuth ? {"Authorization": `Bearer ${accessToken}`} : {},
contentType: "application/json",
data: JSON.stringify(data),
dataType: "json",
}).done((data) => {
resolve(data)
}).fail((data) => {
resolve(data)
});
});
}
async function doAPIRequestWithoutBearerToken(method: string, url: string, data: any): Promise<jqXHR> {
return $.ajax(url, {
method: method,
data: JSON.stringify(data),
})
}
async function doAPIRequestWithBearerToken(method: string, url: string, data: any): Promise<jqXHR> {
const accessToken = await getAccessToken();
return $.ajax(url, {
method: method,
headers: {"Authorization": `Bearer ${accessToken}`},
data: JSON.stringify(data),
})
}
// getAccessToken returns the current valid access token.
// An exception is thrown if no token is found, an error occurs refreshing the access token,
// or if the refresh token is expired.
async function getAccessToken(): Promise<string> {
const storedAccessToken = localStorage.getItem("access_token")!;
const jwtPayload = decodeJwt(storedAccessToken);
const exp = jwtPayload.exp!;
const now = Math.floor(Date.now() / 1000);
if (exp < (now + 15)) { // Assuming corrected logic
const newAccessToken = await refreshAccessToken();
localStorage.setItem("access_token", newAccessToken);
return newAccessToken;
}
return storedAccessToken;
}
async function refreshAccessToken(): Promise<string> {
const storedRefreshToken = localStorage.getItem("refresh_token")!;
const jwtPayload = decodeJwt(storedRefreshToken);
const exp = jwtPayload.exp!;
const now = Math.floor(Date.now() / 1000);
if (exp < (now + 15)) {
throw new Error("refresh token expired");
}
return new Promise((resolve, reject) => {
$.ajax({
url: "/api/v1/auth/refresh",
method: "POST",
contentType: "application/json",
data: JSON.stringify({ 'refresh_token': storedRefreshToken }),
}).done((data) => resolve(data['access_token']))
.fail(() => reject(new Error("failed to refresh access token")));
});
}

View File

@@ -0,0 +1 @@
import './pages/login/login'

View File

@@ -0,0 +1,22 @@
import $ from 'jquery';
import { doAPIRequest } from "../../api/api";
$("#login-form").on('submit', async (ev) => {
ev.preventDefault()
const requestBody = {
'cid': $("#login-input-cid").val() as string,
'password': $("#login-input-password").val(),
'remember_me': $("#login-input-remember-me").prop('checked')
}
const res = await doAPIRequest('POST', '/api/v1/auth/login', false, requestBody);
if (res.err !== null) {
alert(`Error signing in: ${res.err}`);
return
}
localStorage.setItem("access_token", res.data['access_token'])
localStorage.setItem("refresh_token", res.data['refresh_token'])
})

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"outDir": "./dist/",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node"
},
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,22 @@
const path = require('path');
module.exports = {
entry: './src/index.ts',
output: {
filename: 'openfsd-bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.ts', '.js'],
},
mode: "production",
};