@homie-rocks/studio 0.39.0 → 0.42.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 +55 -1
- package/README.md +11 -0
- package/bin/homie-studio.mjs +8 -0
- package/functions/FUNCTIONS.md +99 -0
- package/functions/functions.ts +13 -0
- package/lib/build.mjs +2 -0
- package/lib/cloudflare.mjs +9 -9
- package/lib/dev.mjs +2 -1
- package/lib/functions-build.mjs +36 -0
- package/lib/functions-cli.mjs +23 -0
- package/lib/port-check.mjs +6 -3
- package/lib/purchase-links.mjs +8 -0
- package/lib/rules-build.mjs +8 -1
- package/lib/rules-check-worker.mjs +3 -3
- package/lib/rules-check.mjs +3 -1
- package/lib/scaffold.mjs +16 -3
- package/lib/shop.mjs +1 -1
- package/lib/template-history.json +85 -0
- package/lib/tools-build.mjs +2 -0
- package/lib/upgrade.mjs +2 -0
- package/lib/worker-build.mjs +8 -20
- package/lib/worker-config.mjs +28 -0
- package/netplay/NETPLAY.md +74 -2
- package/package.json +7 -2
- package/rules/collision.ts +115 -0
- package/rules/core.ts +65 -25
- package/rules/host.ts +5 -1
- package/rules/math.ts +15 -5
- package/rules/rules.ts +33 -10
- package/rules/types.ts +2 -2
- package/rules/view.ts +126 -32
- package/shop/SHOP.md +4 -3
- package/starters/ember-vale/game.json +16 -4
- package/starters/ember-vale/map/main.json +66 -0
- package/starters/ember-vale/src/move.ts +31 -0
- package/starters/ember-vale/src/rules.ts +244 -0
- package/starters/ember-vale/src/{main.ts → view.ts} +89 -574
- package/starters/ember-vale/tunables.json +100 -11
- package/starters/gem-rush/src/move.ts +9 -1
- package/starters/gem-rush/src/view.ts +2 -1
- package/starters/gem-rush-3d/game.json +23 -5
- package/starters/gem-rush-3d/map/main.json +153 -0
- package/starters/gem-rush-3d/src/move.ts +37 -0
- package/starters/gem-rush-3d/src/rules.ts +133 -0
- package/starters/gem-rush-3d/src/{main.ts → view.ts} +63 -671
- package/starters/gem-rush-3d/tunables.json +98 -11
- package/starters/hero-rush-3d/game.json +23 -5
- package/starters/hero-rush-3d/map/main.json +153 -0
- package/starters/hero-rush-3d/src/move.ts +49 -0
- package/starters/hero-rush-3d/src/rules.ts +200 -0
- package/starters/hero-rush-3d/src/{main.ts → view.ts} +83 -976
- package/starters/hero-rush-3d/tunables.json +260 -30
- package/tools/TOOLS.md +42 -2
- package/tools/tools.ts +3 -0
- package/worker/customer-mcp.mjs +77 -0
- package/worker/customer-resources.mjs +53 -0
- package/worker/customer-shop.mjs +54 -0
- package/worker/extensions.mjs +1 -0
- package/worker/functions-schema.mjs +55 -0
- package/worker/functions.mjs +93 -0
- package/worker/index.mjs +37 -2
- package/worker/machine-payments.mjs +1 -1
- package/worker/mcp-store.mjs +3 -2
- package/worker/mcp-tools.mjs +16 -4
- package/worker/mcp.mjs +31 -15
- package/worker/office-page.mjs +1 -1
- package/worker/players.mjs +5 -2
- package/worker/purchase-delivery.mjs +1 -0
- package/worker/purchase-hosted-readiness.mjs +0 -1
- package/worker/purchase-links.mjs +1 -1
- package/worker/purchase-machine.mjs +1 -0
- package/worker/purchase-office.mjs +1 -1
- package/worker/purchase-owner.mjs +0 -2
- package/worker/purchase-reconciliation.mjs +1 -0
- package/worker/purchase-routes.mjs +11 -17
- package/worker/purchase-store.mjs +1 -0
- package/worker/selling/runtime.mjs +4 -1
- package/worker/service-run.mjs +25 -0
- package/worker/service-terms.mjs +2 -0
- package/worker/shop-rules.mjs +9 -8
- package/worker/shop.mjs +22 -9
- package/worker/tool-webhook.mjs +1 -0
- package/worker/version.mjs +1 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Shared immutable terms for priced tools and their Stripe Payment Links. */
|
|
2
|
+
export const serviceTerms = (price) => ({billing:'one-time',scope:'service',updates:'none',source:false,commercialUse:true,transferable:false,onRefund:'terminate',onExpiry:'terminate',refund:'Ask the studio for a refund.',...price});
|
package/worker/shop-rules.mjs
CHANGED
|
@@ -128,14 +128,15 @@ export function checkShop(raw, { games = null, audience = 'general', studioName
|
|
|
128
128
|
const requestBytes = optionalNumber(raw.requestBytes, 'requestBytes');
|
|
129
129
|
const refundDays = optionalNumber(raw.refundDays, 'refundDays');
|
|
130
130
|
const cap = optionalNumber(raw.capPerPlayerMonth, 'capPerPlayerMonth');
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
const ratePolicy = key => {
|
|
132
|
+
if(raw[key] === undefined || raw[key] === null)return null;
|
|
133
|
+
const value=numeric(raw[key]);
|
|
134
|
+
if(!Number.isSafeInteger(value)||value<1)err(key,'use a positive whole number, or omit the rate policy');
|
|
135
|
+
return value;
|
|
136
|
+
};
|
|
137
|
+
const guestBuyersPerAddressPerHour = ratePolicy('guestBuyersPerAddressPerHour');
|
|
138
|
+
const purchaseAttemptsPerMinute = ratePolicy('purchaseAttemptsPerMinute');
|
|
139
|
+
const purchaseAttemptsPerAddressPerMinute = ratePolicy('purchaseAttemptsPerAddressPerMinute');
|
|
139
140
|
|
|
140
141
|
for (const key of ['automaticTax', 'referralNewPlayersOnly']) if (raw[key] !== undefined && typeof raw[key] !== 'boolean') err(key, 'use a boolean');
|
|
141
142
|
const items = [];
|
package/worker/shop.mjs
CHANGED
|
@@ -556,8 +556,8 @@ const WAY_WORDS = Object.freeze({
|
|
|
556
556
|
});
|
|
557
557
|
|
|
558
558
|
function purchaseLimited(request, shop, player, kind) {
|
|
559
|
-
const account = player && limited(`${kind}:account:${player}`, shop.purchaseAttemptsPerMinute, 60_000);
|
|
560
|
-
const address = limited(`${kind}:address:${addressOf(request)}`, shop.purchaseAttemptsPerAddressPerMinute, 60_000);
|
|
559
|
+
const account = player && shop.purchaseAttemptsPerMinute !== null && limited(`${kind}:account:${player}`, shop.purchaseAttemptsPerMinute, 60_000);
|
|
560
|
+
const address = shop.purchaseAttemptsPerAddressPerMinute !== null && limited(`${kind}:address:${addressOf(request)}`, shop.purchaseAttemptsPerAddressPerMinute, 60_000);
|
|
561
561
|
return account || address;
|
|
562
562
|
}
|
|
563
563
|
|
|
@@ -707,6 +707,18 @@ function refundableNow(order, owns, shop, now = Date.now()) {
|
|
|
707
707
|
return shop.policy.refundUsedItems || !owns.some((o) => o.item === order.item && o.used);
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
+
/** Shared authorization for web and claim refunds, using persisted grants and line snapshots. */
|
|
711
|
+
export async function refundableShopOrder(env, order, shop, lines = null) {
|
|
712
|
+
if (!order || !shop) return false;
|
|
713
|
+
lines ??= await orderLines(env, order.id);
|
|
714
|
+
if (!lines.length) return false;
|
|
715
|
+
for (const line of lines) {
|
|
716
|
+
const used = await lineWasUsed(env, order.id, line);
|
|
717
|
+
if (!refundableNow({ ...lineView(line), paidAt: order.paid_at }, used ? [{ item: line.item, used: true }] : [], shop)) return false;
|
|
718
|
+
}
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
|
|
710
722
|
/** A player's own refund follows the studio window and used-item setting, excluding tips. */
|
|
711
723
|
async function selfRefundRoute(request, env, shop, ready) {
|
|
712
724
|
const b = await bodyOf(request, requestSize(shop));
|
|
@@ -717,10 +729,7 @@ async function selfRefundRoute(request, env, shop, ready) {
|
|
|
717
729
|
if (!o || o.player !== p.id) return fail(404, 'order', 'no such order on this account');
|
|
718
730
|
const lines = (await orderLines(env, o.id)).filter((l) => !b.body.line || l.id === b.body.line);
|
|
719
731
|
if (!lines.length) return fail(404, 'line', 'no such line on this order');
|
|
720
|
-
|
|
721
|
-
const used = await lineWasUsed(env, o.id, line);
|
|
722
|
-
if (!refundableNow({ ...lineView(line), paidAt: o.paid_at }, used ? [{ item: line.item, used: true }] : [], shop)) return fail(403, 'not-refundable', 'Ask the studio for this refund. Tips are not refundable by the player; items follow the studio refund settings.');
|
|
723
|
-
}
|
|
732
|
+
if (!await refundableShopOrder(env, o, shop, lines)) return fail(403, 'not-refundable', 'Ask the studio for this refund. Tips are not refundable by the player; items follow the studio refund settings.');
|
|
724
733
|
if (Number(o.amount) !== 0 && !hasStripeKey(env)) return fail(409, 'ask-studio', 'Ask the studio to refund this payment in Stripe.');
|
|
725
734
|
const r = await refundOrder(env, o, { reason: 'requested_by_customer', by: 'player', line: b.body.line });
|
|
726
735
|
if (r.held) return json({ ...r, error: r.error === 'pending' ? 'pending' : 'held', message: r.error === 'pending' ? r.message : 'The studio approves this refund in Stripe first. Your money comes back once they do, and the item leaves your account then.' }, 202);
|
|
@@ -743,7 +752,11 @@ async function hook(request, env, cat, shop, ready) {
|
|
|
743
752
|
if (selling.enabled) {
|
|
744
753
|
if (typeof ev.livemode === 'boolean' && ev.livemode !== (shopMode(env) === 'live')) return json({ ok: true, ignored: 'mode' });
|
|
745
754
|
const purchase = await selling.purchasePaymentEvent(env, ev);
|
|
746
|
-
if (purchase !== null) {
|
|
755
|
+
if (purchase !== null) {
|
|
756
|
+
// Resource carts share the web shop's grants and refund bookkeeping.
|
|
757
|
+
const payment = typeof obj.payment_intent === 'string' ? obj.payment_intent : obj.payment_intent?.id;
|
|
758
|
+
if (payment) await syncRefunds(env, payment);
|
|
759
|
+
await env.DB.prepare('INSERT INTO shop_events (id, type, at) VALUES (?1, ?2, ?3) ON CONFLICT(id) DO NOTHING').bind(ev.id, ev.type, Date.now()).run(); return json({ ok: true, did: purchase }); }
|
|
747
760
|
}
|
|
748
761
|
if (!needed.includes(ev.type)) return json({ ok: true, did: 'ignored' });
|
|
749
762
|
if (typeof ev.livemode === 'boolean' && (hasStripeKey(env) || linkConfig(env)) && ev.livemode !== (shopMode(env) === 'live')) return json({ ok: true, ignored: 'mode' });
|
|
@@ -864,7 +877,7 @@ async function bindLinkSession(env, session, mode) {
|
|
|
864
877
|
}
|
|
865
878
|
|
|
866
879
|
/** A paid checkout: the order is paid, its entitlements granted, a referral line written. Idempotent. */
|
|
867
|
-
async function paid(env, shop, session, paidAt = Date.now()) {
|
|
880
|
+
export async function paid(env, shop, session, paidAt = Date.now()) {
|
|
868
881
|
const o = await orderBySession(env, session.id);
|
|
869
882
|
if (!o) return 'unknown-order';
|
|
870
883
|
// The session must be the one this order opened, for this player and this price.
|
|
@@ -950,7 +963,7 @@ async function paid(env, shop, session, paidAt = Date.now()) {
|
|
|
950
963
|
* https://docs.stripe.com/api/refunds/object#refund_object-status
|
|
951
964
|
* https://docs.stripe.com/api/refunds/list
|
|
952
965
|
*/
|
|
953
|
-
async function syncRefunds(env, payment, freeOrder = null, freeLines = []) {
|
|
966
|
+
export async function syncRefunds(env, payment, freeOrder = null, freeLines = []) {
|
|
954
967
|
const o = freeOrder ?? await orderByPayment(env, payment);
|
|
955
968
|
if (!o) return 'unknown-order';
|
|
956
969
|
const all = await orderLines(env, o.id);
|
package/worker/tool-webhook.mjs
CHANGED
|
@@ -6,6 +6,7 @@ export async function toolWebhook(request,env,{cat,definitions}) {
|
|
|
6
6
|
const name=new URL(request.url).pathname.split('/').pop(),tool=definitions.find(t=>t.name===name&&t.webhook);
|
|
7
7
|
const fail=(status)=>Response.json({ok:false},{status});
|
|
8
8
|
if(!tool||request.method!=='POST')return fail(404);
|
|
9
|
+
if(tool.price)return Response.json({ok:false,error:'Use /mcp to approve and pay for this tool'},{status:402});
|
|
9
10
|
const timestamp=request.headers.get('x-studio-timestamp')??'',delivery=request.headers.get('x-studio-delivery')??'',signature=request.headers.get('x-studio-signature')??'';
|
|
10
11
|
if(!/^\d{10}$/.test(timestamp)||Math.abs(Date.now()/1000-Number(timestamp))>300||!/^[a-zA-Z0-9_-]{8,100}$/.test(delivery)||!/^[a-f0-9]{64}$/.test(signature))return fail(401);
|
|
11
12
|
const secret=env[tool.webhook.secret];if(typeof secret!=='string'||secret.length<32)return fail(503);
|
package/worker/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The @homie-rocks/studio version this Worker code is (package.json's; a test keeps them equal). Cache-busts /_homie/site.js. */
|
|
2
|
-
export const STUDIO_VERSION_TAG = '0.
|
|
2
|
+
export const STUDIO_VERSION_TAG = '0.42.0';
|