@atlasauth/js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/attempt.d.ts +71 -0
  2. package/dist/attempt.js +89 -0
  3. package/dist/attempt.js.map +1 -0
  4. package/dist/check-session.d.ts +56 -0
  5. package/dist/check-session.js +106 -0
  6. package/dist/check-session.js.map +1 -0
  7. package/dist/connect.d.ts +50 -0
  8. package/dist/connect.js +72 -0
  9. package/dist/connect.js.map +1 -0
  10. package/dist/fapi.d.ts +123 -0
  11. package/dist/fapi.js +314 -0
  12. package/dist/fapi.js.map +1 -0
  13. package/dist/index.d.ts +15 -0
  14. package/dist/index.js +43 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/native.d.ts +112 -0
  17. package/dist/native.js +201 -0
  18. package/dist/native.js.map +1 -0
  19. package/dist/passkey.d.ts +70 -0
  20. package/dist/passkey.js +126 -0
  21. package/dist/passkey.js.map +1 -0
  22. package/dist/password-reset.d.ts +41 -0
  23. package/dist/password-reset.js +80 -0
  24. package/dist/password-reset.js.map +1 -0
  25. package/dist/reauth.d.ts +29 -0
  26. package/dist/reauth.js +45 -0
  27. package/dist/reauth.js.map +1 -0
  28. package/dist/redirect.d.ts +38 -0
  29. package/dist/redirect.js +56 -0
  30. package/dist/redirect.js.map +1 -0
  31. package/dist/siwe.d.ts +23 -0
  32. package/dist/siwe.js +28 -0
  33. package/dist/siwe.js.map +1 -0
  34. package/dist/tab-election.d.ts +68 -0
  35. package/dist/tab-election.js +111 -0
  36. package/dist/tab-election.js.map +1 -0
  37. package/dist/telegram.d.ts +25 -0
  38. package/dist/telegram.js +18 -0
  39. package/dist/telegram.js.map +1 -0
  40. package/dist/telemetry.d.ts +125 -0
  41. package/dist/telemetry.js +357 -0
  42. package/dist/telemetry.js.map +1 -0
  43. package/dist/token-cache.d.ts +74 -0
  44. package/dist/token-cache.js +104 -0
  45. package/dist/token-cache.js.map +1 -0
  46. package/package.json +26 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * §6.3 step 3 / §7.1: completing a redirect-based sign-in.
