mirror of
https://github.com/nestriness/nestri.git
synced 2026-11-03 22:22:39 +02:00
Proton's media plugins load in boxes; billing webhooks say what they did (#408)
- **Guest image (#405):** carries `libvpx`, `speex`, `libtheora`, `libvdpau` and `jsoncpp`, which Proton's GStreamer plugins link. Without them, `libgstlibav` and `libgstspeex` failed to load in every box on every Proton shipped so far. Verified: every library both published Protons need is now provided. - **Billing (#406):** every Polar webhook logs its type, the team it resolved to, the plan and status, whether it changed anything, and the subscription and customer ids. Logging only. Cut from prod rather than opened from dev: after squash merges the two histories conflict, though the files differ only in these four. This branch's tree is identical to dev's. After merge, the guest image is published from this prod commit. Host agent 0.2.18 then makes hosts pull it.
This commit is contained in:
@@ -78,6 +78,10 @@ export namespace BillingApi {
|
||||
});
|
||||
|
||||
const delivery = Polar.receive({ body, headers });
|
||||
const ids = {
|
||||
subscription: delivery.subscriptionId,
|
||||
customer: delivery.customerId
|
||||
};
|
||||
|
||||
// Acknowledged rather than refused. A delivery we cannot act on is
|
||||
// still a delivery that arrived intact, and answering an error
|
||||
@@ -85,10 +89,28 @@ export namespace BillingApi {
|
||||
// never become actionable.
|
||||
if (!delivery.teamId || !delivery.standing) {
|
||||
// Said, because a paid subscription that matches no team is
|
||||
// somebody paying for nothing, and a 200 alone hides it.
|
||||
// somebody paying for nothing, and a 200 alone hides it. With
|
||||
// the provider's ids, so whoever reads it can find the customer
|
||||
// in the provider's dashboard and set the team's plan by hand:
|
||||
// a subscription made before checkouts carried the team id
|
||||
// arrives like this for as long as it lives.
|
||||
if (delivery.standing?.plan === 'paid') {
|
||||
console.error('a paid subscription matched no team', { type: delivery.type });
|
||||
console.error(
|
||||
'a paid subscription matched no team; find the customer in the provider dashboard and set the team plan by hand',
|
||||
{ type: delivery.type, team: delivery.teamId, ...ids }
|
||||
);
|
||||
}
|
||||
// Every delivery is said, applied or not. A webhook that
|
||||
// changes nothing looks exactly like one that worked from the
|
||||
// outside, and a cancellation on 2026-10-15 was only found to
|
||||
// have missed by reading the team's row.
|
||||
console.log('billing webhook', {
|
||||
type: delivery.type,
|
||||
team: delivery.teamId,
|
||||
standing: delivery.standing,
|
||||
applied: false,
|
||||
...ids
|
||||
});
|
||||
return c.json({ data: { applied: false, type: delivery.type } });
|
||||
}
|
||||
|
||||
@@ -97,6 +119,20 @@ export namespace BillingApi {
|
||||
plan: delivery.standing.plan,
|
||||
subscriptionStatus: delivery.standing.status
|
||||
});
|
||||
if (!updated) {
|
||||
console.error('a subscription names a team that does not exist', {
|
||||
type: delivery.type,
|
||||
team: delivery.teamId,
|
||||
...ids
|
||||
});
|
||||
}
|
||||
console.log('billing webhook', {
|
||||
type: delivery.type,
|
||||
team: delivery.teamId,
|
||||
standing: delivery.standing,
|
||||
applied: Boolean(updated),
|
||||
...ids
|
||||
});
|
||||
|
||||
return c.json({
|
||||
data: { applied: Boolean(updated), type: delivery.type }
|
||||
|
||||
@@ -396,11 +396,22 @@ RUN pacman -Syu --noconfirm --needed \
|
||||
pipewire pipewire-audio wireplumber opus \
|
||||
python libunwind sdl2-compat \
|
||||
python-xlib python-urllib3 python-pyzstd \
|
||||
libvpx speex libtheora libvdpau jsoncpp \
|
||||
strace lsof less \
|
||||
&& rm -f /usr/share/libalpm/hooks/dbus-reload.hook \
|
||||
&& pacman -Rdd --noconfirm systemd systemd-sysvcompat \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
# `libvpx`, `speex`, `libtheora`, `libvdpau` and `jsoncpp` are not ours at all:
|
||||
# they are what Proton's own GStreamer plugins link against, and Proton does
|
||||
# not carry them. Without them `libgstlibav.so` and `libgstspeex.so` fail to
|
||||
# load (`libvpx.so.12: cannot open shared object file`), so a game whose videos
|
||||
# go through Wine's media stack plays them as nothing. Measured 2026-10-15 by
|
||||
# listing every NEEDED library of the published Proton against this image;
|
||||
# these five were the whole gap, for the stock build and the patched one
|
||||
# alike. They track the sonames Proton was built against, so a Proton built
|
||||
# on a newer Arch can need this list rebuilt with it.
|
||||
#
|
||||
# `strace`, `lsof` and `less` are for the operator's shell (`neslet box exec`):
|
||||
# a root shell in a box that cannot trace a process, list what it has open or
|
||||
# page through a log is the situation that feature exists to get out of. They
|
||||
|
||||
@@ -235,6 +235,50 @@ describe('Both signing schemes', () => {
|
||||
expect(delivery.standing).toEqual({ plan: 'paid', status: 'active' });
|
||||
});
|
||||
|
||||
test('the provider\u2019s ids travel with a delivery, in either spelling', async () => {
|
||||
// They are what a person looks the customer up by when a delivery
|
||||
// matched no team, as a subscription made before checkouts carried the
|
||||
// team id does for as long as it lives.
|
||||
const { Webhook } = await import('standardwebhooks');
|
||||
const secret = `whsec_${Buffer.from('d'.repeat(32)).toString('base64')}`;
|
||||
configure({ POLAR_WEBHOOK_SECRET: secret });
|
||||
|
||||
const send = (payload: object, id: string) => {
|
||||
const body = JSON.stringify(payload);
|
||||
const timestamp = new Date();
|
||||
return Polar.receive({
|
||||
body,
|
||||
headers: {
|
||||
'webhook-id': id,
|
||||
'webhook-timestamp': Math.floor(timestamp.getTime() / 1000).toString(),
|
||||
'webhook-signature': new Webhook(secret).sign(id, timestamp, body)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const unmatched = send(
|
||||
{
|
||||
type: 'subscription.canceled',
|
||||
data: { id: 'sub_1', customer_id: 'cus_1', customer: { external_id: null }, product_id: PAID }
|
||||
},
|
||||
'msg_ids_snake'
|
||||
);
|
||||
expect(unmatched.teamId).toBeNull();
|
||||
expect(unmatched.standing).toEqual({ plan: 'paid', status: 'canceled' });
|
||||
expect(unmatched.subscriptionId).toBe('sub_1');
|
||||
expect(unmatched.customerId).toBe('cus_1');
|
||||
|
||||
// Not a subscription event: its own id is the order's, and the
|
||||
// subscription is a field on it.
|
||||
const order = send(
|
||||
{ type: 'order.refunded', data: { id: 'ord_1', subscriptionId: 'sub_2', customer: { id: 'cus_2' } } },
|
||||
'msg_ids_order'
|
||||
);
|
||||
expect(order.subscriptionId).toBe('sub_2');
|
||||
expect(order.customerId).toBe('cus_2');
|
||||
expect(order.standing).toBeNull();
|
||||
});
|
||||
|
||||
test('a tampered body under a valid-looking signature is refused', () => {
|
||||
const secret = `whsec_${Buffer.from('a'.repeat(32)).toString('base64')}`;
|
||||
configure({ POLAR_WEBHOOK_SECRET: secret });
|
||||
|
||||
@@ -286,6 +286,14 @@ export namespace Polar {
|
||||
/** The team this is about, from the customer's external id. */
|
||||
teamId: string | null;
|
||||
standing: Standing | null;
|
||||
/**
|
||||
* The provider's own ids for the subscription and the customer. Nothing
|
||||
* here acts on them; they are what a person searches the provider's
|
||||
* dashboard for when a delivery matched no team and has to be applied by
|
||||
* hand.
|
||||
*/
|
||||
subscriptionId: string | null;
|
||||
customerId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,7 +380,24 @@ export namespace Polar {
|
||||
product?.id ??
|
||||
null;
|
||||
|
||||
return { type: event.type, teamId, standing: standingFor(event.type, productId) };
|
||||
// A subscription event's own id is the subscription's; anything else
|
||||
// about one carries it as a field.
|
||||
const str = (v: unknown) => (typeof v === 'string' ? v : null);
|
||||
const subscriptionId = event.type.startsWith('subscription.')
|
||||
? str(data.id)
|
||||
: (str(data.subscriptionId) ?? str(data.subscription_id));
|
||||
const customerId =
|
||||
str(data.customerId) ??
|
||||
str(data.customer_id) ??
|
||||
str((data.customer as { id?: unknown } | undefined)?.id);
|
||||
|
||||
return {
|
||||
type: event.type,
|
||||
teamId,
|
||||
standing: standingFor(event.type, productId),
|
||||
subscriptionId,
|
||||
customerId
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user