prividium 0.5.0 → 0.6.1

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.
@@ -48,6 +48,9 @@ async function startServer(opts) {
48
48
  },
49
49
  onReAuthNeeded() {
50
50
  workflow.onMessage(`Please login again: ${serverUrl}`);
51
+ },
52
+ onError: (err) => {
53
+ workflow.onError(err);
51
54
  }
52
55
  });
53
56
  checkHostAndPortWarnings(opts.host, opts.port, opts.allowExternalAccess);
@@ -106,4 +106,7 @@ export class CreationWorkflow {
106
106
  onMessage(msg) {
107
107
  log.message(msg);
108
108
  }
109
+ onError(err) {
110
+ log.error(`Error received: ${err.message}`);
111
+ }
109
112
  }
@@ -23,6 +23,11 @@ export function buildServer(config) {
23
23
  const app = fastifyApp();
24
24
  const validHosts = config.host === 'localhost' || config.host === '127.0.0.1' ? ['localhost', '127.0.0.1'] : [config.host];
25
25
  app.setValidatorCompiler(validatorCompiler);
26
+ app.setErrorHandler((err, _req, reply) => {
27
+ config.onError(err);
28
+ console.error(err);
29
+ return reply.status(500).send({ error: err.message });
30
+ });
26
31
  const state = randomStateString();
27
32
  let accessToken = '';
28
33
  let expiresAt = new Date();
@@ -38,6 +38,11 @@
38
38
  <span>You can now access your Prividium™ RPC proxy at: </span>
39
39
  <span class="font-bold">http://127.0.0.1:24101/rpc</span>
40
40
  </div>
41
+ <pre
42
+ id="error-details"
43
+ style="display: none"
44
+ class="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg text-left text-sm text-red-800 overflow-auto max-h-48"
45
+ ></pre>
41
46
  </div>
42
47
  <script>
43
48
  const titleElem = document.getElementById('main-title');
@@ -58,12 +63,32 @@
58
63
  token: maybeToken,
59
64
  state: maybeState
60
65
  })
61
- }).then(() => {
62
- titleElem.innerHTML = 'Done! This window can be closed now.';
66
+ }).then((res) => {
67
+ if (res.status === 200) {
68
+ titleElem.innerHTML = 'Done! This window can be closed now.';
69
+ document.querySelector('svg').style.display = 'none';
70
+ const urlElem = document.getElementById('proxy-url');
71
+ urlElem.style.display = 'block';
72
+ } else {
73
+ titleElem.innerHTML = 'Oops! Something went wrong';
74
+ titleElem.className = 'text-2xl md:text-3xl font-bold text-red-600';
75
+ document.querySelector('svg').style.display = 'none';
76
+ res.text().then((errMsg) => {
77
+ const errorElem = document.getElementById('error-details');
78
+ errorElem.textContent = errMsg;
79
+ errorElem.style.display = 'block';
80
+ console.error(errMsg);
81
+ });
82
+ }
83
+ }, (err) => {
84
+ titleElem.innerHTML = 'Oops! Something went wrong';
85
+ titleElem.className = 'text-2xl md:text-3xl font-bold text-red-600';
63
86
  document.querySelector('svg').style.display = 'none';
64
- const urlElem = document.getElementById('proxy-url');
65
- urlElem.style.display = 'block';
66
- }, console.error);
87
+ const errorElem = document.getElementById('error-details');
88
+ errorElem.textContent = err.message;
89
+ errorElem.style.display = 'block';
90
+ console.error(err);
91
+ });
67
92
  }
68
93
  </script>
69
94
  </body>
@@ -2,3 +2,10 @@
2
2
  * Checks if a Response contains a Prividium unauthorized/forbidden error.
3
3
  */
4
4
  export declare function hasPrividiumUnauthorizedError(response: Response): Promise<boolean>;
5
+ /**
6
+ * Extracts a human-readable error string from a failed HTTP response.
7
+ * Attempts to parse the server's `{ error: { code, message } }` JSON structure,
8
+ * falls back to plain text, and ultimately to status + statusText.
9
+ * Never throws — always returns a usable string.
10
+ */
11
+ export declare function extractResponseError(response: Response): Promise<string>;
@@ -7,6 +7,12 @@ const jsonRpcErrorSchema = z.object({
7
7
  data: z.unknown().optional()
8
8
  })
9
9
  });
10
+ const apiErrorSchema = z.object({
11
+ error: z.object({
12
+ code: z.string(),
13
+ message: z.string()
14
+ })
15
+ });
10
16
  /**
11
17
  * Checks if a Response contains a Prividium unauthorized/forbidden error.
12
18
  */
@@ -19,3 +25,35 @@ export async function hasPrividiumUnauthorizedError(response) {
19
25
  }
20
26
  return false;
21
27
  }
