-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlever.html
More file actions
401 lines (401 loc) · 90.7 KB
/
lever.html
File metadata and controls
401 lines (401 loc) · 90.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
<h1 id="lever-backend-documentation">Lever Backend Documentation</h1>
<h2 id="environments">Environments</h2>
<h3 id="production">Production</h3>
<ul>
<li><strong>URL:</strong> <code>https://lever.compfest.id</code></li>
<li><strong>Secret:</strong> — <em>(To be provided)</em></li>
</ul>
<h3 id="development">Development</h3>
<ul>
<li><strong>URL:</strong> <code>https://lever.compfest.id/dev</code></li>
<li><strong>Secret:</strong> <code>ua/HDyYrvb+lce90Z0fUUw==</code></li>
</ul>
<hr />
<h2 id="game-build-settings-unity">Game Build Settings (Unity)</h2>
<ul>
<li><strong>Platform:</strong> WebGL</li>
<li>
<strong>Player Settings:</strong>
<ul>
<li>Default Canvas Width: <code>310</code></li>
<li>Default Canvas Height: <code>570</code></li>
</ul>
</li>
</ul>
<hr />
<h2 id="request-headers">Request Headers</h2>
<p>
These headers are required for authenticated requests to the backend endpoints
(<code>/v1/token</code>, <code>/v1/point</code>).
</p>
<h3 id="x-signature"><code>X-Signature</code></h3>
<ul>
<li>
<p>
<strong>Description:</strong> Signature to protect requests between the
game and backend. Generated using HMACSHA512 based on the request body,
timestamp, endpoint path, and the environment's secret key.
</p>
</li>
<li>
<p><strong>Generation Logic:</strong></p>
<ol>
<li>Compute the SHA256 hash of the raw JSON request body bytes.</li>
<li>Convert the hash to a lowercase hexadecimal string.</li>
<li>
Create the signature base string:
<code>"{HTTP_METHOD}:{ENDPOINT_PATH}:{HEX_HASH}:{TIMESTAMP}"</code>.
<ul>
<li>
Example for <code>/v1/token</code>:
<code
>"POST:/v1/token:61e17ea10eb9b4632702e993b0a7407776d5266b12876b7337172ab06d68d808:2025-05-05T11:58:58.182Z"</code
>
</li>
<li>
Example for <code>/v1/point</code>:
<code
>"POST:/v1/point:5cf81188063b454b640cbc5486ddacb62d9536211e7859351e3c1ff55e31622a:2025-05-05T11:49:37.832Z"</code
>
</li>
</ul>
</li>
<li>
Compute the HMACSHA512 hash of the signature base string using the
environment's <strong>Secret</strong> key (UTF8 encoded).
</li>
<li>
Encode the resulting HMAC hash as a Base64 string. This is the value for
the <code>X-Signature</code> header.
</li>
</ol>
</li>
<li>
<p><strong>Code Examples:</strong></p>
<ul>
<li>
<p><strong>C# / Unity C# / Godot C#</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System.Security.Cryptography;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System.Text;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Ensure you have a variable 'SignatureSecret' holding the correct environment secret.</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// e.g., private const string SignatureSecret = "ua/HDyYrvb+lce90Z0fUUw=="; // For Development</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public static string GenerateSignature(byte[] body, string timestamp, string endpointPath /* e.g., "/v1/token" or "/v1/point" */)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> byte[] hashBody;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> using (SHA256 sha256 = SHA256.Create())</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> hashBody = sha256.ComputeHash(body);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Ensure BitConverter output is consistently lower-case hex</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string hexHash = BitConverter.ToString(hashBody).Replace("-", "").ToLowerInvariant();</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string signatureBase = $"POST:{endpointPath}:{hexHash}:{timestamp}";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> using (var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(SignatureSecret)))</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> byte[] signatureBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(signatureBase));</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> return Convert.ToBase64String(signatureBytes);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div></pre></pre>
</li>
<li>
<p><strong>Javascript (Example for Postman)</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(92, 99, 112);">// Postman Pre-request Script Example</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">const</span><span> crypto </span><span style="color: rgb(97, 175, 239);">=</span><span> </span><span style="color: rgb(97, 175, 239);">require</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(152, 195, 121);">'crypto-js'</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(92, 99, 112);">// Set the secret key in Postman environment variables</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">const</span><span> secretKey </span><span style="color: rgb(97, 175, 239);">=</span><span> pm</span><span style="color: rgb(171, 178, 191);">.</span><span>environment</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">get</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(152, 195, 121);">"apiSecret"</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span> </span><span style="color: rgb(92, 99, 112);">// e.g., "ua/HDyYrvb+lce90Z0fUUw=="</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">const</span><span> timeStamp </span><span style="color: rgb(97, 175, 239);">=</span><span> </span><span style="color: rgb(198, 120, 221);">new</span><span> </span><span style="color: rgb(209, 154, 102);">Date</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">toISOString</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>pm</span><span style="color: rgb(171, 178, 191);">.</span><span>request</span><span style="color: rgb(171, 178, 191);">.</span><span>headers</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">upsert</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(171, 178, 191);">{</span><span> </span><span style="color: rgb(224, 108, 117);">key</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"X-Timestamp"</span><span style="color: rgb(171, 178, 191);">,</span><span> </span><span style="color: rgb(224, 108, 117);">value</span><span style="color: rgb(97, 175, 239);">:</span><span> timeStamp </span><span style="color: rgb(171, 178, 191);">}</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(92, 99, 112);">// Ensure body is raw JSON and properly stringified without extra whitespace</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">const</span><span> rawBody </span><span style="color: rgb(97, 175, 239);">=</span><span> pm</span><span style="color: rgb(171, 178, 191);">.</span><span>request</span><span style="color: rgb(171, 178, 191);">.</span><span>body</span><span> </span><span style="color: rgb(97, 175, 239);">?</span><span> pm</span><span style="color: rgb(171, 178, 191);">.</span><span>request</span><span style="color: rgb(171, 178, 191);">.</span><span>body</span><span style="color: rgb(171, 178, 191);">.</span><span>raw</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">trim</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(171, 178, 191);">)</span><span> </span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">""</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">let</span><span> bodyToHash </span><span style="color: rgb(97, 175, 239);">=</span><span> </span><span style="color: rgb(152, 195, 121);">""</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">if</span><span> </span><span style="color: rgb(171, 178, 191);">(</span><span>rawBody</span><span style="color: rgb(171, 178, 191);">)</span><span> </span><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(198, 120, 221);">try</span><span> </span><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(92, 99, 112);">// Parse and restringify to ensure canonical format if needed</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(198, 120, 221);">const</span><span> jsonBody </span><span style="color: rgb(97, 175, 239);">=</span><span> </span><span style="color: rgb(209, 154, 102);">JSON</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">parse</span><span style="color: rgb(171, 178, 191);">(</span><span>rawBody</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> bodyToHash </span><span style="color: rgb(97, 175, 239);">=</span><span> </span><span style="color: rgb(209, 154, 102);">JSON</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">stringify</span><span style="color: rgb(171, 178, 191);">(</span><span>jsonBody</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(171, 178, 191);">}</span><span> </span><span style="color: rgb(198, 120, 221);">catch</span><span> </span><span style="color: rgb(171, 178, 191);">(</span><span>e</span><span style="color: rgb(171, 178, 191);">)</span><span> </span><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> bodyToHash </span><span style="color: rgb(97, 175, 239);">=</span><span> rawBody</span><span style="color: rgb(171, 178, 191);">;</span><span> </span><span style="color: rgb(92, 99, 112);">// Fallback to rawBody if not JSON</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(171, 178, 191);">}</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span><span> </span><span style="color: rgb(198, 120, 221);">else</span><span> </span><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> bodyToHash </span><span style="color: rgb(97, 175, 239);">=</span><span> </span><span style="color: rgb(152, 195, 121);">""</span><span style="color: rgb(171, 178, 191);">;</span><span> </span><span style="color: rgb(92, 99, 112);">// Handle empty body</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(92, 99, 112);">// Calculate SHA256 hash of the body</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">const</span><span> hashedBody </span><span style="color: rgb(97, 175, 239);">=</span><span> crypto</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(209, 154, 102);">SHA256</span><span style="color: rgb(171, 178, 191);">(</span><span>bodyToHash</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">toString</span><span style="color: rgb(171, 178, 191);">(</span><span>crypto</span><span style="color: rgb(171, 178, 191);">.</span><span>enc</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(209, 154, 102);">Hex</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(92, 99, 112);">// Construct the signature base string</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(92, 99, 112);">// Ensure the path matches the endpoint exactly (e.g., "/v1/token")</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">const</span><span> endpointPath </span><span style="color: rgb(97, 175, 239);">=</span><span> pm</span><span style="color: rgb(171, 178, 191);">.</span><span>request</span><span style="color: rgb(171, 178, 191);">.</span><span>url</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">getPath</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span> </span><span style="color: rgb(92, 99, 112);">// Adjust if necessary (e.g., ensure leading slash)</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">const</span><span> signatureString </span><span style="color: rgb(97, 175, 239);">=</span><span> </span><span style="color: rgb(152, 195, 121);">`</span><span style="color: rgb(171, 178, 191);">${</span><span>pm</span><span style="color: rgb(171, 178, 191);">.</span><span>request</span><span style="color: rgb(171, 178, 191);">.</span><span>method</span><span style="color: rgb(171, 178, 191);">}</span><span style="color: rgb(152, 195, 121);">:</span><span style="color: rgb(171, 178, 191);">${</span><span>endpointPath</span><span style="color: rgb(171, 178, 191);">}</span><span style="color: rgb(152, 195, 121);">:</span><span style="color: rgb(171, 178, 191);">${</span><span>hashedBody</span><span style="color: rgb(171, 178, 191);">}</span><span style="color: rgb(152, 195, 121);">:</span><span style="color: rgb(171, 178, 191);">${</span><span>timeStamp</span><span style="color: rgb(171, 178, 191);">}</span><span style="color: rgb(152, 195, 121);">`</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(209, 154, 102);">console</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">log</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(152, 195, 121);">"Signature Base:"</span><span style="color: rgb(171, 178, 191);">,</span><span> signatureString</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(92, 99, 112);">// Calculate HMACSHA512 signature</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(198, 120, 221);">const</span><span> signature </span><span style="color: rgb(97, 175, 239);">=</span><span> crypto</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(209, 154, 102);">HmacSHA512</span><span style="color: rgb(171, 178, 191);">(</span><span>signatureString</span><span style="color: rgb(171, 178, 191);">,</span><span> secretKey</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">toString</span><span style="color: rgb(171, 178, 191);">(</span><span>crypto</span><span style="color: rgb(171, 178, 191);">.</span><span>enc</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(209, 154, 102);">Base64</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(209, 154, 102);">console</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">log</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(152, 195, 121);">"Calculated Signature:"</span><span style="color: rgb(171, 178, 191);">,</span><span> signature</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(92, 99, 112);">// Add the signature header</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>pm</span><span style="color: rgb(171, 178, 191);">.</span><span>request</span><span style="color: rgb(171, 178, 191);">.</span><span>headers</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">upsert</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(171, 178, 191);">{</span><span> </span><span style="color: rgb(224, 108, 117);">key</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"X-Signature"</span><span style="color: rgb(171, 178, 191);">,</span><span> </span><span style="color: rgb(224, 108, 117);">value</span><span style="color: rgb(97, 175, 239);">:</span><span> signature </span><span style="color: rgb(171, 178, 191);">}</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span></div></pre></pre>
</li>
</ul>
</li>
</ul>
<h3 id="x-timestamp"><code>X-Timestamp</code></h3>
<ul>
<li>
<p>
<strong>Description:</strong> The UTC timestamp when the signature was
generated, in ISO 8601 format (<code>yyyy-MM-ddTHH:mm:ssZ</code>). This
<strong>must</strong> exactly match the timestamp used in the
<code>X-Signature</code> calculation for the request to be valid.
</p>
</li>
<li>
<p><strong>Code Examples:</strong></p>
<ul>
<li>
<p><strong>C# / Unity C# / Godot C#</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>string timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");</span></div></pre></pre>
</li>
<li>
<p><strong>Javascript</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(198, 120, 221);">const</span><span> timestamp </span><span style="color: rgb(97, 175, 239);">=</span><span> </span><span style="color: rgb(198, 120, 221);">new</span><span> </span><span style="color: rgb(209, 154, 102);">Date</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">.</span><span style="color: rgb(97, 175, 239);">toISOString</span><span style="color: rgb(171, 178, 191);">(</span><span style="color: rgb(171, 178, 191);">)</span><span style="color: rgb(171, 178, 191);">;</span></div></pre></pre>
</li>
</ul>
</li>
</ul>
<hr />
<h2 id="api-endpoints">API Endpoints</h2>
<h3 id="post-auth---authenticate-user">
<code>POST /v1/token</code> - Authenticate User
</h3>
<p>
Authenticates a user and arcade session using a playground token and an arcade
identifier.
</p>
<ul>
<li>
<p><strong>Headers:</strong></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Content</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>X-Signature</code></td>
<td><em>Generated Signature</em></td>
<td>Signature generated for this request (see above)</td>
</tr>
<tr>
<td><code>X-Timestamp</code></td>
<td><em>Current Timestamp</em></td>
<td>Timestamp used for the signature (ISO 8601 UTC)</td>
</tr>
<tr>
<td><code>Content-Type</code></td>
<td><code>application/json</code></td>
<td>Specifies request body format</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>
<strong>Request Body DTO (<code>ValidateCodeRequestDto</code>):</strong>
</p>
<ul>
<li>
<code>token</code> (string): The playground token provided to the user.
</li>
<li>
<code>arcadeCode</code> (string): Identifier for the specific
game/arcade machine (e.g., <code>WHACK_A_VIM</code>). This identifies
which game context the playground token is being used for.
</li>
</ul>
</li>
<li>
<p><strong>Request Body Example:</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"token"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"123456"</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"arcadeCode"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"WHACK_A_VIM"</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p><strong>Responses:</strong></p>
<ul>
<li>
<p>
<strong><code>200 OK</code></strong> (Success)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">200</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">true</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"Token validated successfully"</span><span style="color: rgb(171, 178, 191);">,</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"data"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"attemptId"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"38be4c72-cdea-4c53-b32d-a5c38e298175"</span><span style="color: rgb(171, 178, 191);">,</span><span> </span><span style="color: rgb(92, 99, 112);">// Unique identifier for this game session</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"username"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"test_user"</span><span> </span><span style="color: rgb(92, 99, 112);">// Username of the player; used for display purposes</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(171, 178, 191);">}</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
<p>
<em
>(Note: You will need the <code>attemptId</code> for the
<code>/v1/point</code> request.)</em
>
</p>
</li>
<li>
<p>
<strong><code>400 Bad Request</code></strong> (Maximum attempts
reached for the token, or code already used/invalid in context)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">400</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">false</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"Token usage limit reached"</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p>
<strong><code>400 Bad Request</code></strong> (Invalid Request Body /
Missing Fields)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">400</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">false</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"token must be a string; token should not be empty"</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p>
<strong><code>401 Unauthorized</code></strong> (Invalid Signature
Header)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">401</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">false</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"Invalid Signature"</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p>
<strong><code>401 Unauthorized</code></strong> (Invalid Timestamp
Header)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">401</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">false</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"Invalid Timestamp"</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
</ul>
</li>
<li>
<p><strong>Signature Code Specifics (C# Example):</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Use "/v1/token" as the endpointPath when calling GenerateSignature</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Ensure bodyRaw contains the JSON with both token and arcadeCode</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>string signature = GenerateSignature(bodyRaw, timestamp, "/v1/token");</span></div></pre></pre>
</li>
<li>
<p><strong>Request/Response Classes (C# - Unity/Godot):</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System.Collections.Generic; // For potential error details</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Request DTO</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>[Serializable]</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class ValidateCodeRequestDto</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public string token;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public string arcadeCode;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Success Response Structure (Root)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>[Serializable]</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class AuthSuccessResponse</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public int code;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public bool success;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public string message;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public AuthResponseData data;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Data part of the Success Response</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>[Serializable]</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class AuthResponseData</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public string attemptId;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public string username;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// General Error Response Structure (Example)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>[Serializable]</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class ErrorResponse</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public int code;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public bool success = false;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public string message;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div></pre></pre>
</li>
<li>
<p>
<strong>Request Example (Unity C# - <code>IEnumerator</code>):</strong>
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using UnityEngine;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using UnityEngine.Networking;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System.Collections;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System.Text;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using UnityEngine.SceneManagement;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using TMPro;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class GameAuthenticator : MonoBehaviour</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public TMP_Text textMessage;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public static string AttemptId { get; private set; } // Store the necessary attempt ID</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> private const string ApiUrl = "https://lever.compfest.id/dev";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> private const string SignatureSecret = "ua/HDyYrvb+lce90Z0fUUw=="; // For Development</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Call this with the user's code and the arcade's code</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public void StartAuthentication(string userCode, string arcadeIdentifier)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> StartCoroutine(AuthenticateUser(userCode, arcadeIdentifier));</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> IEnumerator AuthenticateUser(string userCode, string arcadeIdentifier)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (textMessage != null)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> textMessage.color = Color.black;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> textMessage.text = "Please wait... connecting to server";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string url = $"{ApiUrl}/v1/token";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Use the updated Request DTO</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> ValidateCodeRequestDto request = new() {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> token = userCode,</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> arcadeCode = arcadeIdentifier</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> };</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string requestBody = JsonUtility.ToJson(request);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> byte[] bodyRaw = Encoding.UTF8.GetBytes(requestBody);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> DateTime now = DateTime.UtcNow;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string timestamp = now.ToString("yyyy-MM-ddTHH:mm:ssZ");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Ensure GenerateSignature exists and uses the correct secret/logic</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string signature = GenerateSignature(bodyRaw, timestamp, "/v1/token");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> using (UnityWebRequest webRequest = new UnityWebRequest(url, "POST"))</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.uploadHandler = new UploadHandlerRaw(bodyRaw);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.downloadHandler = new DownloadHandlerBuffer();</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.SetRequestHeader("Content-Type", "application/json");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.SetRequestHeader("X-Timestamp", timestamp);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.SetRequestHeader("X-Signature", signature);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> yield return webRequest.SendWebRequest();</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (webRequest.result == UnityWebRequest.Result.Success)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Parse the standardized success response</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> AuthSuccessResponse response = JsonUtility.FromJson<AuthSuccessResponse>(webRequest.downloadHandler.text);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (response != null && response.success && response.data != null) {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (textMessage != null)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> textMessage.color = Color.black;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> textMessage.text = response.message; // Display success message</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Store credentials from the 'data' field</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> AttemptId = response.data.attemptId;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> Debug.Log($"Authentication successful for {response.data.username}.");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> SceneManager.LoadScene("GameScene"); // Load next scene</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> } else {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Handle cases where parsing succeeded but response structure was unexpected</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> HandleAuthError(webRequest, "Unexpected success response structure");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> else</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> HandleAuthError(webRequest);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Helper to handle errors consistently</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> void HandleAuthError(UnityWebRequest webRequest, string defaultMessage = null) {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string errorJson = webRequest.downloadHandler.text;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string errorMessage = defaultMessage ?? $"Error {webRequest.responseCode}";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> try {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Try parsing the standard error format</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> ErrorResponse error = JsonUtility.FromJson<ErrorResponse>(errorJson);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (error != null && !string.IsNullOrEmpty(error.message)) {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> errorMessage = error.message;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> } catch {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> Debug.LogError($"Raw Error Response: {errorJson}");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (textMessage != null)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> textMessage.color = Color.red;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> textMessage.text = errorMessage;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> Debug.LogError($"Authentication failed: {errorMessage}");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Include the static GenerateSignature function here (from Headers section)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public static string GenerateSignature(byte[] body, string timestamp, string endpointPath)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // ... (Implementation from Headers section) ...</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> byte[] hashBody;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> using (var sha256 = System.Security.Cryptography.SHA256.Create()) { hashBody = sha256.ComputeHash(body); }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string hexHash = BitConverter.ToString(hashBody).Replace("-", "").ToLowerInvariant();</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string signatureBase = $"POST:{endpointPath}:{hexHash}:{timestamp}";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> using (var hmac = new System.Security.Cryptography.HMACSHA512(Encoding.UTF8.GetBytes(SignatureSecret))) {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> byte[] signatureBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(signatureBase));</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> return Convert.ToBase64String(signatureBytes);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Include C# Class Definitions here (ValidateCodeRequestDto, AuthSuccessResponse, AuthResponseData, UserData, ErrorResponse)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// ...</span></div></pre></pre>
</li>
<li>
<p>
<strong>Godot C# Example:</strong> Adapt the Godot C# example similarly,
using <code>System.Text.Json</code> for potentially better JSON handling
(especially with nested objects and potential error structures), updating
the request DTO, removing <code>X-Gamecode</code>, and parsing the new
standardized success/error response formats.
</p>
</li>
</ul>
<hr />
<h3 id="post-point---submit-player-points">
<code>POST /v1/point</code> - Submit Player Points
</h3>
<p>
Submits the player's points for the authenticated session using the
<code>attemptId</code> obtained from <code>/v1/token</code>.
</p>
<ul>
<li>
<p><strong>Headers:</strong></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Content</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>X-Signature</code></td>
<td><em>Generated Signature</em></td>
<td>Signature generated for this request (see above)</td>
</tr>
<tr>
<td><code>X-Timestamp</code></td>
<td><em>Current Timestamp</em></td>
<td>Timestamp used for the signature (ISO 8601 UTC)</td>
</tr>
<tr>
<td><code>Content-Type</code></td>
<td><code>application/json</code></td>
<td>Specifies request body format</td>
</tr>
</tbody>
</table>
</li>
<li>
<p><strong>Request Body DTO (<code>SubmitPointDto</code>):</strong></p>
<ul>
<li>
<code>point</code> (integer): The number of points the player scored in
the session. <strong>Required.</strong>
</li>
<li>
<code>attemptId</code> (string): The ID obtained from the successful
<code>/v1/token</code> response (<code>data.attemptId</code>).
<strong>Required.</strong>
</li>
</ul>
</li>
<li>
<p><strong>Request Body Example:</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"point"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">100</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"attemptId"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"3569c962-8bf6-4ef6-806f-65eb4ab6ac9c"</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p><strong>Responses:</strong></p>
<ul>
<li>
<p>
<strong><code>200 OK</code></strong> (Success)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">200</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">true</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"Point submitted successfully"</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"data"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"addedPoint"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">100</span><span> </span><span style="color: rgb(92, 99, 112);">// Actual points recorded (could potentially differ from points sent based on backend logic/caps)</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(171, 178, 191);">}</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p>
<strong><code>400 Bad Request</code></strong> (Points already
submitted for this session)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">400</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">false</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"Whoops! Looks like you've already snagged your points for this session!"</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p>
<strong><code>400 Bad Request</code></strong> (Invalid Request Body /
Missing Fields)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">400</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">false</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"point must be an integer number"</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p>
<strong><code>401 Unauthorized</code></strong> (Invalid Signature
Header)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">401</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">false</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"Invalid Signature"</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
<li>
<p>
<strong><code>401 Unauthorized</code></strong> (Invalid Timestamp
Header)
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="color: rgb(171, 178, 191);">{</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"code"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">401</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"success"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(209, 154, 102);">false</span><span style="color: rgb(171, 178, 191);">,</span><span></span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> </span><span style="color: rgb(224, 108, 117);">"message"</span><span style="color: rgb(97, 175, 239);">:</span><span> </span><span style="color: rgb(152, 195, 121);">"Invalid Timestamp"</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span></span><span style="color: rgb(171, 178, 191);">}</span></div></pre></pre>
</li>
</ul>
</li>
<li>
<p><strong>Signature Code Specifics (C# Example):</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Use "/v1/point" as the endpointPath when calling GenerateSignature</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Ensure bodyRaw contains the JSON with attemptId and point</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>string signature = GenerateSignature(bodyRaw, timestamp, "/v1/point");</span></div></pre></pre>
</li>
<li>
<p><strong>Request/Response Classes (C# - Unity/Godot):</strong></p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System.Collections.Generic;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Request DTO</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>[Serializable]</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class SubmitPointDto</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public int point;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public string attemptId;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Success Response Structure (Root)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>[Serializable]</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class PointSuccessResponse</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public int code;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public bool success;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public string message;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public PointResponseData data;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Data part of the Success Response</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>[Serializable]</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class PointResponseData</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public int addedPoint;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Can reuse the general ErrorResponse class from /auth section for error handling</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// [Serializable] public class ErrorResponse { ... }</span></div></pre></pre>
</li>
<li>
<p>
<strong
>Request Example (Unity C# - <code>IEnumerator</code> -
Updated):</strong
>
</p>
<pre><pre><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using UnityEngine;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using UnityEngine.Networking;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System.Collections;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System.Text;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using System;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using TMPro;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>using UnityEngine.UI;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>public class PointSubmitter : MonoBehaviour</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>{</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public TMP_Text scoreMessage;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public Button retryButton;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> private const string ApiUrl = "https://lever.compfest.id/dev";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> private const string SignatureSecret = "ua/HDyYrvb+lce90Z0fUUw=="; // For Development</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Call this when the game ends, passing the final points</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> public void SubmitPlayerPoints(int finalPoints)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Get the attempt ID (assuming stored statically in GameAuthenticator)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string currentAttemptId = GameAuthenticator.AttemptId;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (string.IsNullOrEmpty(currentAttemptId))</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> Debug.LogError("Cannot submit points: attemptId is missing. Was authentication successful?");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (scoreMessage != null) {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> scoreMessage.color = Color.red;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> scoreMessage.text = "Error: Not authenticated.";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (retryButton != null) retryButton.gameObject.SetActive(true); // Show retry even if auth failed previously</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> return;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> StartCoroutine(SendPoints(currentAttemptId, finalPoints));</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> IEnumerator SendPoints(int point, string attemptId)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (scoreMessage != null)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> scoreMessage.color = Color.black;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> scoreMessage.text = "Please wait... submitting points";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (retryButton != null) retryButton.gameObject.SetActive(false);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string url = $"{ApiUrl}/v1/point";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Use the updated Request DTO</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> SubmitPointDto request = new()</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> point = point,</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> attemptId = attemptId</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> };</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string requestBody = JsonUtility.ToJson(request);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> byte[] bodyRaw = Encoding.UTF8.GetBytes(requestBody);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> DateTime now = DateTime.UtcNow;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string timestamp = now.ToString("yyyy-MM-ddTHH:mm:ssZ");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Ensure GenerateSignature exists and uses the correct secret/logic</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string signature = GameAuthenticator.GenerateSignature(bodyRaw, timestamp, "/v1/point"); // Assuming GenerateSignature is accessible</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> using (UnityWebRequest webRequest = new UnityWebRequest(url, "POST"))</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.uploadHandler = new UploadHandlerRaw(bodyRaw);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.downloadHandler = new DownloadHandlerBuffer();</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.SetRequestHeader("Content-Type", "application/json");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.SetRequestHeader("X-Timestamp", timestamp);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> webRequest.SetRequestHeader("X-Signature", signature);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> yield return webRequest.SendWebRequest();</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (webRequest.result == UnityWebRequest.Result.Success)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Parse the standardized success response</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> PointSuccessResponse response = JsonUtility.FromJson<PointSuccessResponse>(webRequest.downloadHandler.text);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if(response != null && response.success && response.data != null) {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (scoreMessage != null)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> scoreMessage.color = Color.black;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> scoreMessage.text = $"{response.message}. You gained {response.data.addedPoint} point(s)!";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> Debug.Log($"Points submitted successfully. Points added: {response.data.addedPoint}");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Invalidate local state if necessary (e.g., prevent resubmit button press)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Since attemptId is gone, you might disable the submit button or rely on server rejection</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> } else {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> HandlePointError(webRequest, "Unexpected success response structure");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> else</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> HandlePointError(webRequest);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Always show retry button after attempt (success or fail)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (retryButton != null) retryButton.gameObject.SetActive(true);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Helper to handle errors consistently</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> void HandlePointError(UnityWebRequest webRequest, string defaultMessage = null) {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string errorJson = webRequest.downloadHandler.text;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> string errorMessage = defaultMessage ?? $"Error {webRequest.responseCode}";</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> try {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Try parsing the standard error format (reuse ErrorResponse class)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> ErrorResponse error = JsonUtility.FromJson<ErrorResponse>(errorJson);</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (error != null && !string.IsNullOrEmpty(error.message)) {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> errorMessage = error.message;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> } catch {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> Debug.LogError($"Raw Error Response: {errorJson}");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> if (scoreMessage != null)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> {</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> scoreMessage.color = Color.red;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> scoreMessage.text = errorMessage;</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> Debug.LogError($"Point submission failed: {errorMessage}");</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> }</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span> // Note: Assumes GameAuthenticator class with GenerateSignature and stored AttemptId exists.</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>}</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span style="display: inline-block;">
</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// Include C# Class Definitions here (SubmitPointDto, PointSuccessResponse, PointResponseData, ErrorResponse)</span></div><div style="color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px;"><span>// ...</span></div></pre></pre>
</li>
</ul>