Skip to content

Commit a18493c

Browse files
authored
Merge pull request #3 from Daniel-Ric/version/2026-01-21/add-marketplace-message-system
Add inbox event support and alias inbox/start for Marketplace messaging
2 parents 669e2dc + 14cd71a commit a18493c

7 files changed

Lines changed: 452 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ curl -X POST http://localhost:3000/debug/decode-token -H "Authorization: Beare
246246
| GET | `/inventory/minecraft` | Minecraft entitlements (optional `includeReceipt`) | `x-mc-token` |
247247
| GET | `/inventory/minecraft/creators/top`| Top creators from entitlements (by item count) | `x-mc-token` |
248248
| GET | `/inventory/minecraft/search` | Search entitlements (`productId`, `q`, `limit`) | `x-mc-token` |
249+
| POST | `/messaging/inbox/start` | Marketplace inbox session (start/resume) | `x-mc-token` |
250+
| POST | `/messaging/inbox/event` | Mark seen/delete message events | `x-mc-token` |
249251
| POST | `/minecraft/token` | Create Minecraft multiplayer token from SessionTicket ||
250252

251253
### Debug & Health

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "index.js",
66
"type": "module",
77
"scripts": {
8-
"test": "echo \"Error: no test specified\" && exit 1"
8+
"test": "node --test"
99
},
1010
"repository": {
1111
"type": "git",

src/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import capturesRoutes from "./routes/captures.routes.js";
2727
import titlesRoutes from "./routes/titles.routes.js";
2828

2929
import wishlistRoutes from "./routes/wishlist.routes.js";
30+
import messagingRoutes from "./routes/messaging.routes.js";
3031

3132
import debugRoutes from "./routes/debug.routes.js";
3233

@@ -160,6 +161,7 @@ app.use("/achievements", achievementsRoutes);
160161
app.use("/stats", statsRoutes);
161162
app.use("/inventory", inventoryRoutes);
162163
app.use("/wishlist", wishlistRoutes);
164+
app.use("/messaging", messagingRoutes);
163165
app.use("/playfab", playfabRoutes);
164166
app.use("/minecraft", minecraftRoutes);
165167
if (env.NODE_ENV !== "production") app.use("/debug", debugRoutes);

src/routes/messaging.routes.js

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import express from "express";
2+
import Joi from "joi";
3+
4+
import {jwtMiddleware} from "../utils/jwt.js";
5+
import {asyncHandler} from "../utils/async.js";
6+
import {badRequest} from "../utils/httpError.js";
7+
import {sendMarketplaceMessageEvents, startMarketplaceMessagingSession} from "../services/minecraft.service.js";
8+
9+
const router = express.Router();
10+
11+
/**
12+
* @swagger
13+
* tags:
14+
* - name: Messaging
15+
* description: Minecraft Marketplace inbox and messaging APIs.
16+
*/
17+
18+
/**
19+
* @swagger
20+
* /messaging/inbox/start:
21+
* post:
22+
* summary: Start or resume a Marketplace inbox session
23+
* description: |
24+
* Starts a Marketplace messaging session and returns the inbox payload from
25+
* `messaging.mktpl.minecraft-services.net/api/v1.0/session/start`.
26+
*
27+
* **What you must provide**
28+
* - Header `x-mc-token`: the full Marketplace authorization header (starts with `MCToken ...`)
29+
*
30+
* **Optional inputs (body)**
31+
* - `continuationToken`: resume pagination
32+
* - `previousSessionId`: last session id for continuity
33+
* - `sessionContext`: optional context string
34+
* - `sessionId`: provide a fixed session id instead of auto-generated
35+
*
36+
* **Optional headers**
37+
* - `x-mc-session-id`: override the session-id header sent to Marketplace
38+
* tags: [Messaging]
39+
* security:
40+
* - BearerAuth: []
41+
* parameters:
42+
* - in: header
43+
* name: x-mc-token
44+
* required: true
45+
* schema:
46+
* type: string
47+
* description: Marketplace authorization header (must be the full value, e.g. `MCToken eyJ...`)
48+
* - in: header
49+
* name: x-mc-session-id
50+
* required: false
51+
* schema:
52+
* type: string
53+
* description: Optional session id header passed to the Marketplace messaging service
54+
* requestBody:
55+
* required: false
56+
* content:
57+
* application/json:
58+
* schema:
59+
* type: object
60+
* properties:
61+
* continuationToken:
62+
* type: string
63+
* previousSessionId:
64+
* type: string
65+
* sessionContext:
66+
* type: string
67+
* sessionId:
68+
* type: string
69+
* responses:
70+
* 200:
71+
* description: Marketplace messaging session payload
72+
* 400:
73+
* description: Bad request
74+
* content:
75+
* application/json:
76+
* schema:
77+
* $ref: '#/components/schemas/ErrorResponse'
78+
*/
79+
router.post(["/inbox/start", "/session/start"], jwtMiddleware, asyncHandler(async (req, res) => {
80+
const mcToken = req.headers["x-mc-token"];
81+
if (!mcToken) throw badRequest("Missing x-mc-token header");
82+
83+
const schema = Joi.object({
84+
continuationToken: Joi.string().optional(),
85+
previousSessionId: Joi.string().optional(),
86+
sessionContext: Joi.string().allow("").default(""),
87+
sessionId: Joi.string().optional()
88+
});
89+
90+
const {value, error} = schema.validate(req.body || {});
91+
if (error) throw badRequest(error.message);
92+
93+
const sessionHeaderId = req.headers["x-mc-session-id"] || req.headers["session-id"];
94+
95+
const result = await startMarketplaceMessagingSession(mcToken, {
96+
continuationToken: value.continuationToken,
97+
previousSessionId: value.previousSessionId,
98+
sessionContext: value.sessionContext,
99+
sessionId: value.sessionId,
100+
sessionHeaderId
101+
});
102+
103+
const headerSessionId = result.headers?.["session-id"] || result.sessionHeaderId;
104+
if (headerSessionId) res.setHeader("Session-Id", headerSessionId);
105+
106+
res.json(result.data);
107+
}));
108+
109+
/**
110+
* @swagger
111+
* /messaging/inbox/event:
112+
* post:
113+
* summary: Send inbox events (impression or delete)
114+
* description: |
115+
* Sends Marketplace inbox events to mark a message as seen (Impression) or delete it.
116+
* Proxies `messaging.mktpl.minecraft-services.net/api/v1.0/messages/event`.
117+
*
118+
* **What you must provide**
119+
* - Header `x-mc-token`: the full Marketplace authorization header (starts with `MCToken ...`)
120+
* - Body fields required for an event
121+
*
122+
* **Simplest body**
123+
* - `sessionId`
124+
* - `instanceId`
125+
* - `reportId`
126+
* - `eventType`: `Impression` or `Delete`
127+
*
128+
* **Advanced body**
129+
* - `events`: array of events (each with `instanceId`, `reportId`, `eventType`, optional `eventDateTime`, `sessionId`)
130+
* - `continuationToken`
131+
* - `sessionContext`
132+
*
133+
* **Optional headers**
134+
* - `x-mc-session-id`: override the session-id header sent to Marketplace
135+
* tags: [Messaging]
136+
* security:
137+
* - BearerAuth: []
138+
* parameters:
139+
* - in: header
140+
* name: x-mc-token
141+
* required: true
142+
* schema:
143+
* type: string
144+
* description: Marketplace authorization header (must be the full value, e.g. `MCToken eyJ...`)
145+
* - in: header
146+
* name: x-mc-session-id
147+
* required: false
148+
* schema:
149+
* type: string
150+
* description: Optional session id header passed to the Marketplace messaging service
151+
* requestBody:
152+
* required: true
153+
* content:
154+
* application/json:
155+
* schema:
156+
* type: object
157+
* properties:
158+
* sessionId:
159+
* type: string
160+
* sessionContext:
161+
* type: string
162+
* continuationToken:
163+
* type: string
164+
* eventType:
165+
* type: string
166+
* enum: [Impression, Delete]
167+
* instanceId:
168+
* type: string
169+
* reportId:
170+
* type: string
171+
* eventDateTime:
172+
* type: string
173+
* format: date-time
174+
* events:
175+
* type: array
176+
* items:
177+
* type: object
178+
* properties:
179+
* eventType:
180+
* type: string
181+
* instanceId:
182+
* type: string
183+
* reportId:
184+
* type: string
185+
* eventDateTime:
186+
* type: string
187+
* format: date-time
188+
* sessionId:
189+
* type: string
190+
* responses:
191+
* 200:
192+
* description: Marketplace messaging event response
193+
* 400:
194+
* description: Bad request
195+
* content:
196+
* application/json:
197+
* schema:
198+
* $ref: '#/components/schemas/ErrorResponse'
199+
*/
200+
router.post("/inbox/event", jwtMiddleware, asyncHandler(async (req, res) => {
201+
const mcToken = req.headers["x-mc-token"];
202+
if (!mcToken) throw badRequest("Missing x-mc-token header");
203+
204+
const schema = Joi.object({
205+
sessionId: Joi.string().optional(),
206+
sessionContext: Joi.string().allow("").default(""),
207+
continuationToken: Joi.string().optional(),
208+
eventType: Joi.string().valid("Impression", "Delete").optional(),
209+
instanceId: Joi.string().optional(),
210+
reportId: Joi.string().optional(),
211+
eventDateTime: Joi.string().optional(),
212+
events: Joi.array().items(Joi.object({
213+
eventType: Joi.string().valid("Impression", "Delete").required(),
214+
instanceId: Joi.string().required(),
215+
reportId: Joi.string().required(),
216+
eventDateTime: Joi.string().optional(),
217+
sessionId: Joi.string().optional()
218+
})).optional()
219+
});
220+
221+
const {value, error} = schema.validate(req.body || {});
222+
if (error) throw badRequest(error.message);
223+
224+
const sessionHeaderId = req.headers["x-mc-session-id"] || req.headers["session-id"];
225+
226+
const result = await sendMarketplaceMessageEvents(mcToken, {
227+
sessionId: value.sessionId,
228+
sessionContext: value.sessionContext,
229+
continuationToken: value.continuationToken,
230+
eventType: value.eventType,
231+
instanceId: value.instanceId,
232+
reportId: value.reportId,
233+
eventDateTime: value.eventDateTime,
234+
events: value.events,
235+
sessionHeaderId
236+
});
237+
238+
const headerSessionId = result.headers?.["session-id"] || result.sessionHeaderId;
239+
if (headerSessionId) res.setHeader("Session-Id", headerSessionId);
240+
241+
res.json(result.data);
242+
}));
243+
244+
export default router;

0 commit comments

Comments
 (0)