28
+ /**
29
+ * Extracts a human-readable error string from a failed HTTP response.
30
+ * Attempts to parse the server's `{ error: { code, message } }` JSON structure,
31
+ * falls back to plain text, and ultimately to status + statusText.
32
+ * Never throws — always returns a usable string.
33
+ */
34
+ export async function extractResponseError(response) {
35
+ const base = `${response.status} ${response.statusText}`;
36
+ try {
37
+ const cloned = response.clone();
38
+ const text = await cloned.text();
39
+ if (!text) {
40
+ return base;
41
+ }
42
+ try {
43
+ const json = JSON.parse(text);
44
+ const parsed = apiErrorSchema.safeParse(json);
45
+ if (parsed.success) {
46
+ const { code, message } = parsed.data.error;
47
+ return `${base}: ${message} (${code})`;
48
+ }
49
+ }
50
+ catch {
51
+ // Not JSON
52
+ }
53
+ return `${base}: ${text}`;
54
+ }
55
+ catch {
56
+ // Body unreadable
57
+ }
58
+ return base;
59
+ }
@@ -1,7 +1,7 @@
1
1
  import { http } from 'viem';
2
2
  import { LocalStorage, TokenManager } from './storage.js';
3
3
  import { PopupAuth } from './popup-auth.js';
4
- import { hasPrividiumUnauthorizedError } from './error-utils.js';
4
+ import { extractResponseError, hasPrividiumUnauthorizedError } from './error-utils.js';
5
5
  import { PrividiumSessionError } from './errors.js';
6
6
  import { buildChainObject, createApiMethods, rpcUrl } from './chain-core.js';
7
7
  export function createPrividiumChain(config) {
@@ -44,7 +44,8 @@ export function createPrividiumChain(config) {
44
44
  throw new PrividiumSessionError();
45
45
  }
46
46
  if (!response.ok) {
47
- throw new Error(`Error calling ${url}: ${response.status} ${response.statusText}`);
47
+ const detail = await extractResponseError(response);
48
+ throw new Error(`Error calling ${url}: ${detail}`);
48
49
  }
49
50
  const parsed = schema.safeParse(await response.json());
50
51
  if (!parsed.success) {
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { extractResponseError } from './error-utils.js';
2
3
  const siweMessageResponseSchema = z.object({
3
4
  msg: z.string()
4
5
  });
@@ -19,8 +20,7 @@ export class SiweAuth {
19
20
  }
20
21
  async authorize() {
21
22
  // Step 1: Request SIWE message
22
- const siweMessageUrl = new URL('/api/siwe-messages', this.config.prividiumApiBaseUrl).toString();
23
- const siweResponse = await fetch(siweMessageUrl, {
23
+ const siweResponse = await fetch(new URL('/api/siwe-messages', this.config.prividiumApiBaseUrl), {
24
24
  method: 'POST',
25
25
  headers: { 'Content-Type': 'application/json' },
26
26
  body: JSON.stringify({
@@ -29,20 +29,21 @@ export class SiweAuth {
29
29
  })
30
30
  });
31
31
  if (!siweResponse.ok) {
32
- throw new Error(`Failed to get SIWE message: ${siweResponse.status} ${siweResponse.statusText}`);
32
+ const detail = await extractResponseError(siweResponse);
33
+ throw new Error(`Failed to get SIWE message: ${detail}`);
33
34
  }
34
35
  const siweData = siweMessageResponseSchema.parse(await siweResponse.json());
35
36
  // Step 2: Sign the message
36
37
  const signature = await this.config.account.signMessage({ message: siweData.msg });
37
38
  // Step 3: Login
38
- const loginUrl = new URL('/auth/login/crypto-native', this.config.prividiumApiBaseUrl).toString();
39
- const loginResponse = await fetch(loginUrl, {
39
+ const loginResponse = await fetch(new URL('/api/auth/login/crypto-native', this.config.prividiumApiBaseUrl), {
40
40
  method: 'POST',
41
41
  headers: { 'Content-Type': 'application/json' },
42
42
  body: JSON.stringify({ message: siweData.msg, signature })
43
43
  });
44
44
  if (!loginResponse.ok) {
45
- throw new Error(`SIWE login failed: ${loginResponse.status} ${loginResponse.statusText}`);
45
+ const detail = await extractResponseError(loginResponse);
46
+ throw new Error(`SIWE login failed: ${detail}`);
46
47
  }
47
48
  const loginJson = await loginResponse.json();
48
49
  // Check for MFA requirement (admin users with passkeys)
@@ -2,7 +2,7 @@ import { http } from 'viem';
2
2
  import { TokenManager } from './storage.js';
3
3
  import { MemoryStorage } from './memory-storage.js';
4
4
  import { SiweAuth } from './siwe-auth.js';
5
- import { hasPrividiumUnauthorizedError } from './error-utils.js';
5
+ import { extractResponseError, hasPrividiumUnauthorizedError } from './error-utils.js';
6
6
  import { PrividiumSessionError } from './errors.js';
7
7
  import { buildChainObject, createApiMethods, rpcUrl } from './chain-core.js';
8
8
  export function createPrividiumSiweChain(config) {
@@ -102,7 +102,8 @@ export function createPrividiumSiweChain(config) {
102
102
  throw new PrividiumSessionError();
103
103
  }
104
104
  if (!response.ok) {
105
- throw new Error(`Error calling ${url}: ${response.status} ${response.statusText}`);
105
+ const detail = await extractResponseError(response);
106
+ throw new Error(`Error calling ${url}: ${detail}`);
106
107
  }
107
108
  const parsed = schema.safeParse(await response.json());
108
109
  if (!parsed.success) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prividium",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "bin": {
5
5
  "prividium": "bin/cli.js"
6
6
  },