web: all the basic commands are working: build, build types, lint source, lint lockfile, lint packagefile, lint types, lint spelling, format source, format package.
This commit is contained in:
7
web/package-lock.json
generated
7
web/package-lock.json
generated
@ -33,6 +33,7 @@
|
|||||||
"@patternfly/elements": "^3.0.2",
|
"@patternfly/elements": "^3.0.2",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
"@sentry/browser": "^8.24.0",
|
"@sentry/browser": "^8.24.0",
|
||||||
|
"@types/webappsec-credential-management": "^0.6.8",
|
||||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"chart.js": "^4.4.3",
|
"chart.js": "^4.4.3",
|
||||||
@ -8816,6 +8817,11 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/webappsec-credential-management": {
|
||||||
|
"version": "0.6.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webappsec-credential-management/-/webappsec-credential-management-0.6.8.tgz",
|
||||||
|
"integrity": "sha512-DES/SkK54U7AG8hmMkGCJkOSlywM3R+TzaWT+rBnX3lQTJ3K57jWr+UccWY8ImkuKekC9BjB+AH4zLJB4JKpvQ=="
|
||||||
|
},
|
||||||
"node_modules/@types/which": {
|
"node_modules/@types/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -26150,6 +26156,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/browser": "^8.23.0",
|
"@sentry/browser": "^8.23.0",
|
||||||
|
"@types/webappsec-credential-management": "^0.6.8",
|
||||||
"base64-js": "^1.5.1"
|
"base64-js": "^1.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
16
web/packages/common/.prettierignore
Normal file
16
web/packages/common/.prettierignore
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# don't ever lint node_modules
|
||||||
|
node_modules
|
||||||
|
# don't lint build output (make sure it's set to your correct build folder name)
|
||||||
|
dist
|
||||||
|
# don't lint nyc coverage output
|
||||||
|
coverage
|
||||||
|
# Import order matters
|
||||||
|
poly.ts
|
||||||
|
src/locale-codes.ts
|
||||||
|
src/locales/
|
||||||
|
storybook-static/
|
||||||
|
# Prettier breaks the tsconfig file
|
||||||
|
tsconfig.json
|
||||||
|
.storybook/css-import-maps*
|
||||||
|
package.json
|
||||||
|
packages/**/package.json
|
23
web/packages/common/.prettierrc.json
Normal file
23
web/packages/common/.prettierrc.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "always",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"embeddedLanguageFormatting": "auto",
|
||||||
|
"htmlWhitespaceSensitivity": "css",
|
||||||
|
"insertPragma": false,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"printWidth": 100,
|
||||||
|
"proseWrap": "preserve",
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"requirePragma": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": false,
|
||||||
|
"vueIndentScriptAndStyle": false,
|
||||||
|
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
||||||
|
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
|
||||||
|
"importOrderSeparation": true,
|
||||||
|
"importOrderSortSpecifiers": true,
|
||||||
|
"importOrderParserPlugins": ["typescript", "classProperties", "decorators-legacy"]
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
# @goauthentik/common
|
# @goauthentik/common
|
||||||
|
|
||||||
The `common` package is a bit of a grab-bag of tools, utilities, and configuration details used
|
The `common` package is a bit of a grab-bag of tools, utilities, and configuration details used
|
||||||
throughout the Authentik front-end suite. Here, we'll try (emphasis on the *try*) to document what
|
throughout the Authentik front-end suite. Here, we'll try (emphasis on the _try_) to document what
|
||||||
each part does.
|
each part does.
|
||||||
|
|
||||||
- `./api`
|
- `./api`
|
||||||
@ -59,6 +59,7 @@ which it was invoked. Used by our Django application to preload configuration i
|
|||||||
- `./labels.ts`,
|
- `./labels.ts`,
|
||||||
|
|
||||||
Maps a variety of API tokens to human-readable labels, including those for:
|
Maps a variety of API tokens to human-readable labels, including those for:
|
||||||
|
|
||||||
- Events
|
- Events
|
||||||
- Severities
|
- Severities
|
||||||
- User Types
|
- User Types
|
||||||
@ -90,5 +91,3 @@ YouMightNotNeedLodash, a slugifier, some date handling utilities, that sort of t
|
|||||||
- `./ws.ts`
|
- `./ws.ts`
|
||||||
|
|
||||||
Sets up our web socket for receiving server-side events. Used by all three major interfaces.
|
Sets up our web socket for receiving server-side events. Used by all three major interfaces.
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ export default [
|
|||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
"dist/",
|
"dist/",
|
||||||
|
".wireit/",
|
||||||
|
"packages/common/.wireit/",
|
||||||
// don't ever lint node_modules
|
// don't ever lint node_modules
|
||||||
"node_modules/",
|
"node_modules/",
|
||||||
".storybook/*",
|
".storybook/*",
|
||||||
|
@ -1,23 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/common",
|
"name": "@goauthentik/common",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
"./*": "./dist/*"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"./dist/**/*"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/browser": "^8.23.0",
|
"@sentry/browser": "^8.23.0",
|
||||||
|
"@types/webappsec-credential-management": "^0.6.8",
|
||||||
"base64-js": "^1.5.1"
|
"base64-js": "^1.5.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
|
||||||
"@lit/localize": "^0.12.2",
|
|
||||||
"lit": "^3.2.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.8.0",
|
"@eslint/js": "^9.8.0",
|
||||||
"@types/eslint__js": "^8.42.3",
|
"@types/eslint__js": "^8.42.3",
|
||||||
@ -39,6 +27,18 @@
|
|||||||
"typesync": "^0.13.0",
|
"typesync": "^0.13.0",
|
||||||
"wireit": "^0.14.4"
|
"wireit": "^0.14.4"
|
||||||
},
|
},
|
||||||
|
"exports": {
|
||||||
|
"./*": "./dist/*"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./dist/**/*"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@lit/localize": "^0.12.2",
|
||||||
|
"lit": "^3.2.0"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "wireit",
|
"build": "wireit",
|
||||||
"build:types": "wireit",
|
"build:types": "wireit",
|
||||||
@ -47,11 +47,12 @@
|
|||||||
"lint:lockfile": "wireit",
|
"lint:lockfile": "wireit",
|
||||||
"lint:nightmare": "wireit",
|
"lint:nightmare": "wireit",
|
||||||
"lint:package": "wireit",
|
"lint:package": "wireit",
|
||||||
"lint:precommit": "wireit",
|
|
||||||
"lint:spelling": "wireit",
|
"lint:spelling": "wireit",
|
||||||
"lint:types": "wireit",
|
"lint:types": "wireit",
|
||||||
|
"precommit": "wireit",
|
||||||
"prettier": "wireit"
|
"prettier": "wireit"
|
||||||
},
|
},
|
||||||
|
"type": "module",
|
||||||
"wireit": {
|
"wireit": {
|
||||||
"build": {
|
"build": {
|
||||||
"command": "${NODE_RUNNER} build.mjs",
|
"command": "${NODE_RUNNER} build.mjs",
|
||||||
@ -88,16 +89,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
"command": "eslint --max-warnings 0 --fix",
|
"command": "eslint --max-warnings 0 --fix --config ./eslint.config.mjs",
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_OPTIONS": "--max_old_space_size=65536"
|
"NODE_OPTIONS": "--max_old_space_size=65536"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint:types": {
|
"lint:types": {
|
||||||
"command": "tsc --noEmit -p .",
|
"command": "tsc --noEmit -p ."
|
||||||
"dependencies": [
|
|
||||||
"build-locales"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"lint:lockfile": {
|
"lint:lockfile": {
|
||||||
"command": "lockfile-lint --path package.json --type npm --allowed-hosts npm --validate-https"
|
"command": "lockfile-lint --path package.json --type npm --allowed-hosts npm --validate-https"
|
||||||
@ -105,6 +103,15 @@
|
|||||||
"lint:package": {
|
"lint:package": {
|
||||||
"command": "syncpack format -i ' '"
|
"command": "syncpack format -i ' '"
|
||||||
},
|
},
|
||||||
|
"lint:precommit": {
|
||||||
|
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --precommit",
|
||||||
|
"env": {
|
||||||
|
"NODE_RUNNER": {
|
||||||
|
"external": true,
|
||||||
|
"default": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lint:nightmare": {
|
"lint:nightmare": {
|
||||||
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --nightmare",
|
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --nightmare",
|
||||||
"env": {
|
"env": {
|
||||||
@ -114,8 +121,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint:precommit": {
|
"precommit": {
|
||||||
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --precommit",
|
"command": "prettier --write .",
|
||||||
|
"dependencies": [
|
||||||
|
"lint:types",
|
||||||
|
"lint:spelling",
|
||||||
|
"lint:lockfile",
|
||||||
|
"lint:package",
|
||||||
|
"lint:precommit"
|
||||||
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_RUNNER": {
|
"NODE_RUNNER": {
|
||||||
"external": true,
|
"external": true,
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import { execFileSync } from "child_process";
|
|
||||||
import { ESLint } from "eslint";
|
|
||||||
import path from "path";
|
|
||||||
import process from "process";
|
|
||||||
|
|
||||||
// Code assumes this script is in the './web/scripts' folder.
|
|
||||||
const projectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
||||||
encoding: "utf8",
|
|
||||||
}).replace("\n", "");
|
|
||||||
process.chdir(path.join(projectRoot, "./web"));
|
|
||||||
|
|
||||||
const eslintConfig = {
|
|
||||||
overrideConfig: {
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2021: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:lit/recommended",
|
|
||||||
"plugin:custom-elements/recommended",
|
|
||||||
"plugin:storybook/recommended",
|
|
||||||
],
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 12,
|
|
||||||
sourceType: "module",
|
|
||||||
},
|
|
||||||
plugins: ["@typescript-eslint", "lit", "custom-elements", "sonarjs"],
|
|
||||||
rules: {
|
|
||||||
"indent": "off",
|
|
||||||
"linebreak-style": ["error", "unix"],
|
|
||||||
"quotes": ["error", "double", { avoidEscape: true }],
|
|
||||||
"semi": ["error", "always"],
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const eslint = new ESLint(eslintConfig);
|
|
||||||
const results = await eslint.lintFiles(["./src/**/*", "./build.mjs", "./scripts/*.mjs"]);
|
|
||||||
const formatter = await eslint.loadFormatter("stylish");
|
|
||||||
const resultText = formatter.format(results);
|
|
||||||
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
|
|
||||||
|
|
||||||
console.log(resultText);
|
|
||||||
process.exit(errors > 1 ? 1 : 0);
|
|
@ -13,6 +13,7 @@ export default [
|
|||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
"dist/",
|
"dist/",
|
||||||
|
".wireit/",
|
||||||
// don't ever lint node_modules
|
// don't ever lint node_modules
|
||||||
"node_modules/",
|
"node_modules/",
|
||||||
".storybook/*",
|
".storybook/*",
|
||||||
|
@ -32,7 +32,6 @@ export async function popupCenterScreen(
|
|||||||
w: number,
|
w: number,
|
||||||
h: number,
|
h: number,
|
||||||
): Promise<Window | null> {
|
): Promise<Window | null> {
|
||||||
// eslint-disable-next-line no-magic-numbers
|
|
||||||
const [top, left] = [(screen.height - h) / 4, (screen.width - w) / 2];
|
const [top, left] = [(screen.height - h) / 4, (screen.width - w) / 2];
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -3,7 +3,7 @@ import * as base64js from "base64-js";
|
|||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
|
|
||||||
export function b64enc(buf: Uint8Array): string {
|
export function b64enc(buf: Uint8Array): string {
|
||||||
return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function b64RawEnc(buf: Uint8Array): string {
|
export function b64RawEnc(buf: Uint8Array): string {
|
||||||
|
@ -15,6 +15,17 @@ import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api";
|
|||||||
|
|
||||||
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
export const TAG_SENTRY_COMPONENT = "authentik.component";
|
||||||
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
||||||
|
const MIN_PATH_LENGTH = 2;
|
||||||
|
|
||||||
|
// Get the interface name from URL
|
||||||
|
export function currentInterface(): string {
|
||||||
|
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
|
||||||
|
let knownInterface = "unknown";
|
||||||
|
if (pathMatches && pathMatches.length >= MIN_PATH_LENGTH) {
|
||||||
|
knownInterface = pathMatches[1];
|
||||||
|
}
|
||||||
|
return knownInterface.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
export async function configureSentry(canDoPpi = false): Promise<Config> {
|
export async function configureSentry(canDoPpi = false): Promise<Config> {
|
||||||
const cfg = await config();
|
const cfg = await config();
|
||||||
@ -81,13 +92,3 @@ export async function configureSentry(canDoPpi = false): Promise<Config> {
|
|||||||
}
|
}
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the interface name from URL
|
|
||||||
export function currentInterface(): string {
|
|
||||||
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
|
|
||||||
let currentInterface = "unknown";
|
|
||||||
if (pathMatches && pathMatches.length >= 2) {
|
|
||||||
currentInterface = pathMatches[1];
|
|
||||||
}
|
|
||||||
return currentInterface.toLowerCase();
|
|
||||||
}
|
|
||||||
|
@ -5,10 +5,8 @@ import { CoreApi, ResponseError, SessionUser } from "@goauthentik/api";
|
|||||||
|
|
||||||
let globalMePromise: Promise<SessionUser> | undefined;
|
let globalMePromise: Promise<SessionUser> | undefined;
|
||||||
|
|
||||||
export function refreshMe(): Promise<SessionUser> {
|
const HTTP_FORBIDDEN = 403;
|
||||||
globalMePromise = undefined;
|
const HTTP_UNAUTHORIZED = 401;
|
||||||
return me();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function me(): Promise<SessionUser> {
|
export function me(): Promise<SessionUser> {
|
||||||
if (!globalMePromise) {
|
if (!globalMePromise) {
|
||||||
@ -48,7 +46,10 @@ export function me(): Promise<SessionUser> {
|
|||||||
systemPermissions: [],
|
systemPermissions: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (ex.response?.status === 401 || ex.response?.status === 403) {
|
if (
|
||||||
|
ex.response?.status === HTTP_UNAUTHORIZED ||
|
||||||
|
ex.response?.status === HTTP_FORBIDDEN
|
||||||
|
) {
|
||||||
const relativeUrl = window.location
|
const relativeUrl = window.location
|
||||||
.toString()
|
.toString()
|
||||||
.substring(window.location.origin.length);
|
.substring(window.location.origin.length);
|
||||||
@ -61,3 +62,8 @@ export function me(): Promise<SessionUser> {
|
|||||||
}
|
}
|
||||||
return globalMePromise;
|
return globalMePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function refreshMe(): Promise<SessionUser> {
|
||||||
|
globalMePromise = undefined;
|
||||||
|
return me();
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ export function getCookie(name: string): string {
|
|||||||
for (let i = 0; i < cookies.length; i++) {
|
for (let i = 0; i < cookies.length; i++) {
|
||||||
const cookie = cookies[i].trim();
|
const cookie = cookies[i].trim();
|
||||||
// Does this cookie string begin with the name we want?
|
// Does this cookie string begin with the name we want?
|
||||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
if (cookie.substring(0, name.length + 1) === `${name}=`) {
|
||||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -25,21 +25,21 @@ export function convertToSlug(text: string): string {
|
|||||||
.replace(/[^\w-]+/g, "");
|
.replace(/[^\w-]+/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WORD_COUNT_TRUNCATION_DEFAULT = 10;
|
||||||
/**
|
/**
|
||||||
* Truncate a string based on maximum word count
|
* Truncate a string based on maximum word count
|
||||||
*/
|
*/
|
||||||
export function truncateWords(string: string, length = 10): string {
|
export function truncateWords(input: string, length = WORD_COUNT_TRUNCATION_DEFAULT): string {
|
||||||
string = string || "";
|
const words = (input ?? "").trim().split(" ");
|
||||||
const array = string.trim().split(" ");
|
const ellipsis = words.length > length ? "..." : "";
|
||||||
const ellipsis = array.length > length ? "..." : "";
|
return words.slice(0, length).join(" ") + ellipsis;
|
||||||
|
|
||||||
return array.slice(0, length).join(" ") + ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CHAR_COUNT_TRUNCATION_DEFAULT = 10;
|
||||||
/**
|
/**
|
||||||
* Truncate a string based on character count
|
* Truncate a string based on character count
|
||||||
*/
|
*/
|
||||||
export function truncate(string: string, length = 10): string {
|
export function truncate(string: string, length = CHAR_COUNT_TRUNCATION_DEFAULT): string {
|
||||||
return string.length > length ? `${string.substring(0, length)}...` : string;
|
return string.length > length ? `${string.substring(0, length)}...` : string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,20 +83,24 @@ export const ascii_lowercase = "abcdefghijklmnopqrstuvwxyz";
|
|||||||
export const ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
export const ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
export const ascii_letters = ascii_lowercase + ascii_uppercase;
|
export const ascii_letters = ascii_lowercase + ascii_uppercase;
|
||||||
export const digits = "0123456789";
|
export const digits = "0123456789";
|
||||||
export const hexdigits = digits + "abcdef" + "ABCDEF";
|
export const hexdigits = `${digits}abcdefABCDEF}`;
|
||||||
export const octdigits = "01234567";
|
export const octdigits = "01234567";
|
||||||
export const punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
export const punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
||||||
|
|
||||||
|
const BYTE_SIZE = 256;
|
||||||
|
|
||||||
export function randomString(len: number, charset: string): string {
|
export function randomString(len: number, charset: string): string {
|
||||||
const chars = [];
|
const chars = [];
|
||||||
const array = new Uint8Array(len);
|
const array = new Uint8Array(len);
|
||||||
self.crypto.getRandomValues(array);
|
self.crypto.getRandomValues(array);
|
||||||
for (let index = 0; index < len; index++) {
|
for (let index = 0; index < len; index++) {
|
||||||
chars.push(charset[Math.floor(charset.length * (array[index] / Math.pow(2, 8)))]);
|
chars.push(charset[Math.floor(charset.length * (array[index] / BYTE_SIZE))]);
|
||||||
}
|
}
|
||||||
return chars.join("");
|
return chars.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TIMEZONE_OFFSET = 60000; // milliseconds
|
||||||
|
|
||||||
export function dateTimeLocal(date: Date): string {
|
export function dateTimeLocal(date: Date): string {
|
||||||
// So for some reason, the datetime-local input field requires ISO Datetime as value
|
// So for some reason, the datetime-local input field requires ISO Datetime as value
|
||||||
// But the standard javascript date.toISOString() returns everything with seconds and
|
// But the standard javascript date.toISOString() returns everything with seconds and
|
||||||
@ -105,7 +109,7 @@ export function dateTimeLocal(date: Date): string {
|
|||||||
// figure.
|
// figure.
|
||||||
// Additionally, toISOString always returns the date without timezone, which we would like
|
// Additionally, toISOString always returns the date without timezone, which we would like
|
||||||
// to include for better usability
|
// to include for better usability
|
||||||
const tzOffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
const tzOffset = new Date().getTimezoneOffset() * TIMEZONE_OFFSET;
|
||||||
const localISOTime = new Date(date.getTime() - tzOffset).toISOString().slice(0, -1);
|
const localISOTime = new Date(date.getTime() - tzOffset).toISOString().slice(0, -1);
|
||||||
const parts = localISOTime.split(":");
|
const parts = localISOTime.split(":");
|
||||||
return `${parts[0]}:${parts[1]}`;
|
return `${parts[0]}:${parts[1]}`;
|
||||||
@ -122,7 +126,7 @@ export function dateToUTC(date: Date): Date {
|
|||||||
// then subtract the timezone offset to create an "invalid" date (correct time&date)
|
// then subtract the timezone offset to create an "invalid" date (correct time&date)
|
||||||
// but it still "thinks" it's in local TZ
|
// but it still "thinks" it's in local TZ
|
||||||
const timestamp = date.getTime();
|
const timestamp = date.getTime();
|
||||||
const offset = -1 * (new Date().getTimezoneOffset() * 60000);
|
const offset = -1 * (new Date().getTimezoneOffset() * TIMEZONE_OFFSET);
|
||||||
return new Date(timestamp - offset);
|
return new Date(timestamp - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,13 +155,26 @@ export function adaptCSS(sheet: AdaptableStylesheet | AdaptableStylesheet[]): Ad
|
|||||||
return Array.isArray(sheet) ? sheet.map(_adaptCSS) : _adaptCSS(sheet);
|
return Array.isArray(sheet) ? sheet.map(_adaptCSS) : _adaptCSS(sheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SECONDS_IN_A_MINUTE = 60;
|
||||||
|
const MINUTES_IN_AN_HOUR = 60;
|
||||||
|
const HOURS_IN_A_DAY = 24;
|
||||||
|
const DAYS_IN_A_YEAR = 365;
|
||||||
|
const MONTHS_IN_A_YEAR = 12;
|
||||||
|
|
||||||
|
const MILLISECONDS_IN_A_SECOND = 1000;
|
||||||
|
const MILLISECONDS_IN_A_MINUTE = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
|
||||||
|
const MILLISECONDS_IN_AN_HOUR = MILLISECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR;
|
||||||
|
const MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_AN_HOUR * HOURS_IN_A_DAY;
|
||||||
|
const MILLISECONDS_IN_A_YEAR = MILLISECONDS_IN_A_DAY * DAYS_IN_A_YEAR;
|
||||||
|
const MILLISECONDS_IN_A_MONTH = MILLISECONDS_IN_A_YEAR / MONTHS_IN_A_YEAR;
|
||||||
|
|
||||||
const _timeUnits = new Map<Intl.RelativeTimeFormatUnit, number>([
|
const _timeUnits = new Map<Intl.RelativeTimeFormatUnit, number>([
|
||||||
["year", 24 * 60 * 60 * 1000 * 365],
|
["year", MILLISECONDS_IN_A_YEAR],
|
||||||
["month", (24 * 60 * 60 * 1000 * 365) / 12],
|
["month", MILLISECONDS_IN_A_MONTH],
|
||||||
["day", 24 * 60 * 60 * 1000],
|
["day", MILLISECONDS_IN_A_DAY],
|
||||||
["hour", 60 * 60 * 1000],
|
["hour", MILLISECONDS_IN_AN_HOUR],
|
||||||
["minute", 60 * 1000],
|
["minute", MILLISECONDS_IN_A_MINUTE],
|
||||||
["second", 1000],
|
["second", MILLISECONDS_IN_A_SECOND],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function getRelativeTime(d1: Date, d2: Date = new Date()): string {
|
export function getRelativeTime(d1: Date, d2: Date = new Date()): string {
|
||||||
@ -166,9 +183,9 @@ export function getRelativeTime(d1: Date, d2: Date = new Date()): string {
|
|||||||
|
|
||||||
// "Math.abs" accounts for both "past" & "future" scenarios
|
// "Math.abs" accounts for both "past" & "future" scenarios
|
||||||
for (const [key, value] of _timeUnits) {
|
for (const [key, value] of _timeUnits) {
|
||||||
if (Math.abs(elapsed) > value || key == "second") {
|
if (Math.abs(elapsed) > value || key === "second") {
|
||||||
return rtf.format(Math.round(elapsed / value), key);
|
return rtf.format(Math.round(elapsed / value), key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rtf.format(Math.round(elapsed / 1000), "second");
|
return rtf.format(Math.round(elapsed / MILLISECONDS_IN_A_SECOND), "second");
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,14 @@ export interface WSMessage {
|
|||||||
message_type: string;
|
message_type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MESSAGE_RETRY_DELAY = 200; // milliseconds
|
||||||
|
const CLOSE_RETRY_DELAY = 6000; // milliseconds
|
||||||
|
const OPEN_RETRY_DELAY = 200; // milliseconds
|
||||||
|
const RETRY_BACKOFF = 2;
|
||||||
|
|
||||||
export class WebsocketClient {
|
export class WebsocketClient {
|
||||||
messageSocket?: WebSocket;
|
messageSocket?: WebSocket;
|
||||||
retryDelay = 200;
|
retryDelay = MESSAGE_RETRY_DELAY;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
try {
|
try {
|
||||||
@ -20,18 +25,20 @@ export class WebsocketClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connect(): void {
|
connect(): void {
|
||||||
if (navigator.webdriver) return;
|
if (navigator.webdriver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
|
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
|
||||||
window.location.host
|
window.location.host
|
||||||
}/ws/client/`;
|
}/ws/client/`;
|
||||||
this.messageSocket = new WebSocket(wsUrl);
|
this.messageSocket = new WebSocket(wsUrl);
|
||||||
this.messageSocket.addEventListener("open", () => {
|
this.messageSocket.addEventListener("open", () => {
|
||||||
console.debug(`authentik/ws: connected to ${wsUrl}`);
|
console.debug(`authentik/ws: connected to ${wsUrl}`);
|
||||||
this.retryDelay = 200;
|
this.retryDelay = OPEN_RETRY_DELAY;
|
||||||
});
|
});
|
||||||
this.messageSocket.addEventListener("close", (e) => {
|
this.messageSocket.addEventListener("close", (e) => {
|
||||||
console.debug("authentik/ws: closed ws connection", e);
|
console.debug("authentik/ws: closed ws connection", e);
|
||||||
if (this.retryDelay > 6000) {
|
if (this.retryDelay > CLOSE_RETRY_DELAY) {
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent(EVENT_MESSAGE, {
|
new CustomEvent(EVENT_MESSAGE, {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
@ -47,7 +54,7 @@ export class WebsocketClient {
|
|||||||
console.debug(`authentik/ws: reconnecting ws in ${this.retryDelay}ms`);
|
console.debug(`authentik/ws: reconnecting ws in ${this.retryDelay}ms`);
|
||||||
this.connect();
|
this.connect();
|
||||||
}, this.retryDelay);
|
}, this.retryDelay);
|
||||||
this.retryDelay = this.retryDelay * 2;
|
this.retryDelay = this.retryDelay * RETRY_BACKOFF;
|
||||||
});
|
});
|
||||||
this.messageSocket.addEventListener("message", (e) => {
|
this.messageSocket.addEventListener("message", (e) => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
@ -60,7 +67,7 @@ export class WebsocketClient {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.messageSocket.addEventListener("error", () => {
|
this.messageSocket.addEventListener("error", () => {
|
||||||
this.retryDelay = this.retryDelay * 2;
|
this.retryDelay = this.retryDelay * RETRY_BACKOFF;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "./dist/",
|
"outDir": "./dist/",
|
||||||
|
"types": ["webauthn"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@goauthentik/elements/*": ["./src/*"],
|
"@goauthentik/elements/*": ["./src/*"],
|
||||||
"@goauthentik/locales/*": ["src/locales/*"]
|
"@goauthentik/locales/*": ["src/locales/*"]
|
||||||
|
Reference in New Issue
Block a user