mppx 0.14.0 → 0.15.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # mppx
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4f8790f: Fixed implicit TLS bypass for local hostnames, discarded Express payment responses, expiry-crossing replay claims, standalone realm validation, and WebSocket metering from stale challenge prices. Bounded anonymous subscription challenge keys and retained billing keys only after successful payment submission.
8
+
9
+ Standalone credential APIs now require a configured matching realm or trusted route context. Custom WebSocket routes must return the canonical challenge or set an explicit `amount`. The CLI no longer pays when `--insecure` is enabled. Anonymous subscription setup carries an authenticated encrypted key token in the challenge, using one persistent sealing key instead of per-request pending state. Challenge expiry remains enforced before activation. Previously persisted abandoned keys are not automatically removed.
10
+
11
+ Preserved successful charge receipts when a challenge expired during settlement, preventing a fresh payment challenge after funds moved.
12
+
13
+ Preserved Express JSON replacers, spacing, and escaping before payment response wrapping. Preserved HTTPS hostnames for certificate validation; local HTTPS names use the system resolver, while plain HTTP localhost subdomains retain the loopback shortcut.
14
+
15
+ Selected the key bound to a new subscription challenge before a previously retained billing key, allowing an expired subscription to be activated again for the same payer.
16
+
17
+ ## 0.14.1
18
+
19
+ ### Patch Changes
20
+
21
+ - 908af47: `superviseCloses` accepts a `settlementSchedule`, in raw token units, and settles open channels whose spend crossed it, within spend, reporting them `settled`. Servers that charge with `charge()` rather than through the session handler, whose schedule runs only on its own charges, now collect spend without waiting for a close. `resolveSettlementSchedule` and `ResolvedSettlementSchedule` are exported.
22
+
3
23
  ## 0.14.0
4
24
 
5
25
  ### Minor Changes
package/README.md CHANGED
@@ -71,8 +71,8 @@ Generate `MPP_SECRET_KEY` with at least 32 bytes, for example: `openssl rand -ba
71
71
  ### Client
72
72
 
73
73
  ```ts
74
- import { privateKeyToAccount } from 'viem/accounts'
75
74
  import { Mppx, tempo } from 'mppx/client'
75
+ import { privateKeyToAccount } from 'viem/accounts'
76
76
 
77
77
  Mppx.create({
78
78
  methods: [tempo({ account: privateKeyToAccount('0x...') })],
package/dist/Expires.js CHANGED
@@ -16,7 +16,7 @@ export function assert(expires, challengeId) {
16
16
  ...(challengeId && { id: challengeId }),
17
17
  reason: 'malformed expires timestamp',
18
18
  });
19
- if (new Date(expires) < new Date())
19
+ if (Date.parse(expires) <= Date.now())
20
20
  throw new PaymentExpiredError({ expires });
21
21
  }
22
22
  /** Returns an ISO 8601 datetime string `n` days from now. */
package/dist/Store.d.ts CHANGED
@@ -28,7 +28,7 @@ export type Change<value, result> = {
28
28
  * must be synchronous and free of side effects.
29
29
  */
30
30
  export type Update<itemMap extends StoreItemMap = StoreItemMap> = <key extends keyof itemMap & string, result>(key: key, fn: (current: itemMap[key] | null) => Change<itemMap[key], result>) => Promise<result>;
31
- /** Attempts to atomically record first use of a replay key through its expiry. */
31
+ /** Attempts to atomically record first use of a replay key. Must reject expires <= Date.now() inside the atomic operation. */
32
32
  export type TryClaim<itemMap extends StoreItemMap = StoreItemMap> = <key extends keyof itemMap & string>(key: key, expires: number) => MaybePromise<boolean>;
33
33
  /** Persisted marker used by the {@link tryClaim} fallback. */
34
34
  export type ReplayMarker = {
@@ -68,7 +68,7 @@ export type AtomicStore<itemMap extends StoreItemMap = StoreItemMap> = Store<ite
68
68
  * Attempts to atomically record first use of a replay key through its expiry.
69
69
  *
70
70
  * Returns `true` when this call recorded the key and `false` when it was already
71
- * recorded and unexpired. `expires` is a Unix timestamp in milliseconds.
71
+ * recorded and unexpired, or the proposed expiry has passed. `expires` is a Unix timestamp in milliseconds.
72
72
  * Uses the adapter's optimized primitive when available and otherwise falls
73
73
  * back to {@link AtomicStore.update}.
74
74
  */
package/dist/Store.js CHANGED
@@ -19,14 +19,22 @@ import { Json } from 'ox';
19
19
  * Attempts to atomically record first use of a replay key through its expiry.
20
20
  *
21
21
  * Returns `true` when this call recorded the key and `false` when it was already
22
- * recorded and unexpired. `expires` is a Unix timestamp in milliseconds.
22
+ * recorded and unexpired, or the proposed expiry has passed. `expires` is a Unix timestamp in milliseconds.
23
23
  * Uses the adapter's optimized primitive when available and otherwise falls
24
24
  * back to {@link AtomicStore.update}.
25
25
  */
26
26
  export function tryClaim(store, key, expires) {
27
- if (store.tryClaim)
28
- return store.tryClaim(key, expires);
27
+ if (!Number.isFinite(expires) || expires <= Date.now())
28
+ return false;
29
+ if (store.tryClaim) {
30
+ const result = store.tryClaim(key, expires);
31
+ return typeof result === 'boolean'
32
+ ? result && expires > Date.now()
33
+ : result.then((claimed) => claimed && expires > Date.now());
34
+ }
29
35
  return store.update(key, (current) => {
36
+ if (!Number.isFinite(expires) || expires <= Date.now())
37
+ return { op: 'noop', result: false };
30
38
  if (current !== null && (!isReplayMarker(current) || current.expires > Date.now()))
31
39
  return { op: 'noop', result: false };
32
40
  return {
@@ -144,6 +152,8 @@ export function memory(options) {
144
152
  return change.result;
145
153
  },
146
154
  tryClaim(key, expires) {
155
+ if (!Number.isFinite(expires) || expires <= Date.now())
156
+ return false;
147
157
  const current = store.has(key) ? Json.parse(store.get(key)) : null;
148
158
  if (current !== null && (!isReplayMarker(current) || current.expires > Date.now()))
149
159
  return false;
@@ -132,6 +132,8 @@ export async function verify(request, options) {
132
132
  catch {
133
133
  return { status: 'unverified', reason: 'Replay protection is unavailable.' };
134
134
  }
135
+ if (input.expires <= Math.floor(Date.now() / 1000))
136
+ return { status: 'invalid', reason: 'The signature has expired.' };
135
137
  return { status: 'verified', input };
136
138
  }
137
139
  function parse(request, expectedTag) {