horizon-mcp 0.0.0-development
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/debug-connection.d.ts +3 -0
- package/dist/prompts/debug-connection.d.ts.map +1 -0
- package/dist/prompts/debug-connection.js +17 -0
- package/dist/prompts/debug-connection.js.map +1 -0
- package/dist/prompts/explain-feature.d.ts +3 -0
- package/dist/prompts/explain-feature.d.ts.map +1 -0
- package/dist/prompts/explain-feature.js +32 -0
- package/dist/prompts/explain-feature.js.map +1 -0
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +14 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/integrate-feature.d.ts +3 -0
- package/dist/prompts/integrate-feature.d.ts.map +1 -0
- package/dist/prompts/integrate-feature.js +35 -0
- package/dist/prompts/integrate-feature.js.map +1 -0
- package/dist/prompts/setup-auth.d.ts +3 -0
- package/dist/prompts/setup-auth.d.ts.map +1 -0
- package/dist/prompts/setup-auth.js +26 -0
- package/dist/prompts/setup-auth.js.map +1 -0
- package/dist/resources/index.d.ts +6 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +208 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/__tests__/api-client.test.d.ts +2 -0
- package/dist/tools/__tests__/api-client.test.d.ts.map +1 -0
- package/dist/tools/__tests__/api-client.test.js +156 -0
- package/dist/tools/__tests__/api-client.test.js.map +1 -0
- package/dist/tools/api-client.d.ts +25 -0
- package/dist/tools/api-client.d.ts.map +1 -0
- package/dist/tools/api-client.js +78 -0
- package/dist/tools/api-client.js.map +1 -0
- package/dist/tools/auth.d.ts +3 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +123 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/cloud-save.d.ts +3 -0
- package/dist/tools/cloud-save.d.ts.map +1 -0
- package/dist/tools/cloud-save.js +50 -0
- package/dist/tools/cloud-save.js.map +1 -0
- package/dist/tools/connection.d.ts +3 -0
- package/dist/tools/connection.d.ts.map +1 -0
- package/dist/tools/connection.js +20 -0
- package/dist/tools/connection.js.map +1 -0
- package/dist/tools/feedback.d.ts +3 -0
- package/dist/tools/feedback.d.ts.map +1 -0
- package/dist/tools/feedback.js +36 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/gift-codes.d.ts +3 -0
- package/dist/tools/gift-codes.d.ts.map +1 -0
- package/dist/tools/gift-codes.js +52 -0
- package/dist/tools/gift-codes.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/leaderboard.d.ts +3 -0
- package/dist/tools/leaderboard.d.ts.map +1 -0
- package/dist/tools/leaderboard.js +96 -0
- package/dist/tools/leaderboard.js.map +1 -0
- package/dist/tools/news.d.ts +3 -0
- package/dist/tools/news.d.ts.map +1 -0
- package/dist/tools/news.js +29 -0
- package/dist/tools/news.js.map +1 -0
- package/dist/tools/remote-config.d.ts +3 -0
- package/dist/tools/remote-config.d.ts.map +1 -0
- package/dist/tools/remote-config.js +42 -0
- package/dist/tools/remote-config.js.map +1 -0
- package/dist/tools/tool-helpers.d.ts +24 -0
- package/dist/tools/tool-helpers.d.ts.map +1 -0
- package/dist/tools/tool-helpers.js +54 -0
- package/dist/tools/tool-helpers.js.map +1 -0
- package/dist/tools/user-logs.d.ts +3 -0
- package/dist/tools/user-logs.d.ts.map +1 -0
- package/dist/tools/user-logs.js +30 -0
- package/dist/tools/user-logs.js.map +1 -0
- package/package.json +52 -0
- package/src/resources/api/app-api.md +495 -0
- package/src/resources/docs/auth.md +280 -0
- package/src/resources/docs/cloud-save.md +180 -0
- package/src/resources/docs/feedback.md +126 -0
- package/src/resources/docs/gift-codes.md +153 -0
- package/src/resources/docs/leaderboard.md +201 -0
- package/src/resources/docs/news.md +114 -0
- package/src/resources/docs/overview.md +80 -0
- package/src/resources/docs/remote-config.md +149 -0
- package/src/resources/docs/user-logs.md +144 -0
- package/src/resources/quickstart/godot.md +224 -0
- package/src/resources/quickstart/unity.md +317 -0
- package/src/resources/quickstart/unreal.md +390 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Authentication
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
horizOn supports three authentication methods:
|
|
6
|
+
|
|
7
|
+
| Method | Type Value | Description |
|
|
8
|
+
|--------|-----------|-------------|
|
|
9
|
+
| **Anonymous** | `ANONYMOUS` | Token-based, no credentials needed. Ideal for frictionless onboarding. |
|
|
10
|
+
| **Email** | `EMAIL` | Traditional email/password. Supports verification and password reset. |
|
|
11
|
+
| **Google** | `GOOGLE` | OAuth-based. Requires Google authorization code from OAuth flow. |
|
|
12
|
+
|
|
13
|
+
## Endpoints
|
|
14
|
+
|
|
15
|
+
### Sign Up
|
|
16
|
+
|
|
17
|
+
**`POST /api/v1/app/user-management/signup`**
|
|
18
|
+
|
|
19
|
+
Creates a new user account.
|
|
20
|
+
|
|
21
|
+
**Request Body:**
|
|
22
|
+
|
|
23
|
+
| Field | Type | Required | Description |
|
|
24
|
+
|-------|------|----------|-------------|
|
|
25
|
+
| `type` | string | Yes | `ANONYMOUS`, `EMAIL`, or `GOOGLE` |
|
|
26
|
+
| `username` | string | No | Display name (1-50 chars) |
|
|
27
|
+
| `email` | string | Email only | User's email address |
|
|
28
|
+
| `password` | string | Email only | Password (4-32 chars) |
|
|
29
|
+
| `anonymousToken` | string | Anonymous only | Unique token (max 32 chars). Auto-generated if omitted. |
|
|
30
|
+
| `googleAuthorizationCode` | string | Google only | OAuth authorization code |
|
|
31
|
+
| `googleRedirectUri` | string | No | Redirect URI used in OAuth flow |
|
|
32
|
+
|
|
33
|
+
**Response (200):**
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"userId": "abc123",
|
|
38
|
+
"username": "Player1",
|
|
39
|
+
"email": "user@example.com",
|
|
40
|
+
"isAnonymous": false,
|
|
41
|
+
"isVerified": true,
|
|
42
|
+
"anonymousToken": null,
|
|
43
|
+
"createdAt": "2025-01-15T10:30:00Z"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Error Codes:** `401` invalid API key, `409` user already exists.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Sign In
|
|
52
|
+
|
|
53
|
+
**`POST /api/v1/app/user-management/signin`**
|
|
54
|
+
|
|
55
|
+
Authenticates an existing user.
|
|
56
|
+
|
|
57
|
+
**Request Body:**
|
|
58
|
+
|
|
59
|
+
| Field | Type | Required | Description |
|
|
60
|
+
|-------|------|----------|-------------|
|
|
61
|
+
| `type` | string | Yes | `EMAIL`, `ANONYMOUS`, or `GOOGLE` |
|
|
62
|
+
| `email` | string | Email only | User's email |
|
|
63
|
+
| `password` | string | Email only | User's password |
|
|
64
|
+
| `anonymousToken` | string | Anonymous only | The token from signup |
|
|
65
|
+
| `googleAuthorizationCode` | string | Google only | OAuth code |
|
|
66
|
+
| `googleRedirectUri` | string | No | Redirect URI |
|
|
67
|
+
|
|
68
|
+
**Response (200):**
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"userId": "abc123",
|
|
73
|
+
"username": "Player1",
|
|
74
|
+
"email": "user@example.com",
|
|
75
|
+
"accessToken": "session-token-here",
|
|
76
|
+
"authStatus": "AUTHENTICATED",
|
|
77
|
+
"message": null
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`authStatus` values: `AUTHENTICATED` (success), `FAILED`, `NOT_VERIFIED`.
|
|
82
|
+
|
|
83
|
+
**Error Codes:** `401` invalid API key, `403` user not verified, `404` user not found.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### Check Auth
|
|
88
|
+
|
|
89
|
+
**`POST /api/v1/app/user-management/check-auth`**
|
|
90
|
+
|
|
91
|
+
Validates whether a session token is still active.
|
|
92
|
+
|
|
93
|
+
**Request Body:**
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"userId": "abc123",
|
|
98
|
+
"sessionToken": "session-token-here"
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Response (200):**
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"userId": "abc123",
|
|
107
|
+
"isAuthenticated": true,
|
|
108
|
+
"authStatus": "AUTHENTICATED",
|
|
109
|
+
"message": null
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### Change Name
|
|
116
|
+
|
|
117
|
+
**`POST /api/v1/app/user-management/change-name`**
|
|
118
|
+
|
|
119
|
+
Changes the display name of an authenticated user.
|
|
120
|
+
|
|
121
|
+
**Request Body:**
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"userId": "abc123",
|
|
126
|
+
"sessionToken": "session-token-here",
|
|
127
|
+
"newName": "NewDisplayName"
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Response (200):**
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"isAuthenticated": true,
|
|
136
|
+
"authStatus": "AUTHENTICATED"
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Verify Email
|
|
143
|
+
|
|
144
|
+
**`POST /api/v1/app/user-management/verify-email`**
|
|
145
|
+
|
|
146
|
+
Verifies a user's email address using a token sent via email.
|
|
147
|
+
|
|
148
|
+
**Request Body:**
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"token": "verification-token-from-email"
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
### Forgot Password
|
|
159
|
+
|
|
160
|
+
**`POST /api/v1/app/user-management/forgot-password`**
|
|
161
|
+
|
|
162
|
+
Sends a password reset email to the user.
|
|
163
|
+
|
|
164
|
+
**Request Body:**
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"email": "user@example.com"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Reset Password
|
|
175
|
+
|
|
176
|
+
**`POST /api/v1/app/user-management/reset-password`**
|
|
177
|
+
|
|
178
|
+
Resets the user's password using a reset token.
|
|
179
|
+
|
|
180
|
+
**Request Body:**
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"token": "reset-token-from-email",
|
|
185
|
+
"newPassword": "newSecurePassword"
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Code Examples
|
|
190
|
+
|
|
191
|
+
### Godot (GDScript)
|
|
192
|
+
|
|
193
|
+
```gdscript
|
|
194
|
+
# Anonymous sign-up
|
|
195
|
+
await Horizon.auth.signUpAnonymous("PlayerName")
|
|
196
|
+
|
|
197
|
+
# Email sign-up
|
|
198
|
+
await Horizon.auth.signUpEmail("user@example.com", "password", "Username")
|
|
199
|
+
|
|
200
|
+
# Email sign-in
|
|
201
|
+
await Horizon.auth.signInEmail("user@example.com", "password")
|
|
202
|
+
|
|
203
|
+
# Restore anonymous session from cached token
|
|
204
|
+
await Horizon.auth.restoreAnonymousSession()
|
|
205
|
+
|
|
206
|
+
# Quick anonymous sign-in (restores or creates new)
|
|
207
|
+
await Horizon.quickSignInAnonymous("Player1")
|
|
208
|
+
|
|
209
|
+
# Check if signed in
|
|
210
|
+
if Horizon.isSignedIn():
|
|
211
|
+
var user = Horizon.getCurrentUser()
|
|
212
|
+
print("Welcome, %s!" % user.displayName)
|
|
213
|
+
|
|
214
|
+
# Change display name
|
|
215
|
+
await Horizon.auth.changeName("NewName")
|
|
216
|
+
|
|
217
|
+
# Sign out (preserves anonymous token by default)
|
|
218
|
+
Horizon.auth.signOut()
|
|
219
|
+
|
|
220
|
+
# Listen for auth events
|
|
221
|
+
Horizon.auth.signin_completed.connect(func(user): print("Signed in: %s" % user.userId))
|
|
222
|
+
Horizon.auth.signin_failed.connect(func(error): print("Error: %s" % error))
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Unity (C#)
|
|
226
|
+
|
|
227
|
+
```csharp
|
|
228
|
+
using PM.horizOn.Cloud.Manager;
|
|
229
|
+
|
|
230
|
+
// Anonymous sign-up
|
|
231
|
+
await UserManager.Instance.SignUpAnonymous("PlayerName");
|
|
232
|
+
|
|
233
|
+
// Email sign-up
|
|
234
|
+
await UserManager.Instance.SignUpEmail("user@example.com", "password", "DisplayName");
|
|
235
|
+
|
|
236
|
+
// Email sign-in
|
|
237
|
+
await UserManager.Instance.SignInEmail("user@example.com", "password");
|
|
238
|
+
|
|
239
|
+
// Restore anonymous session
|
|
240
|
+
await UserManager.Instance.RestoreAnonymousSession();
|
|
241
|
+
|
|
242
|
+
// Check if signed in
|
|
243
|
+
if (UserManager.Instance.IsSignedIn)
|
|
244
|
+
{
|
|
245
|
+
var user = UserManager.Instance.CurrentUser;
|
|
246
|
+
Debug.Log($"Welcome, {user.DisplayName}!");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Change name
|
|
250
|
+
await UserManager.Instance.ChangeName("NewName");
|
|
251
|
+
|
|
252
|
+
// Sign out
|
|
253
|
+
UserManager.Instance.SignOut();
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### REST (cURL)
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Anonymous signup
|
|
260
|
+
curl -X POST https://horizon.pm/api/v1/app/user-management/signup \
|
|
261
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
262
|
+
-H "Content-Type: application/json" \
|
|
263
|
+
-d '{"type": "ANONYMOUS", "username": "Player1"}'
|
|
264
|
+
|
|
265
|
+
# Email signin
|
|
266
|
+
curl -X POST https://horizon.pm/api/v1/app/user-management/signin \
|
|
267
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
268
|
+
-H "Content-Type: application/json" \
|
|
269
|
+
-d '{"type": "EMAIL", "email": "user@example.com", "password": "pass123"}'
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Common Errors
|
|
273
|
+
|
|
274
|
+
| Status | Cause | Solution |
|
|
275
|
+
|--------|-------|----------|
|
|
276
|
+
| 401 | Invalid API key | Check `X-API-Key` header |
|
|
277
|
+
| 403 | User not verified | Complete email verification first |
|
|
278
|
+
| 404 | User not found | User may not exist, try signup |
|
|
279
|
+
| 409 | User already exists | Use signin instead of signup |
|
|
280
|
+
| 429 | Rate limit exceeded | Wait before retrying (10 req/min limit) |
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Cloud Save
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Cloud Save allows players to persist their game data across devices and sessions. horizOn supports two modes:
|
|
6
|
+
|
|
7
|
+
- **JSON mode** — Send structured data as a UTF-8 string via `application/json`
|
|
8
|
+
- **Binary mode** — Send raw bytes via `application/octet-stream`
|
|
9
|
+
|
|
10
|
+
## Tier Limits
|
|
11
|
+
|
|
12
|
+
Data size is limited by the account's pricing tier:
|
|
13
|
+
|
|
14
|
+
| Tier | Max Save Size |
|
|
15
|
+
|------|--------------|
|
|
16
|
+
| FREE | 1 KB |
|
|
17
|
+
| BASIC | 5 KB |
|
|
18
|
+
| PRO | 20 KB |
|
|
19
|
+
| ENTERPRISE | 250 KB |
|
|
20
|
+
|
|
21
|
+
If the save data exceeds the tier limit, the save request will fail with a `403` error.
|
|
22
|
+
|
|
23
|
+
## Endpoints
|
|
24
|
+
|
|
25
|
+
### Save Data
|
|
26
|
+
|
|
27
|
+
**`POST /api/v1/app/cloud-save/save`**
|
|
28
|
+
|
|
29
|
+
Saves user data to the cloud. Overwrites any existing save.
|
|
30
|
+
|
|
31
|
+
**Request Body (JSON mode):**
|
|
32
|
+
|
|
33
|
+
| Field | Type | Required | Description |
|
|
34
|
+
|-------|------|----------|-------------|
|
|
35
|
+
| `userId` | string | Yes | The user's ID |
|
|
36
|
+
| `saveData` | string | Yes | Data to save (JSON string) |
|
|
37
|
+
|
|
38
|
+
**Response (200):**
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"success": true,
|
|
43
|
+
"dataSizeBytes": 256
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Binary mode:** `POST /api/v1/app/cloud-save/save?userId={userId}` with `Content-Type: application/octet-stream` and raw bytes in the body.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Load Data
|
|
52
|
+
|
|
53
|
+
**`POST /api/v1/app/cloud-save/load`**
|
|
54
|
+
|
|
55
|
+
Loads the user's saved data from the cloud.
|
|
56
|
+
|
|
57
|
+
**Request Body (JSON mode):**
|
|
58
|
+
|
|
59
|
+
| Field | Type | Required | Description |
|
|
60
|
+
|-------|------|----------|-------------|
|
|
61
|
+
| `userId` | string | Yes | The user's ID |
|
|
62
|
+
|
|
63
|
+
**Response (200):**
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"found": true,
|
|
68
|
+
"saveData": "{\"level\":5,\"coins\":1000}"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If no save data exists:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"found": false,
|
|
77
|
+
"saveData": null
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Binary mode:** `GET /api/v1/app/cloud-save/load?userId={userId}` with `Accept: application/octet-stream`.
|
|
82
|
+
|
|
83
|
+
## Code Examples
|
|
84
|
+
|
|
85
|
+
### Godot (GDScript)
|
|
86
|
+
|
|
87
|
+
```gdscript
|
|
88
|
+
# Save a Dictionary (recommended approach)
|
|
89
|
+
await Horizon.cloudSave.saveObject({"level": 5, "coins": 1000, "inventory": ["sword", "shield"]})
|
|
90
|
+
|
|
91
|
+
# Load as Dictionary
|
|
92
|
+
var data: Dictionary = await Horizon.cloudSave.loadObject()
|
|
93
|
+
if not data.is_empty():
|
|
94
|
+
var level = data.get("level", 1)
|
|
95
|
+
var coins = data.get("coins", 0)
|
|
96
|
+
|
|
97
|
+
# Save raw JSON string
|
|
98
|
+
await Horizon.cloudSave.saveData('{"level": 5}')
|
|
99
|
+
|
|
100
|
+
# Load raw JSON string
|
|
101
|
+
var json: String = await Horizon.cloudSave.loadData()
|
|
102
|
+
|
|
103
|
+
# Binary data support
|
|
104
|
+
await Horizon.cloudSave.saveBytes(my_packed_byte_array)
|
|
105
|
+
var bytes: PackedByteArray = await Horizon.cloudSave.loadBytes()
|
|
106
|
+
|
|
107
|
+
# Save any Variant using Godot serialization
|
|
108
|
+
await Horizon.cloudSave.saveVariant(my_resource)
|
|
109
|
+
var loaded = await Horizon.cloudSave.loadVariant()
|
|
110
|
+
|
|
111
|
+
# Listen for events
|
|
112
|
+
Horizon.cloudSave.data_saved.connect(func(size): print("Saved %d bytes" % size))
|
|
113
|
+
Horizon.cloudSave.data_loaded.connect(func(data): print("Data loaded"))
|
|
114
|
+
Horizon.cloudSave.data_save_failed.connect(func(error): print("Save failed: %s" % error))
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Unity (C#)
|
|
118
|
+
|
|
119
|
+
```csharp
|
|
120
|
+
using PM.horizOn.Cloud.Manager;
|
|
121
|
+
|
|
122
|
+
// Save a typed object as JSON
|
|
123
|
+
[System.Serializable]
|
|
124
|
+
public class GameData
|
|
125
|
+
{
|
|
126
|
+
public int Level;
|
|
127
|
+
public int Coins;
|
|
128
|
+
public string[] Inventory;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var saveData = new GameData { Level = 5, Coins = 1000, Inventory = new[] { "sword", "shield" } };
|
|
132
|
+
await CloudSaveManager.Instance.SaveObject(saveData);
|
|
133
|
+
|
|
134
|
+
// Load a typed object
|
|
135
|
+
var loaded = await CloudSaveManager.Instance.LoadObject<GameData>();
|
|
136
|
+
if (loaded != null)
|
|
137
|
+
{
|
|
138
|
+
Debug.Log($"Level: {loaded.Level}, Coins: {loaded.Coins}");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Save/load raw string
|
|
142
|
+
await CloudSaveManager.Instance.Save("{\"level\": 5}");
|
|
143
|
+
string json = await CloudSaveManager.Instance.Load();
|
|
144
|
+
|
|
145
|
+
// Binary data support
|
|
146
|
+
byte[] data = GetBinaryData();
|
|
147
|
+
await CloudSaveManager.Instance.SaveBytes(data);
|
|
148
|
+
byte[] loadedBytes = await CloudSaveManager.Instance.LoadBytes();
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### REST (cURL)
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Save data (JSON mode)
|
|
155
|
+
curl -X POST https://horizon.pm/api/v1/app/cloud-save/save \
|
|
156
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
157
|
+
-H "Content-Type: application/json" \
|
|
158
|
+
-d '{"userId": "user123", "saveData": "{\"level\":5,\"coins\":1000}"}'
|
|
159
|
+
|
|
160
|
+
# Load data (JSON mode)
|
|
161
|
+
curl -X POST https://horizon.pm/api/v1/app/cloud-save/load \
|
|
162
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
163
|
+
-H "Content-Type: application/json" \
|
|
164
|
+
-d '{"userId": "user123"}'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Best Practices
|
|
168
|
+
|
|
169
|
+
- **Save on meaningful events** — Save when a level is completed, not on every frame or action.
|
|
170
|
+
- **Compress large saves** — If nearing the tier limit, consider compressing JSON data.
|
|
171
|
+
- **Handle "not found" gracefully** — On first load, `found` will be `false`. Initialize defaults.
|
|
172
|
+
- **Use typed objects** — Both SDKs support saving/loading typed objects directly.
|
|
173
|
+
|
|
174
|
+
## Common Errors
|
|
175
|
+
|
|
176
|
+
| Status | Cause | Solution |
|
|
177
|
+
|--------|-------|----------|
|
|
178
|
+
| 401 | Invalid API key | Check `X-API-Key` header |
|
|
179
|
+
| 403 | Save exceeds tier limit | Reduce data size or upgrade tier |
|
|
180
|
+
| 429 | Rate limit exceeded | Batch saves, save less frequently |
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# User Feedback
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The User Feedback feature lets players submit bug reports, feature requests, and general feedback directly from your app. All submissions are visible in the horizOn Dashboard. Optional device information can be attached to help with debugging.
|
|
6
|
+
|
|
7
|
+
## Endpoints
|
|
8
|
+
|
|
9
|
+
### Submit Feedback
|
|
10
|
+
|
|
11
|
+
**`POST /api/v1/app/user-feedback/submit`**
|
|
12
|
+
|
|
13
|
+
Submits a feedback entry.
|
|
14
|
+
|
|
15
|
+
**Request Body:**
|
|
16
|
+
|
|
17
|
+
| Field | Type | Required | Description |
|
|
18
|
+
|-------|------|----------|-------------|
|
|
19
|
+
| `title` | string | Yes | Feedback title (1-100 characters) |
|
|
20
|
+
| `message` | string | Yes | Feedback message (1-2048 characters) |
|
|
21
|
+
| `userId` | string | Yes | The user's ID |
|
|
22
|
+
| `email` | string | No | Contact email for follow-up |
|
|
23
|
+
| `category` | string | No | Category: `BUG`, `FEATURE`, or `GENERAL` |
|
|
24
|
+
| `deviceInfo` | string | No | Device information (max 500 characters) |
|
|
25
|
+
|
|
26
|
+
**Response (200):**
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
"ok"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Code Examples
|
|
33
|
+
|
|
34
|
+
### Godot (GDScript)
|
|
35
|
+
|
|
36
|
+
```gdscript
|
|
37
|
+
# Submit general feedback
|
|
38
|
+
await Horizon.feedback.submit("Great Game!", "I love the new features.", "GENERAL")
|
|
39
|
+
|
|
40
|
+
# Submit bug report with auto device info
|
|
41
|
+
await Horizon.feedback.submitBugReport("Crash on Level 5", "Game crashes when opening inventory")
|
|
42
|
+
|
|
43
|
+
# Submit feature request
|
|
44
|
+
await Horizon.feedback.submitFeatureRequest("Dark Mode", "Please add a dark mode option")
|
|
45
|
+
|
|
46
|
+
# Full submit with all options
|
|
47
|
+
await Horizon.feedback.submit(
|
|
48
|
+
"Bug Title",
|
|
49
|
+
"Detailed description of the bug...",
|
|
50
|
+
"BUG",
|
|
51
|
+
"user@example.com", # optional email
|
|
52
|
+
true # include device info
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Listen for events
|
|
56
|
+
Horizon.feedback.feedback_submitted.connect(func(): print("Feedback sent!"))
|
|
57
|
+
Horizon.feedback.feedback_submit_failed.connect(func(error): print("Failed: %s" % error))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Automatic device info includes:** OS, model, Godot version, screen resolution, renderer, and memory usage.
|
|
61
|
+
|
|
62
|
+
### Unity (C#)
|
|
63
|
+
|
|
64
|
+
```csharp
|
|
65
|
+
using PM.horizOn.Cloud.Manager;
|
|
66
|
+
|
|
67
|
+
// Bug report with auto device info
|
|
68
|
+
await FeedbackManager.Instance.ReportBug(
|
|
69
|
+
title: "Crash on level 5",
|
|
70
|
+
message: "Game crashes when opening inventory"
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Feature request
|
|
74
|
+
await FeedbackManager.Instance.RequestFeature(
|
|
75
|
+
title: "Dark mode",
|
|
76
|
+
message: "Please add dark mode option"
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// General feedback
|
|
80
|
+
await FeedbackManager.Instance.SendGeneral(
|
|
81
|
+
title: "Great game!",
|
|
82
|
+
message: "Really enjoying the new update"
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Full submit with all options
|
|
86
|
+
await FeedbackManager.Instance.Submit(
|
|
87
|
+
title: "Bug Title",
|
|
88
|
+
category: "BUG",
|
|
89
|
+
message: "Detailed description...",
|
|
90
|
+
email: "user@example.com",
|
|
91
|
+
includeDeviceInfo: true
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Automatic device info includes:** Unity version, OS, device model, and graphics device.
|
|
96
|
+
|
|
97
|
+
### REST (cURL)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
curl -X POST https://horizon.pm/api/v1/app/user-feedback/submit \
|
|
101
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
102
|
+
-H "Content-Type: application/json" \
|
|
103
|
+
-d '{
|
|
104
|
+
"title": "Bug Report",
|
|
105
|
+
"message": "Game crashes on level 5",
|
|
106
|
+
"userId": "user123",
|
|
107
|
+
"email": "user@example.com",
|
|
108
|
+
"category": "BUG",
|
|
109
|
+
"deviceInfo": "Windows 11 | RTX 3080 | 16GB RAM"
|
|
110
|
+
}'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Best Practices
|
|
114
|
+
|
|
115
|
+
- **Include device info for bug reports** — Both SDKs auto-collect device info; enable it for bug reports.
|
|
116
|
+
- **Use categories** — Categorize feedback as `BUG`, `FEATURE`, or `GENERAL` for easier triage in the Dashboard.
|
|
117
|
+
- **Add contact email** — Include the user's email if they want to receive follow-up responses.
|
|
118
|
+
- **Rate limit awareness** — Feedback submission counts toward the 10 req/min limit.
|
|
119
|
+
|
|
120
|
+
## Common Errors
|
|
121
|
+
|
|
122
|
+
| Status | Cause | Solution |
|
|
123
|
+
|--------|-------|----------|
|
|
124
|
+
| 400 | Missing required fields | Ensure `title`, `message`, and `userId` are provided |
|
|
125
|
+
| 401 | Invalid API key | Check `X-API-Key` header |
|
|
126
|
+
| 429 | Rate limit exceeded | Limit feedback submissions per session |
|