@medialane/sdk 0.128.0 → 0.129.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.
- package/README.md +34 -0
- package/dist/{errors-De9avg1S.d.ts → errors-DQJIxfVu.d.ts} +1 -2
- package/dist/{errors-CO99guAf.d.cts → errors-Fgqpww_8.d.cts} +1 -2
- package/dist/index.cjs +30 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +29 -11
- package/dist/index.js.map +1 -1
- package/dist/starknet/index.cjs +4 -8
- package/dist/starknet/index.cjs.map +1 -1
- package/dist/starknet/index.d.cts +2 -2
- package/dist/starknet/index.d.ts +2 -2
- package/dist/starknet/index.js +4 -8
- package/dist/starknet/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -465,6 +465,40 @@ try {
|
|
|
465
465
|
}
|
|
466
466
|
```
|
|
467
467
|
|
|
468
|
+
### Showing an error to a user
|
|
469
|
+
|
|
470
|
+
Three types carry text written for someone to read. Everything else is machinery, and the
|
|
471
|
+
message belongs to whatever your screen was trying to do.
|
|
472
|
+
|
|
473
|
+
| Type | What it means | Show |
|
|
474
|
+
|---|---|---|
|
|
475
|
+
| `MedialaneApiError` | The API refused the request | `err.message`, which is the reason the API gave or a plain statement of the status |
|
|
476
|
+
| `UserFacingError` | The SDK got partway and stopped, for example a transaction that reverted | `err.message` |
|
|
477
|
+
| `PasskeyCancelledError` | The prompt was dismissed | Nothing was submitted, so avoid wording it as a failure |
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { MedialaneApiError, PasskeyCancelledError, UserFacingError } from "@medialane/sdk";
|
|
481
|
+
|
|
482
|
+
function describe(err: unknown, fallback: string): string {
|
|
483
|
+
if (err instanceof PasskeyCancelledError) return "Request not completed. Nothing was submitted.";
|
|
484
|
+
if (err instanceof UserFacingError) return err.message;
|
|
485
|
+
if (err instanceof MedialaneApiError) return err.message;
|
|
486
|
+
return fallback;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
catch (err) {
|
|
490
|
+
console.error(err);
|
|
491
|
+
setError(describe(err, "We couldn't create that listing. Please try again."));
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
The fallback matters. An error carrying none of those types says nothing a reader can act on,
|
|
496
|
+
and its text is as likely to be a gateway response as a sentence. Your call site knows which
|
|
497
|
+
action was attempted, so that is the message worth showing.
|
|
498
|
+
|
|
499
|
+
A response body that is not ours stays in `err.details`, where it is available while debugging
|
|
500
|
+
without reaching a reader.
|
|
501
|
+
|
|
468
502
|
---
|
|
469
503
|
|
|
470
504
|
## Configuration Reference
|
|
@@ -1057,9 +1057,8 @@ declare class MedialaneApiError extends Error {
|
|
|
1057
1057
|
readonly status: number;
|
|
1058
1058
|
readonly retryAfterMs?: number | undefined;
|
|
1059
1059
|
readonly details?: unknown | undefined;
|
|
1060
|
-
readonly isAuthored: boolean;
|
|
1061
1060
|
readonly code: MedialaneErrorCode;
|
|
1062
|
-
constructor(status: number, message: string, retryAfterMs?: number | undefined, details?: unknown | undefined
|
|
1061
|
+
constructor(status: number, message: string, retryAfterMs?: number | undefined, details?: unknown | undefined);
|
|
1063
1062
|
}
|
|
1064
1063
|
declare class ApiClient {
|
|
1065
1064
|
private readonly baseUrl;
|
|
@@ -1057,9 +1057,8 @@ declare class MedialaneApiError extends Error {
|
|
|
1057
1057
|
readonly status: number;
|
|
1058
1058
|
readonly retryAfterMs?: number | undefined;
|
|
1059
1059
|
readonly details?: unknown | undefined;
|
|
1060
|
-
readonly isAuthored: boolean;
|
|
1061
1060
|
readonly code: MedialaneErrorCode;
|
|
1062
|
-
constructor(status: number, message: string, retryAfterMs?: number | undefined, details?: unknown | undefined
|
|
1061
|
+
constructor(status: number, message: string, retryAfterMs?: number | undefined, details?: unknown | undefined);
|
|
1063
1062
|
}
|
|
1064
1063
|
declare class ApiClient {
|
|
1065
1064
|
private readonly baseUrl;
|
package/dist/index.cjs
CHANGED
|
@@ -275,12 +275,11 @@ function deriveErrorCode(status) {
|
|
|
275
275
|
return "UNKNOWN";
|
|
276
276
|
}
|
|
277
277
|
var MedialaneApiError = class extends Error {
|
|
278
|
-
constructor(status, message, retryAfterMs, details
|
|
278
|
+
constructor(status, message, retryAfterMs, details) {
|
|
279
279
|
super(message);
|
|
280
280
|
this.status = status;
|
|
281
281
|
this.retryAfterMs = retryAfterMs;
|
|
282
282
|
this.details = details;
|
|
283
|
-
this.isAuthored = isAuthored;
|
|
284
283
|
this.name = "MedialaneApiError";
|
|
285
284
|
this.code = deriveErrorCode(status);
|
|
286
285
|
}
|
|
@@ -317,14 +316,12 @@ var ApiClient = class {
|
|
|
317
316
|
});
|
|
318
317
|
if (!response.ok && !allowed(response.status)) {
|
|
319
318
|
const text = await response.text().catch(() => response.statusText);
|
|
320
|
-
let message =
|
|
321
|
-
let details;
|
|
322
|
-
let isAuthored = false;
|
|
319
|
+
let message = `Request failed with status ${response.status}`;
|
|
320
|
+
let details = text;
|
|
323
321
|
try {
|
|
324
322
|
const body = JSON.parse(text);
|
|
325
323
|
if (typeof body.error === "string" && body.error.trim() !== "") {
|
|
326
324
|
message = body.error;
|
|
327
|
-
isAuthored = true;
|
|
328
325
|
}
|
|
329
326
|
details = body;
|
|
330
327
|
} catch {
|
|
@@ -333,8 +330,7 @@ var ApiClient = class {
|
|
|
333
330
|
response.status,
|
|
334
331
|
message,
|
|
335
332
|
parseRetryAfter(response.headers.get("retry-after")),
|
|
336
|
-
details
|
|
337
|
-
isAuthored
|
|
333
|
+
details
|
|
338
334
|
);
|
|
339
335
|
}
|
|
340
336
|
return response;
|
|
@@ -1473,8 +1469,6 @@ function createRateLimiter(windowMs, max) {
|
|
|
1473
1469
|
}
|
|
1474
1470
|
var TRUSTED_APP_IP_HEADER = "x-medialane-client-ip";
|
|
1475
1471
|
function requestIp(req) {
|
|
1476
|
-
const fromApp = req.headers.get(TRUSTED_APP_IP_HEADER)?.trim();
|
|
1477
|
-
if (fromApp) return fromApp;
|
|
1478
1472
|
const fromEdge = req.headers.get("x-vercel-forwarded-for")?.split(",")[0]?.trim();
|
|
1479
1473
|
if (fromEdge) return fromEdge;
|
|
1480
1474
|
const forwarded = req.headers.get("x-forwarded-for");
|
|
@@ -1863,6 +1857,30 @@ async function getBackendSignedUrl(config, kind = "image") {
|
|
|
1863
1857
|
if (!res.ok || !data.data) throw new Error(data.error ?? "Failed to create upload URL");
|
|
1864
1858
|
return data.data.url;
|
|
1865
1859
|
}
|
|
1860
|
+
|
|
1861
|
+
// src/server/trusted-client-ip.ts
|
|
1862
|
+
var SPOOFABLE_FORWARDING_HEADERS = [
|
|
1863
|
+
"x-forwarded-for",
|
|
1864
|
+
"x-real-ip",
|
|
1865
|
+
"x-client-ip",
|
|
1866
|
+
"true-client-ip",
|
|
1867
|
+
"cf-connecting-ip",
|
|
1868
|
+
TRUSTED_APP_IP_HEADER
|
|
1869
|
+
];
|
|
1870
|
+
function isSpoofableForwardingHeader(name) {
|
|
1871
|
+
return SPOOFABLE_FORWARDING_HEADERS.includes(name.toLowerCase());
|
|
1872
|
+
}
|
|
1873
|
+
function trustedClientIp(req) {
|
|
1874
|
+
const vercel = req.headers.get("x-vercel-forwarded-for")?.split(",")[0]?.trim();
|
|
1875
|
+
if (vercel) return vercel;
|
|
1876
|
+
const forwarded = req.headers.get("x-forwarded-for");
|
|
1877
|
+
if (forwarded) {
|
|
1878
|
+
const hops = forwarded.split(",").map((hop) => hop.trim()).filter(Boolean);
|
|
1879
|
+
const nearest = hops[hops.length - 1];
|
|
1880
|
+
if (nearest) return nearest;
|
|
1881
|
+
}
|
|
1882
|
+
return "unknown";
|
|
1883
|
+
}
|
|
1866
1884
|
var IDENTITY_PREFIX = "siws_";
|
|
1867
1885
|
var ACCOUNT_SESSION_PREFIX = "account_session_";
|
|
1868
1886
|
var IDENTITY_DOMAIN = "siws-identity-v1";
|
|
@@ -2124,6 +2142,7 @@ exports.isPrivateHost = isPrivateHost;
|
|
|
2124
2142
|
exports.isPrivateOrInsecureUrl = isPrivateOrInsecureUrl;
|
|
2125
2143
|
exports.isSameOrigin = isSameOrigin;
|
|
2126
2144
|
exports.isServiceId = isServiceId;
|
|
2145
|
+
exports.isSpoofableForwardingHeader = isSpoofableForwardingHeader;
|
|
2127
2146
|
exports.isTransientRpcError = isTransientRpcError;
|
|
2128
2147
|
exports.isValidIpfsCidPath = isValidIpfsCidPath;
|
|
2129
2148
|
exports.issueAccountSessionToken = issueAccountSessionToken;
|
|
@@ -2141,6 +2160,7 @@ exports.resolveRemixPolicy = resolveRemixPolicy;
|
|
|
2141
2160
|
exports.resolveSafeImageContentType = resolveSafeImageContentType;
|
|
2142
2161
|
exports.shortenAddress = shortenAddress;
|
|
2143
2162
|
exports.stringifyBigInts = stringifyBigInts;
|
|
2163
|
+
exports.trustedClientIp = trustedClientIp;
|
|
2144
2164
|
exports.u256ToBigInt = u256ToBigInt;
|
|
2145
2165
|
exports.uploadDirectoryToBackend = uploadDirectoryToBackend;
|
|
2146
2166
|
exports.uploadFileToBackend = uploadFileToBackend;
|