Compare commits

...

4 Commits

Author SHA1 Message Date
Corentin Thomasset
119041c185 chore(release): 2.17.0 2023-01-13 18:27:39 +01:00
Corentin Thomasset
4607837f9a feat(new-tool): temperature converter 2023-01-13 18:26:28 +01:00
Corentin Thomasset
f52f7a845c refactor(jwt-parser): simplified code 2023-01-13 14:02:44 +01:00
bastantoine
acc7f0a586 feat(new-tool): jwt parser (#262)
* npm install jwt-decode

* added base tool structure

* added function to decode JWT and display header and payload

* use a table to display the data

* show human readable values

* added switch to toggle display of parsed values

* lint

* replaced basic package-lock.json with pnpm-lock.json

* change the icon of the tool

* simplify return

* use camelCase

* added description of the tool

* always parse the values

* use camelCase...
2022-12-27 09:38:35 +01:00
12 changed files with 458 additions and 61 deletions

View File

@@ -2,6 +2,19 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [2.17.0](https://github.com/CorentinTh/it-tools/compare/v2.16.0...v2.17.0) (2023-01-13)
### Features
* **new-tool:** jwt parser ([#262](https://github.com/CorentinTh/it-tools/issues/262)) ([acc7f0a](https://github.com/CorentinTh/it-tools/commit/acc7f0a586c64500c5f720e70cdbccf9bffe76d9))
* **new-tool:** temperature converter ([4607837](https://github.com/CorentinTh/it-tools/commit/4607837f9a398440e0098f2ba862e8d7422ce94f))
### Refactors
* **jwt-parser:** simplified code ([f52f7a8](https://github.com/CorentinTh/it-tools/commit/f52f7a845c34ce7da57b11c17d261733be89554f))
## [2.16.0](https://github.com/CorentinTh/it-tools/compare/v2.15.0...v2.16.0) (2022-12-21)

View File

@@ -1,6 +1,6 @@
{
"name": "it-tools",
"version": "2.16.0",
"version": "2.17.0",
"description": "Collection of handy online tools for developers, with great UX. ",
"keywords": [
"productivity",
@@ -48,6 +48,7 @@
"fuse.js": "^6.6.2",
"highlight.js": "^11.6.0",
"json5": "^2.2.1",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"mathjs": "^10.6.4",
"mime-types": "^2.1.35",
@@ -57,6 +58,7 @@
"qrcode": "^1.5.1",
"randombytes": "^2.1.0",
"sql-formatter": "^8.2.0",
"ts-pattern": "^4.1.3",
"uuid": "^8.3.2",
"vue": "^3.2.45",
"vue-router": "^4.1.6"

81
pnpm-lock.yaml generated
View File

@@ -42,6 +42,7 @@ specifiers:
highlight.js: ^11.6.0
jsdom: ^19.0.0
json5: ^2.2.1
jwt-decode: ^3.1.2
less: ^4.1.3
lodash: ^4.17.21
mathjs: ^10.6.4
@@ -55,6 +56,7 @@ specifiers:
sql-formatter: ^8.2.0
standard-version: ^9.5.0
start-server-and-test: ^1.14.0
ts-pattern: ^4.1.3
typescript: ~4.5.5
uuid: ^8.3.2
vite: ^2.9.15
@@ -85,6 +87,7 @@ dependencies:
fuse.js: 6.6.2
highlight.js: 11.6.0
json5: 2.2.1
jwt-decode: 3.1.2
lodash: 4.17.21
mathjs: 10.6.4
mime-types: 2.1.35
@@ -94,6 +97,7 @@ dependencies:
qrcode: 1.5.1
randombytes: 2.1.0
sql-formatter: 8.2.0
ts-pattern: 4.1.3
uuid: 8.3.2
vue: 3.2.45
vue-router: 4.1.6_vue@3.2.45
@@ -120,7 +124,7 @@ devDependencies:
eslint: 8.27.0
eslint-config-prettier: 8.5.0_eslint@8.27.0
eslint-import-resolver-typescript: 3.5.2_dcpv4nbdr5ks2h5677xdltrk6e
eslint-plugin-import: 2.26.0_gbipkkcbnjmysmpjttq6vkmfqq
eslint-plugin-import: 2.26.0_eslint@8.27.0
eslint-plugin-vue: 8.7.1_eslint@8.27.0
jsdom: 19.0.0
less: 4.1.3
@@ -130,7 +134,7 @@ devDependencies:
typescript: 4.5.5
vite: 2.9.15_less@4.1.3
vite-plugin-md: 0.12.4_vite@2.9.15
vite-plugin-pwa: 0.11.13_7mbbuzxp22mje5bxdolj2b6yg4
vite-plugin-pwa: 0.11.13_vite@2.9.15
vite-svg-loader: 3.6.0
vitest: 0.13.1_uwxj23d3xojfwkqpytqc7pyhry
vue-tsc: 0.31.4_typescript@4.5.5
@@ -2064,10 +2068,6 @@ packages:
peerDependencies:
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
eslint-plugin-vue: ^8.0.1
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 5.42.1_vfr6z4qvdp6defk3ked6x75zyi
'@typescript-eslint/parser': 5.42.1_4rqwsplhh2ekz63wktwk7d7ht4
@@ -2077,6 +2077,7 @@ packages:
vue-eslint-parser: 8.3.0_eslint@8.27.0
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@vue/reactivity-transform/3.2.45:
@@ -2105,6 +2106,7 @@ packages:
'@vue/runtime-core': 3.2.45
'@vue/shared': 3.2.45
csstype: 2.6.21
dev: false
/@vue/server-renderer/3.2.45_vue@3.2.45:
resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==}
@@ -2114,6 +2116,7 @@ packages:
'@vue/compiler-ssr': 3.2.45
'@vue/shared': 3.2.45
vue: 3.2.45
dev: false
/@vue/shared/3.2.45:
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
@@ -2858,8 +2861,8 @@ packages:
engines: {node: '>=10'}
hasBin: true
dependencies:
JSONStream: 1.3.5
is-text-path: 1.0.1
JSONStream: 1.3.5
lodash: 4.17.21
meow: 8.1.2
split2: 3.2.2
@@ -2988,6 +2991,7 @@ packages:
/csstype/2.6.21:
resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
dev: false
/csstype/3.0.11:
resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==}
@@ -3026,22 +3030,12 @@ packages:
/debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.0.0
dev: true
/debug/3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
dev: true
@@ -3580,8 +3574,6 @@ packages:
dependencies:
debug: 3.2.7
resolve: 1.22.1
transitivePeerDependencies:
- supports-color
dev: true
/eslint-import-resolver-typescript/3.5.2_dcpv4nbdr5ks2h5677xdltrk6e:
@@ -3594,7 +3586,7 @@ packages:
debug: 4.3.4
enhanced-resolve: 5.10.0
eslint: 8.27.0
eslint-plugin-import: 2.26.0_gbipkkcbnjmysmpjttq6vkmfqq
eslint-plugin-import: 2.26.0_eslint@8.27.0
get-tsconfig: 4.2.0
globby: 13.1.2
is-core-module: 2.11.0
@@ -3604,54 +3596,32 @@ packages:
- supports-color
dev: true
/eslint-module-utils/2.7.4_idrr6ghswzssuopqxluk4kfum4:
/eslint-module-utils/2.7.4_eslint@8.27.0:
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: '*'
eslint-import-resolver-node: '*'
eslint-import-resolver-typescript: '*'
eslint-import-resolver-webpack: '*'
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
eslint:
optional: true
eslint-import-resolver-node:
optional: true
eslint-import-resolver-typescript:
optional: true
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 5.42.1_4rqwsplhh2ekz63wktwk7d7ht4
debug: 3.2.7
eslint: 8.27.0
eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 3.5.2_dcpv4nbdr5ks2h5677xdltrk6e
transitivePeerDependencies:
- supports-color
dev: true
/eslint-plugin-import/2.26.0_gbipkkcbnjmysmpjttq6vkmfqq:
/eslint-plugin-import/2.26.0_eslint@8.27.0:
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.42.1_4rqwsplhh2ekz63wktwk7d7ht4
array-includes: 3.1.6
array.prototype.flat: 1.3.1
debug: 2.6.9
doctrine: 2.1.0
eslint: 8.27.0
eslint-import-resolver-node: 0.3.6
eslint-module-utils: 2.7.4_idrr6ghswzssuopqxluk4kfum4
eslint-module-utils: 2.7.4_eslint@8.27.0
has: 1.0.3
is-core-module: 2.11.0
is-glob: 4.0.3
@@ -3659,10 +3629,6 @@ packages:
object.values: 1.1.6
resolve: 1.22.1
tsconfig-paths: 3.14.1
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: true
/eslint-plugin-prettier/4.2.1_v7o5sx5x3wbs57ifz6wc4f76we:
@@ -4838,6 +4804,10 @@ packages:
promise: 7.3.1
dev: true
/jwt-decode/3.1.2:
resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
dev: false
/kind-of/6.0.3:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
@@ -4864,8 +4834,6 @@ packages:
mime: 1.6.0
needle: 3.1.0
source-map: 0.6.1
transitivePeerDependencies:
- supports-color
dev: true
/leven/3.1.0:
@@ -5208,8 +5176,6 @@ packages:
debug: 3.2.7
iconv-lite: 0.6.3
sax: 1.2.4
transitivePeerDependencies:
- supports-color
dev: true
optional: true
@@ -6489,6 +6455,10 @@ packages:
engines: {node: '>=8'}
dev: true
/ts-pattern/4.1.3:
resolution: {integrity: sha512-8beXMWTGEv1JfDjSxfNhe4uT5jKYdhmEUKzt4gZW9dmHlquq3b+IbEyA7vX9LjBfzHmvKnM4HiomAUCyaW2Pew==}
dev: false
/tsconfig-paths/3.14.1:
resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==}
dependencies:
@@ -6572,6 +6542,7 @@ packages:
resolution: {integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/uc.micro/1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
@@ -6728,11 +6699,10 @@ packages:
vite: 2.9.15_less@4.1.3
dev: true
/vite-plugin-pwa/0.11.13_7mbbuzxp22mje5bxdolj2b6yg4:
/vite-plugin-pwa/0.11.13_vite@2.9.15:
resolution: {integrity: sha512-Ssj14m3TRVLfkFEAWSMcFE2d1cSdEZyrVTzfY2lSL+umHYvcIFHVDAY143sygtBCb44OPczsAOmWwBTxwOvh7g==}
peerDependencies:
vite: ^2.0.0
workbox-window: ^6.4.0
dependencies:
debug: 4.3.4
fast-glob: 3.2.12
@@ -6998,6 +6968,7 @@ packages:
'@vue/runtime-dom': 3.2.45
'@vue/server-renderer': 3.2.45_vue@3.2.45
'@vue/shared': 3.2.45
dev: false
/vueuc/0.4.49_vue@3.2.45:
resolution: {integrity: sha512-WarAC44a/Yx78CxkAgROYLq+LkAeCGA/6wHidVoFmHLbzyF3SiP2nzRNGD/8zJeJInXv18EnWK6A//eGgMMq8w==}

View File

@@ -53,6 +53,7 @@ import {
NTooltip,
NUpload,
NUploadDragger,
NPopover,
NCheckbox,
} from 'naive-ui';
@@ -111,6 +112,7 @@ const components = [
NIcon,
NSwitch,
NCollapseTransition,
NPopover,
];
export const naive = create({ components });

View File

@@ -1,12 +1,10 @@
import { tool as chmodCalculator } from './chmod-calculator';
import { tool as mimeTypes } from './mime-types';
import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator';
import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator';
import { tool as bcrypt } from './bcrypt';
import { tool as bip39 } from './bip39-generator';
import { tool as caseConverter } from './case-converter';
import { tool as chmodCalculator } from './chmod-calculator';
import { tool as chronometer } from './chronometer';
import { tool as colorConverter } from './color-converter';
import { tool as crontabGenerator } from './crontab-generator';
@@ -20,20 +18,24 @@ import { tool as hmacGenerator } from './hmac-generator';
import { tool as htmlEntities } from './html-entities';
import { tool as baseConverter } from './integer-base-converter';
import { tool as jsonViewer } from './json-viewer';
import { tool as jwtParser } from './jwt-parser';
import { tool as loremIpsumGenerator } from './lorem-ipsum-generator';
import { tool as mathEvaluator } from './math-evaluator';
import { tool as metaTagGenerator } from './meta-tag-generator';
import { tool as mimeTypes } from './mime-types';
import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator';
import { tool as qrCodeGenerator } from './qr-code-generator';
import { tool as randomPortGenerator } from './random-port-generator';
import { tool as romanNumeralConverter } from './roman-numeral-converter';
import { tool as sqlPrettify } from './sql-prettify';
import { tool as svgPlaceholderGenerator } from './svg-placeholder-generator';
import { tool as temperatureConverter } from './temperature-converter';
import { tool as textStatistics } from './text-statistics';
import { tool as tokenGenerator } from './token-generator';
import type { ToolCategory } from './tools.types';
import { tool as urlEncoder } from './url-encoder';
import { tool as urlParser } from './url-parser';
import { tool as uuidGenerator } from './uuid-generator';
import type { ToolCategory } from './tools.types';
export const toolsByCategory: ToolCategory[] = [
{
@@ -63,6 +65,7 @@ export const toolsByCategory: ToolCategory[] = [
metaTagGenerator,
otpCodeGeneratorAndValidator,
mimeTypes,
jwtParser,
],
},
{
@@ -79,7 +82,7 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Measurement',
components: [chronometer],
components: [chronometer, temperatureConverter],
},
{
name: 'Text',

View File

@@ -0,0 +1,27 @@
import { Key } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'JWT parser',
path: '/jwt-parser',
description: 'Parse and decode your JSON Web Token (jwt) and display its content.',
keywords: [
'jwt',
'parser',
'decode',
'typ',
'alg',
'iss',
'sub',
'aud',
'exp',
'nbf',
'iat',
'jti',
'json',
'web',
'token',
],
component: () => import('./jwt-parser.vue'),
icon: Key,
});

View File

@@ -0,0 +1,92 @@
// From https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
export const ALGORITHM_DESCRIPTIONS: { [k: string]: string } = {
HS256: 'HMAC using SHA-256',
HS384: 'HMAC using SHA-384',
HS512: 'HMAC using SHA-512',
RS256: 'RSASSA-PKCS1-v1_5 using SHA-256',
RS384: 'RSASSA-PKCS1-v1_5 using SHA-384',
RS512: 'RSASSA-PKCS1-v1_5 using SHA-512',
ES256: 'ECDSA using P-256 and SHA-256',
ES384: 'ECDSA using P-384 and SHA-384',
ES512: 'ECDSA using P-521 and SHA-512',
PS256: 'RSASSA-PSS using SHA-256 and MGF1 with SHA-256',
PS384: 'RSASSA-PSS using SHA-384 and MGF1 with SHA-384',
PS512: 'RSASSA-PSS using SHA-512 and MGF1 with SHA-512',
none: 'No digital signature or MAC performed',
};
// List extracted from IANA: https://www.iana.org/assignments/jwt/jwt.xhtml
export const CLAIM_DESCRIPTIONS: Record<string, string> = {
typ: 'Type',
alg: 'Algorithm',
iss: 'Issuer',
sub: 'Subject',
aud: 'Audience',
exp: 'Expiration Time',
nbf: 'Not Before',
iat: 'Issued At',
jti: 'JWT ID',
name: 'Full name',
given_name: 'Given name(s) or first name(s)',
family_name: 'Surname(s) or last name(s)',
middle_name: 'Middle name(s)',
nickname: 'Casual name',
preferred_username: 'Shorthand name by which the End-User wishes to be referred to',
profile: 'Profile page URL',
picture: 'Profile picture URL',
website: 'Web page or blog URL',
email: 'Preferred e-mail address',
email_verified: 'True if the e-mail address has been verified; otherwise false',
gender: 'Gender',
birthdate: 'Birthday',
zoneinfo: 'Time zone',
locale: 'Locale',
phone_number: 'Preferred telephone number',
phone_number_verified: 'True if the phone number has been verified; otherwise false',
address: 'Preferred postal address',
updated_at: 'Time the information was last updated',
azp: 'Authorized party - the party to which the ID Token was issued',
nonce: 'Value used to associate a Client session with an ID Token',
auth_time: 'Time when the authentication occurred',
at_hash: 'Access Token hash value',
c_hash: 'Code hash value',
acr: 'Authentication Context Class Reference',
amr: 'Authentication Methods References',
sub_jwk: 'Public key used to check the signature of an ID Token',
cnf: 'Confirmation',
sip_from_tag: 'SIP From tag header field parameter value',
sip_date: 'SIP Date header field value',
sip_callid: 'SIP Call-Id header field value',
sip_cseq_num: 'SIP CSeq numeric header field parameter value',
sip_via_branch: 'SIP Via branch header field parameter value',
orig: 'Originating Identity String',
dest: 'Destination Identity String',
mky: 'Media Key Fingerprint String',
events: 'Security Events',
toe: 'Time of Event',
txn: 'Transaction Identifier',
rph: 'Resource Priority Header Authorization',
sid: 'Session ID',
vot: 'Vector of Trust value',
vtm: 'Vector of Trust trustmark URL',
attest: 'Attestation level as defined in SHAKEN framework',
origid: 'Originating Identifier as defined in SHAKEN framework',
act: 'Actor',
scope: 'Scope Values',
client_id: 'Client Identifier',
may_act: 'Authorized Actor - the party that is authorized to become the actor',
jcard: 'jCard data',
at_use_nbr: 'Number of API requests for which the access token can be used',
div: 'Diverted Target of a Call',
opt: 'Original PASSporT (in Full Form)',
vc: 'Verifiable Credential as specified in the W3C Recommendation',
vp: 'Verifiable Presentation as specified in the W3C Recommendation',
sph: 'SIP Priority header field',
ace_profile: 'ACE profile a token is supposed to be used with.',
cnonce: 'Client nonce',
exi: 'Expires in',
roles: 'Roles',
groups: 'Groups',
entitlements: 'Entitlements',
token_introspection: 'Token introspection response',
};

View File

@@ -0,0 +1,46 @@
import jwtDecode, { type JwtHeader, type JwtPayload } from 'jwt-decode';
import _ from 'lodash';
import { match } from 'ts-pattern';
import { ALGORITHM_DESCRIPTIONS, CLAIM_DESCRIPTIONS } from './jwt-parser.constants';
export { decodeJwt };
function decodeJwt({ jwt }: { jwt: string }) {
const rawHeader = jwtDecode<JwtHeader>(jwt, { header: true });
const rawPayload = jwtDecode<JwtPayload>(jwt);
const header = _.map(rawHeader, (value, claim) => parseClaims({ claim, value }));
const payload = _.map(rawPayload, (value, claim) => parseClaims({ claim, value }));
return {
header,
payload,
};
}
function parseClaims({ claim, value }: { claim: string; value: unknown }) {
const claimDescription = CLAIM_DESCRIPTIONS[claim];
const formattedValue = _.toString(value);
const friendlyValue = getFriendlyValue({ claim, value });
return {
value: formattedValue,
friendlyValue,
claim,
claimDescription,
};
}
function getFriendlyValue({ claim, value }: { claim: string; value: unknown }) {
return match(claim)
.with('exp', 'nbf', 'iat', () => dateFormatter(value))
.with('alg', () => (_.isString(value) ? ALGORITHM_DESCRIPTIONS[value] : undefined))
.otherwise(() => undefined);
}
const dateFormatter = (value: unknown) => {
if (_.isNil(value)) return undefined;
const date = new Date(Number(value) * 1000);
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
};

View File

@@ -0,0 +1,70 @@
<template>
<n-card>
<n-form-item label="JWT to decode" :feedback="validation.message" :validation-status="validation.status">
<n-input v-model:value="rawJwt" type="textarea" placeholder="Put your token here..." rows="5" />
</n-form-item>
<n-table v-if="validation.isValid">
<tbody>
<template v-for="section of sections" :key="section.key">
<th colspan="2" class="table-header">{{ section.title }}</th>
<tr v-for="{ claim, claimDescription, friendlyValue, value } in decodedJWT[section.key]" :key="claim + value">
<td class="claims">
<n-space>
<n-text strong>{{ claim }}</n-text>
<template v-if="claimDescription">
<n-text depth="3">({{ claimDescription }})</n-text>
</template>
</n-space>
</td>
<td>
<n-space>
<n-text>{{ value }}</n-text>
<template v-if="friendlyValue">
<n-text depth="3">({{ friendlyValue }})</n-text>
</template>
</n-space>
</td>
</tr>
</template>
</tbody>
</n-table>
</n-card>
</template>
<script setup lang="ts">
import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import { withDefaultOnError } from '@/utils/defaults';
import { computed, ref } from 'vue';
import { decodeJwt } from './jwt-parser.service';
const rawJwt = ref(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
);
const decodedJWT = computed(() =>
withDefaultOnError(() => decodeJwt({ jwt: rawJwt.value }), { header: [], payload: [] }),
);
const sections = [
{ key: 'header', title: 'Header' },
{ key: 'payload', title: 'Payload' },
] as const;
const validation = useValidation({
source: rawJwt,
rules: [
{
validator: (value) => value.length > 0 && isNotThrowing(() => decodeJwt({ jwt: rawJwt.value })),
message: 'Invalid JWT',
},
],
});
</script>
<style lang="less" scoped>
.table-header {
text-align: center;
}
</style>

View File

@@ -0,0 +1,24 @@
import { Temperature } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Temperature converter',
path: '/temperature-converter',
description:
'Temperature degrees conversions for Kelvin, Celsius, Fahrenheit, Rankine, Delisle, Newton, Réaumur and Rømer.',
keywords: [
'temperature',
'converter',
'degree',
'Kelvin',
'Celsius',
'Fahrenheit',
'Rankine',
'Delisle',
'Newton',
'Réaumur',
'Rømer',
],
component: () => import('./temperature-converter.vue'),
icon: Temperature,
});

View File

@@ -0,0 +1,20 @@
export const convertCelsiusToKelvin = (temperature: number) => temperature + 273.15;
export const convertKelvinToCelsius = (temperature: number) => temperature - 273.15;
export const convertFahrenheitToKelvin = (temperature: number) => (temperature + 459.67) * (5 / 9);
export const convertKelvinToFahrenheit = (temperature: number) => temperature * (9 / 5) - 459.67;
export const convertRankineToKelvin = (temperature: number) => temperature * (5 / 9);
export const convertKelvinToRankine = (temperature: number) => temperature * (9 / 5);
export const convertDelisleToKelvin = (temperature: number) => 373.15 - (2 / 3) * temperature;
export const convertKelvinToDelisle = (temperature: number) => (3 / 2) * (373.15 - temperature);
export const convertNewtonToKelvin = (temperature: number) => temperature * (100 / 33) + 273.15;
export const convertKelvinToNewton = (temperature: number) => (temperature - 273.15) * (33 / 100);
export const convertReaumurToKelvin = (temperature: number) => temperature * (5 / 4) + 273.15;
export const convertKelvinToReaumur = (temperature: number) => (temperature - 273.15) * (4 / 5);
export const convertRomerToKelvin = (temperature: number) => (temperature - 7.5) * (40 / 21) + 273.15;
export const convertKelvinToRomer = (temperature: number) => (temperature - 273.15) * (21 / 40) + 7.5;

View File

@@ -0,0 +1,127 @@
<template>
<div>
<n-input-group
v-for="[key, { title, unit }] in Object.entries(units)"
:key="key"
style="width: 100%; margin-bottom: 15px"
>
<n-input-group-label style="width: 100px">
{{ title }}
</n-input-group-label>
<n-input-number
v-model:value="units[key].ref"
style="flex: 1"
@update:value="() => update(key as TemperatureScale)"
/>
<n-input-group-label style="width: 50px">
{{ unit }}
</n-input-group-label>
</n-input-group>
</div>
</template>
<script setup lang="ts">
import _ from 'lodash';
import { reactive } from 'vue';
import {
convertCelsiusToKelvin,
convertDelisleToKelvin,
convertFahrenheitToKelvin,
convertKelvinToCelsius,
convertKelvinToDelisle,
convertKelvinToFahrenheit,
convertKelvinToNewton,
convertKelvinToRankine,
convertKelvinToReaumur,
convertKelvinToRomer,
convertNewtonToKelvin,
convertRankineToKelvin,
convertReaumurToKelvin,
convertRomerToKelvin,
} from './temperature-converter.models';
type TemperatureScale = 'kelvin' | 'celsius' | 'fahrenheit' | 'rankine' | 'delisle' | 'newton' | 'reaumur' | 'romer';
const units = reactive<
Record<
string | TemperatureScale,
{ title: string; unit: string; ref: number; toKelvin: (v: number) => number; fromKelvin: (v: number) => number }
>
>({
kelvin: {
title: 'Kelvin',
unit: 'K',
ref: 0,
toKelvin: _.identity,
fromKelvin: _.identity,
},
celsius: {
title: 'Celsius',
unit: '°C',
ref: 0,
toKelvin: convertCelsiusToKelvin,
fromKelvin: convertKelvinToCelsius,
},
fahrenheit: {
title: 'Fahrenheit',
unit: '°F',
ref: 0,
toKelvin: convertFahrenheitToKelvin,
fromKelvin: convertKelvinToFahrenheit,
},
rankine: {
title: 'Rankine',
unit: '°R',
ref: 0,
toKelvin: convertRankineToKelvin,
fromKelvin: convertKelvinToRankine,
},
delisle: {
title: 'Delisle',
unit: '°De',
ref: 0,
toKelvin: convertDelisleToKelvin,
fromKelvin: convertKelvinToDelisle,
},
newton: {
title: 'Newton',
unit: '°N',
ref: 0,
toKelvin: convertNewtonToKelvin,
fromKelvin: convertKelvinToNewton,
},
reaumur: {
title: 'Réaumur',
unit: '°Ré',
ref: 0,
toKelvin: convertReaumurToKelvin,
fromKelvin: convertKelvinToReaumur,
},
romer: {
title: 'Rømer',
unit: '°Rø',
ref: 0,
toKelvin: convertRomerToKelvin,
fromKelvin: convertKelvinToRomer,
},
});
function update(key: TemperatureScale) {
const { ref: value, toKelvin } = units[key];
const kelvins = toKelvin(value) ?? 0;
_.chain(units)
.omit(key)
.forEach(({ fromKelvin }, index) => {
units[index].ref = Math.floor((fromKelvin(kelvins) ?? 0) * 100) / 100;
})
.value();
}
update('kelvin');
</script>
<style lang="less" scoped></style>