3
+ *
4
+ * OAuth and hosted-page sign-ins finish by redirecting the browser back to the
5
+ * customer's app with params appended to the URL:
6
+ * - `__atlas_attempt` — the sign-in attempt id (always present)
7
+ * - `__atlas_ticket` — a one-time, 60-second ticket to exchange for cookies
8
+ * - `__atlas_status` — the attempt status, present when a step REMAINS
9
+ *
10
+ * A ticket means the sign-in is complete and may be turned into a session. A
11
+ * status WITHOUT a ticket (e.g. `needs_second_factor`) means the server
12
+ * deliberately withheld the session because a factor is still owed — the client
13
+ * must not treat that as signed-in. Parsing this is pure and lives here so both
14
+ * the React provider and any custom integration read the exact same contract.
15
+ */
16
+ export declare const REDIRECT_PARAMS: readonly ["__atlas_attempt", "__atlas_ticket", "__atlas_status"];
17
+ export interface RedirectResult {
18
+ attemptId: string;
19
+ /** Present only when the sign-in completed — exchange it for cookies. */
20
+ ticket?: string;
21
+ /** Present when a step remains (e.g. `needs_second_factor`); no session yet. */
22
+ status?: string;
23
+ }
24
+ /**
25
+ * Read Atlas's redirect params from a query string (`window.location.search`).
26
+ *
27
+ * Returns null when this is not an Atlas redirect: an `__atlas_attempt` on its
28
+ * own — with neither a ticket nor a status — is not actionable and is treated
29
+ * as absent, so a stray param cannot put the SDK into a half-state.
30
+ */
31
+ export declare function readRedirectResult(search: string): RedirectResult | null;
32
+ /**
33
+ * Return `href` with every Atlas redirect param removed, leaving the customer's
34
+ * own query intact. Called after handling a redirect so a reload — or a link
35
+ * copied from the address bar — cannot replay a spent ticket or resurrect a
36
+ * stale status.
37
+ */
38
+ export declare function stripRedirectParams(href: string): string;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * §6.3 step 3 / §7.1: completing a redirect-based sign-in.
4
+ *
5
+ * OAuth and hosted-page sign-ins finish by redirecting the browser back to the
6
+ * customer's app with params appended to the URL:
7
+ * - `__atlas_attempt` — the sign-in attempt id (always present)
8
+ * - `__atlas_ticket` — a one-time, 60-second ticket to exchange for cookies
9
+ * - `__atlas_status` — the attempt status, present when a step REMAINS
10
+ *
11
+ * A ticket means the sign-in is complete and may be turned into a session. A
12
+ * status WITHOUT a ticket (e.g. `needs_second_factor`) means the server
13
+ * deliberately withheld the session because a factor is still owed — the client
14
+ * must not treat that as signed-in. Parsing this is pure and lives here so both
15
+ * the React provider and any custom integration read the exact same contract.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.REDIRECT_PARAMS = void 0;
19
+ exports.readRedirectResult = readRedirectResult;
20
+ exports.stripRedirectParams = stripRedirectParams;
21
+ exports.REDIRECT_PARAMS = ['__atlas_attempt', '__atlas_ticket', '__atlas_status'];
22
+ /**
23
+ * Read Atlas's redirect params from a query string (`window.location.search`).
24
+ *
25
+ * Returns null when this is not an Atlas redirect: an `__atlas_attempt` on its
26
+ * own — with neither a ticket nor a status — is not actionable and is treated
27
+ * as absent, so a stray param cannot put the SDK into a half-state.
28
+ */
29
+ function readRedirectResult(search) {
30
+ const params = new URLSearchParams(search);
31
+ const attemptId = params.get('__atlas_attempt');
32
+ if (!attemptId)
33
+ return null;
34
+ const ticket = params.get('__atlas_ticket');
35
+ const status = params.get('__atlas_status');
36
+ if (!ticket && !status)
37
+ return null;
38
+ return {
39
+ attemptId,
40
+ ...(ticket ? { ticket } : {}),
41
+ ...(status ? { status } : {}),
42
+ };
43
+ }
44
+ /**
45
+ * Return `href` with every Atlas redirect param removed, leaving the customer's
46
+ * own query intact. Called after handling a redirect so a reload — or a link
47
+ * copied from the address bar — cannot replay a spent ticket or resurrect a
48
+ * stale status.
49
+ */
50
+ function stripRedirectParams(href) {
51
+ const url = new URL(href);
52
+ for (const key of exports.REDIRECT_PARAMS)
53
+ url.searchParams.delete(key);
54
+ return url.toString();
55
+ }
56
+ //# sourceMappingURL=redirect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect.js","sourceRoot":"","sources":["../src/redirect.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAmBH,gDAcC;AAQD,kDAIC;AA3CY,QAAA,eAAe,GAAG,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,CAAU,CAAC;AAUhG;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,MAAc;IAC/C,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAChD,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEpC,OAAO;QACL,SAAS;QACT,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,MAAM,GAAG,IAAI,uBAAe;QAAE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
package/dist/siwe.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * §6 Sign-in with Ethereum (SIWE, EIP-4361) — the client half.
3
+ *
4
+ * Two steps, both pure shapers (the app owns fetch):
5
+ * 1. POST `/v1/oauth/siwe/nonce` (no body) and read the nonce with
6
+ * `parseSiweNonceResponse`.
7
+ * 2. Have the wallet sign an EIP-4361 message carrying that nonce, then POST
8
+ * `siweVerifyBody({ message, signature })` to `/v1/oauth/siwe/verify` and
9
+ * read the result with the shared `parseNativeSignInResponse`.
10
+ */
11
+ export interface SiweVerifyBody {
12
+ message: string;
13
+ signature: string;
14
+ }
15
+ /** Build the POST body for `/v1/oauth/siwe/verify`. */
16
+ export declare function siweVerifyBody(input: {
17
+ message: string;
18
+ signature: string;
19
+ }): SiweVerifyBody;
20
+ /** The single-use nonce returned by `/v1/oauth/siwe/nonce`, or null if malformed. */
21
+ export declare function parseSiweNonceResponse(body: unknown): {
22
+ nonce: string;
23
+ } | null;
package/dist/siwe.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * §6 Sign-in with Ethereum (SIWE, EIP-4361) — the client half.
4
+ *
5
+ * Two steps, both pure shapers (the app owns fetch):
6
+ * 1. POST `/v1/oauth/siwe/nonce` (no body) and read the nonce with
7
+ * `parseSiweNonceResponse`.
8
+ * 2. Have the wallet sign an EIP-4361 message carrying that nonce, then POST
9
+ * `siweVerifyBody({ message, signature })` to `/v1/oauth/siwe/verify` and
10
+ * read the result with the shared `parseNativeSignInResponse`.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.siweVerifyBody = siweVerifyBody;
14
+ exports.parseSiweNonceResponse = parseSiweNonceResponse;
15
+ /** Build the POST body for `/v1/oauth/siwe/verify`. */
16
+ function siweVerifyBody(input) {
17
+ return { message: input.message, signature: input.signature };
18
+ }
19
+ /** The single-use nonce returned by `/v1/oauth/siwe/nonce`, or null if malformed. */
20
+ function parseSiweNonceResponse(body) {
21
+ if (!body || typeof body !== 'object')
22
+ return null;
23
+ const b = body;
24
+ if (typeof b.nonce !== 'string' || b.nonce.length === 0)
25
+ return null;
26
+ return { nonce: b.nonce };
27
+ }
28
+ //# sourceMappingURL=siwe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siwe.js","sourceRoot":"","sources":["../src/siwe.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAQH,wCAEC;AAGD,wDAKC;AAXD,uDAAuD;AACvD,SAAgB,cAAc,CAAC,KAA6C;IAC1E,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;AAChE,CAAC;AAED,qFAAqF;AACrF,SAAgB,sBAAsB,CAAC,IAAa;IAClD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,CAAC,GAAG,IAA2B,CAAC;IACtC,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * §7.4: "a BroadcastChannel elects one refreshing tab."
3
+ *
4
+ * Twenty open tabs must produce ONE refresh, not twenty. That is not only a
5
+ * load concern: refresh tokens rotate (§7.4 step 2), so twenty simultaneous
6
+ * refreshes send the same token twenty times, nineteen of them land outside the
7
+ * grace window, and the session is revoked for token reuse. Getting this wrong
8
+ * does not degrade the experience — it signs the user out.
9
+ *
10
+ * The election is deliberately simple. A tab that wants to refresh announces a
11
+ * claim carrying a random id and a timestamp; the lowest id within the claim
12
+ * window wins. No consensus protocol, because the cost of getting it wrong is
13
+ * bounded: two tabs refreshing within the 30-second grace window is exactly the
14
+ * race the grace window exists to absorb.
15
+ *
16
+ * BroadcastChannel is missing in Safari private mode and older browsers, so
17
+ * there is a localStorage fallback. A tab with NEITHER refreshes alone, which
18
+ * is correct — one tab cannot stampede.
19
+ */
20
+ /** How long a claim stands before it is considered abandoned. */
21
+ export declare const CLAIM_WINDOW_MS = 3000;
22
+ export interface Claim {
23
+ id: string;
24
+ at: number;
25
+ }
26
+ /**
27
+ * Whether `mine` wins against the claims seen so far.
28
+ *
29
+ * Lowest id wins, and only claims inside the window count — otherwise a tab
30
+ * that crashed mid-refresh would block every other tab forever.
31
+ */
32
+ export declare function winsElection(mine: Claim, others: readonly Claim[], now: number): boolean;
33
+ export interface Channel {
34
+ post(message: unknown): void;
35
+ subscribe(handler: (message: unknown) => void): () => void;
36
+ close(): void;
37
+ }
38
+ /** BroadcastChannel where available. */
39
+ export declare function broadcastChannel(name: string): Channel | null;
40
+ /**
41
+ * localStorage fallback.
42
+ *
43
+ * The `storage` event fires only in OTHER tabs, which is exactly the semantics
44
+ * needed — a tab does not need to hear its own message. The key is written and
45
+ * immediately removed so the value never accumulates and a stale claim cannot
46
+ * outlive the session that made it.
47
+ */
48
+ export declare function storageChannel(name: string): Channel | null;
49
+ /**
50
+ * The best channel available, or null.
51
+ *
52
+ * Null is a supported state, not a failure: a single tab with no channel
53
+ * refreshes alone and cannot stampede by definition.
54
+ */
55
+ export declare function bestChannel(name: string): Channel | null;
56
+ export type RefreshMessage = {
57
+ type: 'claim';
58
+ id: string;
59
+ at: number;
60
+ } | {
61
+ type: 'refreshed';
62
+ jwt: string;
63
+ expiresAt: number;
64
+ sessionId: string;
65
+ } | {
66
+ type: 'signed_out';
67
+ };
68
+ export declare function isRefreshMessage(value: unknown): value is RefreshMessage;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ /**
3
+ * §7.4: "a BroadcastChannel elects one refreshing tab."
4
+ *
5
+ * Twenty open tabs must produce ONE refresh, not twenty. That is not only a
6
+ * load concern: refresh tokens rotate (§7.4 step 2), so twenty simultaneous
7
+ * refreshes send the same token twenty times, nineteen of them land outside the
8
+ * grace window, and the session is revoked for token reuse. Getting this wrong
9
+ * does not degrade the experience — it signs the user out.
10
+ *
11
+ * The election is deliberately simple. A tab that wants to refresh announces a
12
+ * claim carrying a random id and a timestamp; the lowest id within the claim
13
+ * window wins. No consensus protocol, because the cost of getting it wrong is
14
+ * bounded: two tabs refreshing within the 30-second grace window is exactly the
15
+ * race the grace window exists to absorb.
16
+ *
17
+ * BroadcastChannel is missing in Safari private mode and older browsers, so
18
+ * there is a localStorage fallback. A tab with NEITHER refreshes alone, which
19
+ * is correct — one tab cannot stampede.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.CLAIM_WINDOW_MS = void 0;
23
+ exports.winsElection = winsElection;
24
+ exports.broadcastChannel = broadcastChannel;
25
+ exports.storageChannel = storageChannel;
26
+ exports.bestChannel = bestChannel;
27
+ exports.isRefreshMessage = isRefreshMessage;
28
+ /** How long a claim stands before it is considered abandoned. */
29
+ exports.CLAIM_WINDOW_MS = 3_000;
30
+ /**
31
+ * Whether `mine` wins against the claims seen so far.
32
+ *
33
+ * Lowest id wins, and only claims inside the window count — otherwise a tab
34
+ * that crashed mid-refresh would block every other tab forever.
35
+ */
36
+ function winsElection(mine, others, now) {
37
+ const live = others.filter((claim) => now - claim.at < exports.CLAIM_WINDOW_MS);
38
+ return live.every((claim) => mine.id <= claim.id);
39
+ }
40
+ /** BroadcastChannel where available. */
41
+ function broadcastChannel(name) {
42
+ if (typeof BroadcastChannel === 'undefined')
43
+ return null;
44
+ const channel = new BroadcastChannel(name);
45
+ return {
46
+ post: (message) => channel.postMessage(message),
47
+ subscribe(handler) {
48
+ const listener = (event) => handler(event.data);
49
+ channel.addEventListener('message', listener);
50
+ return () => channel.removeEventListener('message', listener);
51
+ },
52
+ close: () => channel.close(),
53
+ };
54
+ }
55
+ /**
56
+ * localStorage fallback.
57
+ *
58
+ * The `storage` event fires only in OTHER tabs, which is exactly the semantics
59
+ * needed — a tab does not need to hear its own message. The key is written and
60
+ * immediately removed so the value never accumulates and a stale claim cannot
61
+ * outlive the session that made it.
62
+ */
63
+ function storageChannel(name) {
64
+ if (typeof window === 'undefined' || typeof window.localStorage === 'undefined')
65
+ return null;
66
+ const key = `atlas:${name}`;
67
+ return {
68
+ post(message) {
69
+ try {
70
+ window.localStorage.setItem(key, JSON.stringify({ message, at: Date.now() }));
71
+ window.localStorage.removeItem(key);
72
+ }
73
+ catch {
74
+ // A full or disabled localStorage must not break sign-in. Losing
75
+ // cross-tab coordination degrades to "each tab refreshes for itself",
76
+ // which the 30-second grace window absorbs.
77
+ }
78
+ },
79
+ subscribe(handler) {
80
+ const listener = (event) => {
81
+ if (event.key !== key || !event.newValue)
82
+ return;
83
+ try {
84
+ handler(JSON.parse(event.newValue).message);
85
+ }
86
+ catch {
87
+ // Another script writing to our key is not our problem to crash over.
88
+ }
89
+ };
90
+ window.addEventListener('storage', listener);
91
+ return () => window.removeEventListener('storage', listener);
92
+ },
93
+ close: () => undefined,
94
+ };
95
+ }
96
+ /**
97
+ * The best channel available, or null.
98
+ *
99
+ * Null is a supported state, not a failure: a single tab with no channel
100
+ * refreshes alone and cannot stampede by definition.
101
+ */
102
+ function bestChannel(name) {
103
+ return broadcastChannel(name) ?? storageChannel(name);
104
+ }
105
+ function isRefreshMessage(value) {
106
+ if (!value || typeof value !== 'object')
107
+ return false;
108
+ const type = value.type;
109
+ return type === 'claim' || type === 'refreshed' || type === 'signed_out';
110
+ }
111
+ //# sourceMappingURL=tab-election.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tab-election.js","sourceRoot":"","sources":["../src/tab-election.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AAgBH,oCAGC;AASD,4CAaC;AAUD,wCA6BC;AAQD,kCAEC;AAOD,4CAIC;AAnGD,iEAAiE;AACpD,QAAA,eAAe,GAAG,KAAK,CAAC;AAOrC;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,IAAW,EAAE,MAAwB,EAAE,GAAW;IAC7E,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,EAAE,GAAG,uBAAe,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;AACpD,CAAC;AAQD,wCAAwC;AACxC,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,IAAI,OAAO,gBAAgB,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO;QACL,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC;QAC/C,SAAS,CAAC,OAAO;YACf,MAAM,QAAQ,GAAG,CAAC,KAAmB,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9D,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC9C,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAChE,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE;KAC7B,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,cAAc,CAAC,IAAY;IACzC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,YAAY,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAE7F,MAAM,GAAG,GAAG,SAAS,IAAI,EAAE,CAAC;IAC5B,OAAO;QACL,IAAI,CAAC,OAAO;YACV,IAAI,CAAC;gBACH,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC9E,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACtC,CAAC;YAAC,MAAM,CAAC;gBACP,iEAAiE;gBACjE,sEAAsE;gBACtE,4CAA4C;YAC9C,CAAC;QACH,CAAC;QACD,SAAS,CAAC,OAAO;YACf,MAAM,QAAQ,GAAG,CAAC,KAAmB,EAAE,EAAE;gBACvC,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ;oBAAE,OAAO;gBACjD,IAAI,CAAC;oBACH,OAAO,CAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAA0B,CAAC,OAAO,CAAC,CAAC;gBACxE,CAAC;gBAAC,MAAM,CAAC;oBACP,sEAAsE;gBACxE,CAAC;YACH,CAAC,CAAC;YACF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC7C,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC/D,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,SAAS;KACvB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,IAAY;IACtC,OAAO,gBAAgB,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;AACxD,CAAC;AAOD,SAAgB,gBAAgB,CAAC,KAAc;IAC7C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,IAAI,GAAI,KAA4B,CAAC,IAAI,CAAC;IAChD,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,YAAY,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * §6 Telegram Login Widget — the client half.
3
+ *
4
+ * Like the native / One-Tap helper, the SDK does not own fetch: these are pure
5
+ * request/response shapers. The Telegram Login Widget calls the page back with a
6
+ * signed payload object; the app wraps it with `telegramSignInBody`, POSTs it to
7
+ * `/v1/oauth/telegram`, and reads the result with the shared
8
+ * `parseNativeSignInResponse` (a `ticket` means complete; a bare status means a
9
+ * factor is still owed — exactly the same contract as every other flow).
10
+ */
11
+ export interface TelegramWidgetUser {
12
+ id: number | string;
13
+ first_name?: string;
14
+ last_name?: string;
15
+ username?: string;
16
+ photo_url?: string;
17
+ auth_date: number | string;
18
+ hash: string;
19
+ [key: string]: unknown;
20
+ }
21
+ export interface TelegramSignInBody {
22
+ telegram: TelegramWidgetUser;
23
+ }
24
+ /** Build the POST body for `/v1/oauth/telegram` from the widget's user object. */
25
+ export declare function telegramSignInBody(payload: TelegramWidgetUser): TelegramSignInBody;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * §6 Telegram Login Widget — the client half.
4
+ *
5
+ * Like the native / One-Tap helper, the SDK does not own fetch: these are pure
6
+ * request/response shapers. The Telegram Login Widget calls the page back with a
7
+ * signed payload object; the app wraps it with `telegramSignInBody`, POSTs it to
8
+ * `/v1/oauth/telegram`, and reads the result with the shared
9
+ * `parseNativeSignInResponse` (a `ticket` means complete; a bare status means a
10
+ * factor is still owed — exactly the same contract as every other flow).
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.telegramSignInBody = telegramSignInBody;
14
+ /** Build the POST body for `/v1/oauth/telegram` from the widget's user object. */
15
+ function telegramSignInBody(payload) {
16
+ return { telegram: payload };
17
+ }
18
+ //# sourceMappingURL=telegram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../src/telegram.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAkBH,gDAEC;AAHD,kFAAkF;AAClF,SAAgB,kBAAkB,CAAC,OAA2B;IAC5D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * §bot — client-side signal collection for the anti-bot pipeline.
3
+ *
4
+ * Two collectors, both privacy-preserving and both fully best-effort (they never
5
+ * throw, and degrade to nulls in any environment):
6
+ *
7
+ * - `collectDeviceSignals()` — a snapshot of the device/environment: screen,
8
+ * timezone, hardware, a coarse canvas/WebGL fingerprint, and AUTOMATION
9
+ * TELLS (navigator.webdriver, headless markers, Selenium/Puppeteer traces).
10
+ * - `BehaviorRecorder` — accumulates interaction TIMING AGGREGATES on the auth
11
+ * form: mouse kinematics, keystroke dwell/flight, time-to-submit, scrolls.
12
+ * It records COUNTS AND TIMINGS ONLY — never which keys were pressed, never
13
+ * field values. There is nothing sensitive to leak.
14
+ *
15
+ * `sendTelemetry()` posts a snapshot to `/v1/client/telemetry`. It is
16
+ * fire-and-forget: a failure resolves `false` and is swallowed, so telemetry can
17
+ * never interfere with a real sign-in.
18
+ */
19
+ export interface DeviceSignals {
20
+ screenWidth: number | null;
21
+ screenHeight: number | null;
22
+ colorDepth: number | null;
23
+ pixelRatio: number | null;
24
+ timezone: string | null;
25
+ timezoneOffset: number | null;
26
+ language: string | null;
27
+ languages: string[];
28
+ platform: string | null;
29
+ hardwareConcurrency: number | null;
30
+ deviceMemory: number | null;
31
+ maxTouchPoints: number | null;
32
+ touchSupport: boolean;
33
+ cookieEnabled: boolean | null;
34
+ doNotTrack: boolean | null;
35
+ webglVendor: string | null;
36
+ webglRenderer: string | null;
37
+ pluginCount: number | null;
38
+ pointerType: string | null;
39
+ webdriver: boolean;
40
+ headless: boolean;
41
+ hasChrome: boolean;
42
+ webglSoftware: boolean;
43
+ automationHint: boolean;
44
+ }
45
+ export declare function collectDeviceSignals(): DeviceSignals;
46
+ /** A stable pseudonymous client id, persisted in localStorage. */
47
+ export declare function deviceId(): string | null;
48
+ /** A short client-side fingerprint over the stable device signals. */
49
+ export declare function deviceFingerprint(device: DeviceSignals): string;
50
+ export interface BehaviorSignals {
51
+ mouseMoves: number;
52
+ mouseDistance: number;
53
+ mouseAvgSpeed: number | null;
54
+ mouseMaxSpeed: number;
55
+ mouseSpeedVariance: number | null;
56
+ mouseStraightLineRatio: number | null;
57
+ keyCount: number;
58
+ keyAvgDwellMs: number | null;
59
+ keyAvgFlightMs: number | null;
60
+ keyDwellVariance: number | null;
61
+ keyFlightVariance: number | null;
62
+ pasteCount: number;
63
+ backspaceCount: number;
64
+ timeToFirstInteractionMs: number | null;
65
+ timeToSubmitMs: number | null;
66
+ focusChanges: number;
67
+ scrollCount: number;
68
+ hadPointerMove: boolean;
69
+ hadKeydown: boolean;
70
+ interactionCount: number;
71
+ }
72
+ /**
73
+ * Records interaction TIMINGS on the page/form. Start it when the auth form
74
+ * mounts; call `snapshot()` at submit. It listens passively and captures no
75
+ * content — only counts, distances and millisecond timings.
76
+ */
77
+ export declare class BehaviorRecorder {
78
+ private readonly start;
79
+ private firstInteraction;
80
+ private moves;
81
+ private distance;
82
+ private maxSpeed;
83
+ private speeds;
84
+ private lastX;
85
+ private lastY;
86
+ private lastMoveT;
87
+ private netDx;
88
+ private netDy;
89
+ private keyCount;
90
+ private downAt;
91
+ private dwell;
92
+ private flight;
93
+ private lastUpAt;
94
+ private pastes;
95
+ private backspaces;
96
+ private focusChanges;
97
+ private scrolls;
98
+ private target;
99
+ private bound;
100
+ constructor(target?: Document);
101
+ private mark;
102
+ private attach;
103
+ private onMove;
104
+ private onKeyDown;
105
+ private onKeyUp;
106
+ /** A snapshot of aggregates so far. Safe to call repeatedly. */
107
+ snapshot(): BehaviorSignals;
108
+ /** Detach all listeners. */
109
+ stop(): void;
110
+ }
111
+ export interface TelemetryOptions {
112
+ /** FAPI base, e.g. 'https://accounts.acme.com' or '' for same-origin. */
113
+ api: string;
114
+ publishableKey: string;
115
+ attemptId?: string;
116
+ kind?: 'sign_in' | 'sign_up' | 'telemetry';
117
+ recorder?: BehaviorRecorder;
118
+ fetchImpl?: typeof fetch;
119
+ }
120
+ /**
121
+ * Collect a device snapshot (+ behaviour from `recorder`, if given) and POST it.
122
+ * Resolves `true` when the beacon was accepted, `false` otherwise — never
123
+ * throws. Uses `navigator.sendBeacon` when possible so it survives navigation.
124
+ */
125
+ export declare function sendTelemetry(opts: TelemetryOptions): Promise<boolean>;