openpay-x402-sdk 0.8.0 → 0.8.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
- Fix `openpay-x402-sdk/delivery` on Cloudflare Workers: the JWKS fetch called a
|
|
6
|
+
detached `fetch`, which Workers reject with "Illegal invocation", so every ticket
|
|
7
|
+
failed with `keys_unavailable`. Verified on a real Worker deployment (2026-09-10).
|
|
8
|
+
- Templates: send `Content-Disposition: attachment; filename="<object key>"` only on
|
|
9
|
+
successful file responses. An error response carrying `attachment` made Chrome
|
|
10
|
+
show ERR_INVALID_RESPONSE instead of the JSON body.
|
|
11
|
+
|
|
3
12
|
## 0.8.0
|
|
4
13
|
|
|
5
14
|
- Add the typed `openpay-x402-sdk/delivery` Web-API-only subpath: strict Ed25519
|
package/README.md
CHANGED
|
@@ -171,7 +171,7 @@ and is never transmitted.
|
|
|
171
171
|
|
|
172
172
|
## 利用ライセンス (License NFT)
|
|
173
173
|
|
|
174
|
-
SDK 0.7.1
|
|
174
|
+
SDK 0.7.1 resolves the NFT definition from
|
|
175
175
|
one product ID. Set only `LICENSE_PRODUCT_ID` and `LICENSE_SESSION_SECRET` on
|
|
176
176
|
your server. The secret must contain at least 32 random bytes of key material
|
|
177
177
|
(for example 32 random bytes encoded as hex). Replace the service URLs below
|
|
@@ -376,8 +376,7 @@ consumer types.
|
|
|
376
376
|
|
|
377
377
|
## 保護配布 (Delivery ticket)
|
|
378
378
|
|
|
379
|
-
SDK 0.8.0
|
|
380
|
-
production-adopted. OpenPay signs a 60-second bearer ticket after checking
|
|
379
|
+
SDK 0.8.0 adds delivery-ticket verification. OpenPay signs a 60-second bearer ticket after checking
|
|
381
380
|
entitlement. Sellers verify it with the public JWKS; no secret is shared with
|
|
382
381
|
OpenPay. The token is signed, not encrypted, and its claims are readable.
|
|
383
382
|
Possession authorizes admission during its lifetime; it is access control, not
|
|
@@ -468,7 +467,7 @@ issuance does not revoke an attacker's signing ability or recall downloaded byte
|
|
|
468
467
|
| Node 20.19+ | Global WebCrypto with standard Ed25519; package engine remains Node >=20. |
|
|
469
468
|
| Node 22.13+ | Same Web API entry point. |
|
|
470
469
|
| Node 24 | Same Web API entry point. |
|
|
471
|
-
| Cloudflare Workers | Standard `Ed25519`, no `nodejs_compat
|
|
470
|
+
| Cloudflare Workers | Standard `Ed25519`, no `nodejs_compat`. Verified end-to-end on a real Worker + private R2 deployment with SDK 0.8.1 (2026-09-10); re-run the smoke when you change the compatibility date. |
|
|
472
471
|
|
|
473
472
|
`ready()` detects missing Ed25519 support as `unsupported_crypto`; there is no
|
|
474
473
|
algorithm downgrade. The matrix is a release target, not proof that every runtime
|
|
@@ -3,8 +3,16 @@ import { createDeliveryGate } from 'openpay-x402-sdk/delivery';
|
|
|
3
3
|
const PRIVATE_HEADERS = {
|
|
4
4
|
'Cache-Control': 'private, no-store',
|
|
5
5
|
'Referrer-Policy': 'no-referrer',
|
|
6
|
-
'Content-Disposition': 'attachment',
|
|
7
6
|
};
|
|
7
|
+
// Only successful file responses are downloads. An error response carrying
|
|
8
|
+
// Content-Disposition: attachment makes Chrome show ERR_INVALID_RESPONSE instead of
|
|
9
|
+
// the JSON body (observed on 2026-09-10). The filename comes from the trusted object
|
|
10
|
+
// key, reduced to a safe ASCII token so no header injection is possible.
|
|
11
|
+
function attachment(key) {
|
|
12
|
+
const base = key.split('/').pop() ?? '';
|
|
13
|
+
const name = base.replace(/[^A-Za-z0-9._-]/g, '_').replace(/^\.+/, '').slice(0, 100) || 'download';
|
|
14
|
+
return `attachment; filename="${name}"`;
|
|
15
|
+
}
|
|
8
16
|
const instances = new WeakMap();
|
|
9
17
|
|
|
10
18
|
async function startup(env) {
|
|
@@ -56,7 +64,7 @@ const worker = {
|
|
|
56
64
|
// This small template ignores Range/conditional headers and serves a full
|
|
57
65
|
// 200 (HEAD returns metadata). Every such request still requires a ticket.
|
|
58
66
|
return new Response(request.method === 'HEAD' ? null : file.body, {
|
|
59
|
-
headers: { ...PRIVATE_HEADERS, 'Content-Type': 'application/octet-stream', 'Content-Length': String(file.size) },
|
|
67
|
+
headers: { ...PRIVATE_HEADERS, 'Content-Disposition': attachment(key), 'Content-Type': 'application/octet-stream', 'Content-Length': String(file.size) },
|
|
60
68
|
});
|
|
61
69
|
} catch {
|
|
62
70
|
// Do not expose request URLs, tickets, R2 keys or upstream exceptions.
|
|
@@ -2,8 +2,11 @@ import { createServer } from 'node:http';
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { createDeliveryGate } from 'openpay-x402-sdk/delivery';
|
|
4
4
|
|
|
5
|
+
// No Content-Disposition here: the redirect target (your presigned URL) decides the
|
|
6
|
+
// download name, and an error response carrying "attachment" makes Chrome show
|
|
7
|
+
// ERR_INVALID_RESPONSE instead of the JSON body.
|
|
5
8
|
const PRIVATE_HEADERS = {
|
|
6
|
-
'Cache-Control': 'private, no-store', 'Referrer-Policy': 'no-referrer',
|
|
9
|
+
'Cache-Control': 'private, no-store', 'Referrer-Policy': 'no-referrer',
|
|
7
10
|
};
|
|
8
11
|
|
|
9
12
|
async function presignObject({ key, method, expiresAt, expiresInSeconds }) {
|
package/package.json
CHANGED
package/src/delivery.mjs
CHANGED
|
@@ -192,7 +192,9 @@ async function refresh(cache, config) {
|
|
|
192
192
|
});
|
|
193
193
|
try {
|
|
194
194
|
const { keys, age } = await Promise.race([
|
|
195
|
-
|
|
195
|
+
// Call with `this` = globalThis: Cloudflare Workers reject a detached `fetch`
|
|
196
|
+
// ("Illegal invocation") — observed on a real Worker on 2026-09-10; Node does not.
|
|
197
|
+
(async () => readKeysResponse(await config.fetchImpl.call(globalThis, `${config.origin}/.well-known/openpay-delivery-keys.json`, {
|
|
196
198
|
method: 'GET', redirect: 'manual', signal: controller.signal, headers: { accept: 'application/json' },
|
|
197
199
|
}), config))(), timeout,
|
|
198
200
|
]);
|