javascript-solid-server 0.0.120 → 0.0.121

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.120",
3
+ "version": "0.0.121",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # Demo: HTTP 402 Payment-Gated Article
3
+ # Requires: JSS running on localhost:3000 with --pay --pay-cost 10
4
+
5
+ BASE="http://localhost:3000"
6
+
7
+ echo "=== Setting up payment-gated article demo ==="
8
+
9
+ # 1. Create the premium article
10
+ echo "Creating premium article..."
11
+ curl -s -X PUT "$BASE/premium/article.jsonld" \
12
+ -H "Content-Type: application/ld+json" \
13
+ -d '{
14
+ "@context": "https://schema.org",
15
+ "@type": "Article",
16
+ "headline": "The Future of Web Payments",
17
+ "author": "Melvin Carvalho",
18
+ "datePublished": "2026-03-26",
19
+ "articleBody": "This premium article explains how HTTP 402 enables native web payments. The decentralised web finally has a business model. No Stripe. No PayPal. No app store taking 30%. Just HTTP status codes and Lightning invoices."
20
+ }'
21
+
22
+ # 2. Create the ACL with PaymentCondition
23
+ echo "Creating payment-gated ACL..."
24
+ curl -s -X PUT "$BASE/premium/article.jsonld.acl" \
25
+ -H "Content-Type: application/ld+json" \
26
+ -d '{
27
+ "@context": {
28
+ "acl": "http://www.w3.org/ns/auth/acl#",
29
+ "foaf": "http://xmlns.com/foaf/0.1/"
30
+ },
31
+ "@graph": [{
32
+ "@id": "#paid",
33
+ "@type": "acl:Authorization",
34
+ "acl:agentClass": { "@id": "acl:AuthenticatedAgent" },
35
+ "acl:accessTo": { "@id": "'$BASE'/premium/article.jsonld" },
36
+ "acl:mode": [{ "@id": "acl:Read" }],
37
+ "acl:condition": {
38
+ "@type": "PaymentCondition",
39
+ "amount": "10",
40
+ "currency": "sats",
41
+ "chain": "tbtc4"
42
+ }
43
+ }]
44
+ }'
45
+
46
+ echo ""
47
+ echo "=== Demo ready ==="
48
+ echo ""
49
+ echo "1. Try accessing the article (should get 402):"
50
+ echo " curl $BASE/premium/article.jsonld"
51
+ echo ""
52
+ echo "2. Deposit testnet4 sats:"
53
+ echo " curl -X POST -H 'Authorization: Nostr <nip98-token>' $BASE/pay/.deposit -d 'txo:tbtc4:txid:vout'"
54
+ echo ""
55
+ echo "3. Try again (should get 200 + article):"
56
+ echo " curl -H 'Authorization: Nostr <nip98-token>' $BASE/premium/article.jsonld"
@@ -6,6 +6,7 @@
6
6
  import * as storage from '../storage/filesystem.js';
7
7
  import { parseAcl, AccessMode, AgentClass } from './parser.js';
8
8
  import { getAclUrl } from '../ldp/headers.js';
9
+ import { readLedger, getBalance, debit } from '../webledger.js';
9
10
 
10
11
  /**
11
12
  * Check if agent has required access mode for resource
@@ -37,7 +38,7 @@ export async function checkAccess({
37
38
 
38
39
  // Check authorizations
39
40
  // Note: For default ACLs, we check if the ACL's default rules apply to the actual resource URL
40
- const result = checkAuthorizations(
41
+ const result = await checkAuthorizations(
41
42
  authorizations,
42
43
  resourceUrl, // Use actual resource URL, not the ACL container URL
43
44
  agentWebId,
@@ -128,7 +129,7 @@ function getParentPath(path) {
128
129
  // Supported condition types
129
130
  const SUPPORTED_CONDITIONS = ['PaymentCondition', 'https://webacl.org/ns#PaymentCondition'];
130
131
 
131
- function checkAuthorizations(authorizations, targetUrl, agentWebId, requiredMode, isDefault) {
132
+ async function checkAuthorizations(authorizations, targetUrl, agentWebId, requiredMode, isDefault) {
132
133
  for (const auth of authorizations) {
133
134
  // For default ACLs, check if auth has default rules and matches target
134
135
  // For direct ACLs, check if accessTo matches target
@@ -162,6 +163,24 @@ function checkAuthorizations(authorizations, targetUrl, agentWebId, requiredMode
162
163
  c.type === 'PaymentCondition' || c.type === 'https://webacl.org/ns#PaymentCondition'
163
164
  );
164
165
  if (paymentCondition) {
166
+ // Check if agent has sufficient balance
167
+ const cost = parseInt(paymentCondition.amount, 10) || 0;
168
+ const currency = paymentCondition.currency || 'sat';
169
+ if (agentWebId && cost > 0) {
170
+ try {
171
+ const ledger = await readLedger();
172
+ const balance = getBalance(ledger, agentWebId, currency === 'sats' ? 'sat' : currency);
173
+ if (balance >= cost) {
174
+ // Deduct and grant access
175
+ debit(ledger, agentWebId, cost, currency === 'sats' ? 'sat' : currency);
176
+ const { writeLedger } = await import('../webledger.js');
177
+ await writeLedger(ledger);
178
+ return { allowed: true, paid: cost };
179
+ }
180
+ } catch (e) {
181
+ // Ledger read failed — fall through to payment required
182
+ }
183
+ }
165
184
  return { allowed: false, paymentRequired: paymentCondition };
166
185
  }
167
186
  }