(feat): update createTTP function

This commit is contained in:
Alois 2026-04-14 21:55:17 +02:00
commit 80dc94ba55
6 changed files with 948 additions and 36 deletions

2
dist/index.d.ts vendored
View file

@ -71,7 +71,7 @@ export declare class TAuthClient {
}); });
generateChallenge(userId: number): Promise<string>; generateChallenge(userId: number): Promise<string>;
solveChallenge(userId: number, challenge: string): Promise<string>; solveChallenge(userId: number, challenge: string): Promise<string>;
createTTP(userId: number, sessionId: number, omikronUrl: string): void; createTTP(userId: number, sessionId: number, omikronUrl: string): Promise<TransportClient<typeof this.schema>>;
loadData(userId: number, sessionId: number): Promise<Record<string, unknown>>; loadData(userId: number, sessionId: number): Promise<Record<string, unknown>>;
saveData(userId: number, sessionId: number, data: any): Promise<void>; saveData(userId: number, sessionId: number, data: any): Promise<void>;
private createTemporaryTTPConnection; private createTemporaryTTPConnection;

459
dist/index.html vendored Normal file
View file

@ -0,0 +1,459 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content="Astro v6.1.6" />
<title>Login successful</title>
<script>
const media = window.matchMedia("(prefers-color-scheme: dark)");
const applyTheme = (isDark) => {
document.documentElement.classList.toggle("dark", isDark);
};
applyTheme(media.matches);
media.addEventListener("change", (event) => applyTheme(event.matches));
</script>
<style>
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or
((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*,
:before,
:after,
::backdrop {
--tw-font-weight: initial;
}
}
}
@layer theme {
:root,
:host {
--font-sans:
ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
--spacing: 0.25rem;
--text-xl: 1.25rem;
--text-xl--line-height: calc(1.75 / 1.25);
--text-4xl: 2.25rem;
--text-4xl--line-height: calc(2.5 / 2.25);
--font-weight-semibold: 600;
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
}
}
@layer base {
*,
:after,
:before,
::backdrop {
box-sizing: border-box;
border: 0 solid;
margin: 0;
padding: 0;
}
::file-selector-button {
box-sizing: border-box;
border: 0 solid;
margin: 0;
padding: 0;
}
html,
:host {
-webkit-text-size-adjust: 100%;
tab-size: 4;
line-height: 1.5;
font-family: var(
--default-font-family,
ui-sans-serif,
system-ui,
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji"
);
font-feature-settings: var(--default-font-feature-settings, normal);
font-variation-settings: var(
--default-font-variation-settings,
normal
);
-webkit-tap-highlight-color: transparent;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b,
strong {
font-weight: bolder;
}
code,
kbd,
samp,
pre {
font-family: var(
--default-mono-font-family,
ui-monospace,
SFMono-Regular,
Menlo,
Monaco,
Consolas,
"Liberation Mono",
"Courier New",
monospace
);
font-feature-settings: var(
--default-mono-font-feature-settings,
normal
);
font-variation-settings: var(
--default-mono-font-variation-settings,
normal
);
font-size: 1em;
}
small {
font-size: 80%;
}
sub,
sup {
vertical-align: baseline;
font-size: 75%;
line-height: 0;
position: relative;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol,
ul,
menu {
list-style: none;
}
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
vertical-align: middle;
display: block;
}
img,
video {
max-width: 100%;
height: auto;
}
button,
input,
select,
optgroup,
textarea {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
opacity: 1;
background-color: #0000;
border-radius: 0;
}
::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
opacity: 1;
background-color: #0000;
border-radius: 0;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
}
@supports (not ((-webkit-appearance: -apple-pay-button))) or
(contain-intrinsic-size: 1px) {
::placeholder {
color: currentColor;
}
@supports (color: color-mix(in lab, red, red)) {
::placeholder {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit {
padding-block: 0;
}
::-webkit-datetime-edit-year-field {
padding-block: 0;
}
::-webkit-datetime-edit-month-field {
padding-block: 0;
}
::-webkit-datetime-edit-day-field {
padding-block: 0;
}
::-webkit-datetime-edit-hour-field {
padding-block: 0;
}
::-webkit-datetime-edit-minute-field {
padding-block: 0;
}
::-webkit-datetime-edit-second-field {
padding-block: 0;
}
::-webkit-datetime-edit-millisecond-field {
padding-block: 0;
}
::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
::-webkit-calendar-picker-indicator {
line-height: 1;
}
:-moz-ui-invalid {
box-shadow: none;
}
button,
input:where([type="button"], [type="reset"], [type="submit"]) {
appearance: button;
}
::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button {
height: auto;
}
::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden="until-found"])) {
display: none !important;
}
}
@layer components;
@layer utilities {
.static {
position: static;
}
.flex {
display: flex;
}
.size-80 {
width: calc(var(--spacing) * 80);
height: calc(var(--spacing) * 80);
}
.h-screen {
height: 100vh;
}
.min-h-screen {
min-height: 100vh;
}
.w-screen {
width: 100vw;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.gap-12 {
gap: calc(var(--spacing) * 12);
}
.gap-30 {
gap: calc(var(--spacing) * 30);
}
.bg-background {
background-color: var(--background);
}
.text-4xl {
font-size: var(--text-4xl);
line-height: var(--tw-leading, var(--text-4xl--line-height));
}
.text-xl {
font-size: var(--text-xl);
line-height: var(--tw-leading, var(--text-xl--line-height));
}
.font-semibold {
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
}
.text-foreground {
color: var(--foreground);
}
.text-muted-foreground {
color: var(--muted-foreground);
}
.text-primary {
color: var(--primary);
}
}
:root {
--background: oklch(100% 0 0);
--foreground: oklch(14.5% 0 0);
--card: oklch(100% 0 0);
--card-foreground: oklch(14.5% 0 0);
--popover: oklch(100% 0 0);
--popover-foreground: oklch(14.5% 0 0);
--primary: oklch(51.1% 0.096 186.391);
--primary-foreground: oklch(98.4% 0.014 180.72);
--secondary: oklch(96.7% 0.001 286.375);
--secondary-foreground: oklch(21% 0.006 285.885);
--muted: oklch(97% 0 0);
--muted-foreground: oklch(55.6% 0 0);
--accent: oklch(97% 0 0);
--accent-foreground: oklch(20.5% 0 0);
--destructive: oklch(57.7% 0.245 27.325);
--border: oklch(92.2% 0 0);
--input: oklch(92.2% 0 0);
--ring: oklch(70.8% 0 0);
--chart-1: oklch(85.5% 0.138 181.071);
--chart-2: oklch(70.4% 0.14 182.503);
--chart-3: oklch(60% 0.118 184.704);
--chart-4: oklch(51.1% 0.096 186.391);
--chart-5: oklch(43.7% 0.078 188.216);
--radius: 0.625rem;
--sidebar: oklch(98.5% 0 0);
--sidebar-foreground: oklch(14.5% 0 0);
--sidebar-primary: oklch(60% 0.118 184.704);
--sidebar-primary-foreground: oklch(98.4% 0.014 180.72);
--sidebar-accent: oklch(97% 0 0);
--sidebar-accent-foreground: oklch(20.5% 0 0);
--sidebar-border: oklch(92.2% 0 0);
--sidebar-ring: oklch(70.8% 0 0);
}
.dark {
--background: oklch(14.5% 0 0);
--foreground: oklch(98.5% 0 0);
--card: oklch(22.5% 0 0);
--card-foreground: oklch(98.5% 0 0);
--popover: oklch(20.5% 0 0);
--popover-foreground: oklch(98.5% 0 0);
--primary: oklch(43.7% 0.078 188.216);
--primary-foreground: oklch(98.4% 0.014 180.72);
--secondary: oklch(27.4% 0.006 286.033);
--secondary-foreground: oklch(98.5% 0 0);
--muted: oklch(26.9% 0 0);
--muted-foreground: oklch(70.8% 0 0);
--accent: oklch(26.9% 0 0);
--accent-foreground: oklch(98.5% 0 0);
--destructive: oklch(70.4% 0.191 22.216);
--border: oklch(100% 0 0/0.1);
--input: oklch(100% 0 0/0.15);
--ring: oklch(55.6% 0 0);
--chart-1: oklch(85.5% 0.138 181.071);
--chart-2: oklch(70.4% 0.14 182.503);
--chart-3: oklch(60% 0.118 184.704);
--chart-4: oklch(51.1% 0.096 186.391);
--chart-5: oklch(43.7% 0.078 188.216);
--sidebar: oklch(19% 0 0);
--sidebar-foreground: oklch(98.5% 0 0);
--sidebar-primary: oklch(70.4% 0.14 182.503);
--sidebar-primary-foreground: oklch(27.7% 0.046 192.524);
--sidebar-accent: oklch(26.9% 0 0);
--sidebar-accent-foreground: oklch(98.5% 0 0);
--sidebar-border: oklch(100% 0 0/0.1);
--sidebar-ring: oklch(55.6% 0 0);
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
</style>
</head>
<body
class="min-h-screen bg-background text-foreground w-screen h-screen flex items-center justify-center flex-col gap-30"
>
<div class="flex flex-col gap-12 items-center justify-center">
<h1 class="text-4xl font-semibold">Login successful</h1>
<p class="text-xl text-muted-foreground">You can close this page now.</p>
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
class="lucide lucide-check size-80 text-primary"
>
<path d="M20 6 9 17l-5-5"></path>
</svg>
</body>
</html>

15
dist/index.js vendored
View file

@ -76,7 +76,8 @@ export class TAuthClient {
if ((await this.solveChallenge(userId, originalChallenge)) === if ((await this.solveChallenge(userId, originalChallenge)) ===
potentialChallenge) { potentialChallenge) {
await this.saveSession(userId, potentialSessionId); await this.saveSession(userId, potentialSessionId);
return new Response(htmlSuccessPage || (await readFile("./index.html")), { return new Response(htmlSuccessPage ||
(await readFile(new URL("./index.html", import.meta.url), "utf8")), {
headers: { headers: {
"Content-Type": "text/html", "Content-Type": "text/html",
}, },
@ -126,15 +127,13 @@ export class TAuthClient {
} }
createTTP(userId, sessionId, omikronUrl) { createTTP(userId, sessionId, omikronUrl) {
console.log(`[tauth] Creating TTP client for user ${userId} with session ${sessionId} at ${omikronUrl}`); console.log(`[tauth] Creating TTP client for user ${userId} with session ${sessionId} at ${omikronUrl}`);
this.clientMap.set(`${userId}:${sessionId}`, createTransportClient(this.schema, { const client = createTransportClient(this.schema, {
url: omikronUrl, url: omikronUrl,
onReadyStateChange: (stateIndex) => { onReadyStateChange: (stateIndex) => {
const state = getFriendlyReadyState(stateIndex); const state = getFriendlyReadyState(stateIndex);
console.log(state); console.log(state);
if (state === "OPEN") { if (state === "OPEN") {
this.clientMap this.clientMap.get(`${userId}:${sessionId}`)?.send("identification", {
.get(`${userId}:${sessionId}`)
?.send("identification", {
app_identifier: this.identifier, app_identifier: this.identifier,
app_session_id: sessionId, app_session_id: sessionId,
app_public_key: this.publicKey, app_public_key: this.publicKey,
@ -142,8 +141,10 @@ export class TAuthClient {
}); });
} }
}, },
})); });
this.clientMap.get(`${userId}:${sessionId}`)?.connect(); this.clientMap.set(`${userId}:${sessionId}`, client);
client.connect();
return Promise.resolve(client);
} }
async loadData(userId, sessionId) { async loadData(userId, sessionId) {
const connection = await this.createTemporaryTTPConnection(userId, sessionId); const connection = await this.createTemporaryTTPConnection(userId, sessionId);

View file

@ -1,6 +1,6 @@
{ {
"name": "@tensamin/tauth-sdk", "name": "@tensamin/tauth-sdk",
"version": "0.0.3", "version": "0.0.4",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@ -15,7 +15,7 @@
} }
}, },
"scripts": { "scripts": {
"build": "bun run test && tsc -p tsconfig.build.json", "build": "bun run test && tsc -p tsconfig.build.json && cp src/index.html dist/index.html",
"test": "bun test" "test": "bun test"
}, },
"devDependencies": { "devDependencies": {

File diff suppressed because one or more lines are too long

View file

@ -120,7 +120,8 @@ export class TAuthClient {
await this.saveSession(userId, potentialSessionId); await this.saveSession(userId, potentialSessionId);
return new Response( return new Response(
htmlSuccessPage || (await readFile("./index.html")), htmlSuccessPage ||
(await readFile(new URL("./index.html", import.meta.url), "utf8")),
{ {
headers: { headers: {
"Content-Type": "text/html", "Content-Type": "text/html",
@ -192,22 +193,22 @@ export class TAuthClient {
return await decrypt(sharedSecret, challenge); return await decrypt(sharedSecret, challenge);
} }
createTTP(userId: number, sessionId: number, omikronUrl: string) { createTTP(
userId: number,
sessionId: number,
omikronUrl: string,
): Promise<TransportClient<typeof this.schema>> {
console.log( console.log(
`[tauth] Creating TTP client for user ${userId} with session ${sessionId} at ${omikronUrl}`, `[tauth] Creating TTP client for user ${userId} with session ${sessionId} at ${omikronUrl}`,
); );
this.clientMap.set( const client = createTransportClient(this.schema, {
`${userId}:${sessionId}`,
createTransportClient(this.schema, {
url: omikronUrl, url: omikronUrl,
onReadyStateChange: (stateIndex) => { onReadyStateChange: (stateIndex) => {
const state = getFriendlyReadyState(stateIndex); const state = getFriendlyReadyState(stateIndex);
console.log(state); console.log(state);
if (state === "OPEN") { if (state === "OPEN") {
this.clientMap this.clientMap.get(`${userId}:${sessionId}`)?.send("identification", {
.get(`${userId}:${sessionId}`)
?.send("identification", {
app_identifier: this.identifier, app_identifier: this.identifier,
app_session_id: sessionId, app_session_id: sessionId,
app_public_key: this.publicKey, app_public_key: this.publicKey,
@ -215,10 +216,12 @@ export class TAuthClient {
}); });
} }
}, },
}), });
);
this.clientMap.get(`${userId}:${sessionId}`)?.connect(); this.clientMap.set(`${userId}:${sessionId}`, client);
client.connect();
return Promise.resolve(client);
} }
async loadData(userId: number, sessionId: number) { async loadData(userId: number, sessionId: number) {