@xmobitea/gn-typescript-client 2.6.13 → 2.6.14
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/AGENTS.md +37 -0
- package/README.MD +420 -1
- package/dist/gearn.js.client.js +25056 -1056
- package/dist/gearn.js.client.min.js +1 -1
- package/dist/index.d.ts +30 -0
- package/dist/index.js +24236 -212
- package/dist/runtime/GNNetwork.d.ts +687 -3
- package/dist/runtime/GNNetworkAuthenticateApi.d.ts +327 -0
- package/dist/runtime/GNNetworkCharacterPlayerApi.d.ts +1026 -0
- package/dist/runtime/GNNetworkCloudScriptApi.d.ts +141 -0
- package/dist/runtime/GNNetworkContentApi.d.ts +243 -0
- package/dist/runtime/GNNetworkDashboardApi.d.ts +221 -0
- package/dist/runtime/GNNetworkGamePlayerApi.d.ts +1033 -0
- package/dist/runtime/GNNetworkGroupApi.d.ts +783 -0
- package/dist/runtime/GNNetworkInventoryApi.d.ts +673 -0
- package/dist/runtime/GNNetworkMasterPlayerApi.d.ts +1614 -6
- package/dist/runtime/GNNetworkMultiplayerApi.d.ts +234 -0
- package/dist/runtime/GNNetworkStoreInventoryApi.d.ts +309 -0
- package/dist/runtime/common/Action0.d.ts +30 -0
- package/dist/runtime/common/Action1.d.ts +32 -0
- package/dist/runtime/common/Action2.d.ts +21 -0
- package/dist/runtime/common/Action3.d.ts +16 -0
- package/dist/runtime/common/Action4.d.ts +17 -0
- package/dist/runtime/common/GNData.d.ts +368 -0
- package/dist/runtime/config/GNServerSettings.d.ts +442 -12
- package/dist/runtime/constant/Commands.d.ts +80 -0
- package/dist/runtime/constant/EventCode.d.ts +48 -0
- package/dist/runtime/constant/OperationCode.d.ts +74 -1
- package/dist/runtime/constant/ReturnCode.d.ts +72 -0
- package/dist/runtime/constant/enumType/ExecuteResponseStatus.d.ts +31 -0
- package/dist/runtime/constant/enumType/FriendStatus.d.ts +39 -0
- package/dist/runtime/constant/enumType/GoogleLoginType.d.ts +23 -0
- package/dist/runtime/constant/enumType/GroupStatus.d.ts +32 -0
- package/dist/runtime/constant/enumType/InvalidMemberType.d.ts +75 -0
- package/dist/runtime/constant/enumType/ItemType.d.ts +23 -0
- package/dist/runtime/constant/enumType/MatchStatus.d.ts +30 -0
- package/dist/runtime/constant/enumType/MatchmakingMemberStatus.d.ts +24 -0
- package/dist/runtime/constant/enumType/MatchmakingTicketStatus.d.ts +34 -0
- package/dist/runtime/constant/enumType/OwnerType.d.ts +40 -0
- package/dist/runtime/constant/enumType/PermissionDataItem.d.ts +22 -0
- package/dist/runtime/constant/enumType/PushPlatformType.d.ts +18 -0
- package/dist/runtime/constant/enumType/RequestRole.d.ts +32 -0
- package/dist/runtime/constant/enumType/RequestType.d.ts +70 -0
- package/dist/runtime/constant/enumType/StoreItemType.d.ts +21 -0
- package/dist/runtime/constant/enumType/StoreReceiveType.d.ts +30 -0
- package/dist/runtime/constant/errorCode/ErrorCode.d.ts +190 -8
- package/dist/runtime/constant/parameterCode/ParameterCode.d.ts +35 -5
- package/dist/runtime/entity/DataMember.d.ts +338 -0
- package/dist/runtime/entity/GNMetadata.d.ts +101 -0
- package/dist/runtime/entity/InvalidMember.d.ts +28 -0
- package/dist/runtime/entity/OperationEvent.d.ts +49 -0
- package/dist/runtime/entity/OperationRequest.d.ts +108 -0
- package/dist/runtime/entity/OperationResponse.d.ts +110 -0
- package/dist/runtime/entity/models/AuthenticateModels.d.ts +115 -0
- package/dist/runtime/entity/models/AuthenticateRequestModels.d.ts +131 -0
- package/dist/runtime/entity/models/AuthenticateResponseModels.d.ts +131 -0
- package/dist/runtime/entity/models/CharacterPlayerModels.d.ts +625 -1
- package/dist/runtime/entity/models/CharacterPlayerRequestModels.d.ts +972 -0
- package/dist/runtime/entity/models/CharacterPlayerResponseModels.d.ts +332 -0
- package/dist/runtime/entity/models/CloudScriptModels.d.ts +109 -0
- package/dist/runtime/entity/models/CloudScriptRequestModels.d.ts +107 -0
- package/dist/runtime/entity/models/CloudScriptResponseModels.d.ts +46 -0
- package/dist/runtime/entity/models/ContentModels.d.ts +124 -0
- package/dist/runtime/entity/models/ContentRequestModels.d.ts +152 -0
- package/dist/runtime/entity/models/ContentResponseModels.d.ts +58 -0
- package/dist/runtime/entity/models/DashboardModels.d.ts +371 -8
- package/dist/runtime/entity/models/DashboardRequestModels.d.ts +172 -0
- package/dist/runtime/entity/models/DashboardResponseModels.d.ts +170 -0
- package/dist/runtime/entity/models/GamePlayerModels.d.ts +644 -1
- package/dist/runtime/entity/models/GamePlayerRequestModels.d.ts +959 -0
- package/dist/runtime/entity/models/GamePlayerResponseModels.d.ts +333 -1
- package/dist/runtime/entity/models/GenericModels.d.ts +94 -0
- package/dist/runtime/entity/models/GroupModels.d.ts +484 -2
- package/dist/runtime/entity/models/GroupRequestModels.d.ts +737 -0
- package/dist/runtime/entity/models/GroupResponseModels.d.ts +254 -0
- package/dist/runtime/entity/models/InventoryModels.d.ts +415 -0
- package/dist/runtime/entity/models/InventoryRequestModels.d.ts +629 -0
- package/dist/runtime/entity/models/InventoryResponseModels.d.ts +218 -0
- package/dist/runtime/entity/models/MasterPlayerModels.d.ts +1065 -3
- package/dist/runtime/entity/models/MasterPlayerRequestModels.d.ts +1560 -6
- package/dist/runtime/entity/models/MasterPlayerResponseModels.d.ts +532 -1
- package/dist/runtime/entity/models/MultiplayerModels.d.ts +199 -0
- package/dist/runtime/entity/models/MultiplayerRequestModels.d.ts +196 -0
- package/dist/runtime/entity/models/MultiplayerResponseModels.d.ts +74 -0
- package/dist/runtime/entity/models/StoreInventoryModels.d.ts +262 -0
- package/dist/runtime/entity/models/StoreInventoryRequestModels.d.ts +268 -0
- package/dist/runtime/entity/models/StoreInventoryResponseModels.d.ts +98 -0
- package/dist/runtime/entity/request/CustomOperationRequest.d.ts +99 -0
- package/dist/runtime/entity/response/CustomOperationResponse.d.ts +118 -0
- package/dist/runtime/entity/response/GetAuthInfoResponse.d.ts +53 -0
- package/dist/runtime/entity/response/HealthCheckResponse.d.ts +56 -0
- package/dist/runtime/entity/response/UploadFileResponse.d.ts +19 -0
- package/dist/runtime/helper/CodeHelper.d.ts +122 -0
- package/dist/runtime/helper/ConverterService.d.ts +74 -0
- package/dist/runtime/helper/EnumUtility.d.ts +63 -0
- package/dist/runtime/helper/GNSupport.d.ts +64 -4
- package/dist/runtime/helper/GNUtils.d.ts +54 -0
- package/dist/runtime/helper/MessagePackConverterService.d.ts +48 -0
- package/dist/runtime/helper/OperationHelper.d.ts +51 -0
- package/dist/runtime/helper/StorageService.d.ts +48 -8
- package/dist/runtime/logger/GNDebug.d.ts +117 -0
- package/dist/runtime/networking/AuthenticateStatus.d.ts +64 -0
- package/dist/runtime/networking/IPeer.d.ts +83 -0
- package/dist/runtime/networking/NetworkingPeer.d.ts +256 -1
- package/dist/runtime/networking/OperationPending.d.ts +112 -0
- package/dist/runtime/networking/PeerBase.d.ts +231 -0
- package/dist/runtime/networking/handler/IServerEventHandler.d.ts +84 -0
- package/dist/runtime/networking/handler/OnCharacterPlayerFriendUpdateEventHandler.d.ts +69 -0
- package/dist/runtime/networking/handler/OnCharacterPlayerGroupUpdateEventHandler.d.ts +49 -0
- package/dist/runtime/networking/handler/OnGamePlayerFriendUpdateEventHandler.d.ts +50 -0
- package/dist/runtime/networking/handler/OnGamePlayerGroupUpdateEventHandler.d.ts +41 -0
- package/dist/runtime/networking/handler/OnGroupMemberUpdateEventHandler.d.ts +47 -0
- package/dist/runtime/networking/handler/OnGroupMessageUpdateEventHandler.d.ts +46 -0
- package/dist/runtime/networking/http/HttpPeer.d.ts +173 -0
- package/dist/runtime/networking/http/NetworkingHttpPeerBase.d.ts +87 -0
- package/dist/runtime/networking/http/NetworkingPeerAxiosRequest.d.ts +113 -0
- package/dist/runtime/networking/socket/NetworkingPeerSocketIOClient.d.ts +145 -0
- package/dist/runtime/networking/socket/NetworkingSocketPeerBase.d.ts +198 -0
- package/dist/runtime/networking/socket/SocketPeer.d.ts +155 -0
- package/dist/runtime/typescript/ServiceUpdate.d.ts +46 -0
- package/docs/AI_CHEATSHEET.md +211 -0
- package/docs/COOKBOOK.md +912 -0
- package/docs/RULES.md +307 -0
- package/docs/ai-manifest.json +725 -0
- package/docs/guides/AUTHENTICATE.md +246 -0
- package/docs/guides/CHARACTER_PLAYER.md +439 -0
- package/docs/guides/CLOUDSCRIPT.md +335 -0
- package/docs/guides/COCOS_CREATOR_INTEGRATION.md +150 -0
- package/docs/guides/CONTENT.md +291 -0
- package/docs/guides/DASHBOARD.md +262 -0
- package/docs/guides/GAME_PLAYER.md +473 -0
- package/docs/guides/GROUP.md +412 -0
- package/docs/guides/INVENTORY.md +375 -0
- package/docs/guides/MASTER_PLAYER.md +458 -0
- package/docs/guides/MULTIPLAYER.md +303 -0
- package/docs/guides/STORE_INVENTORY.md +313 -0
- package/docs/llms-full.txt +43 -0
- package/docs/reference/API_AUTHENTICATE.md +75 -0
- package/docs/reference/API_CHARACTER_PLAYER.md +226 -0
- package/docs/reference/API_CLOUDSCRIPT.md +82 -0
- package/docs/reference/API_CONTENT.md +88 -0
- package/docs/reference/API_DASHBOARD.md +82 -0
- package/docs/reference/API_GAME_PLAYER.md +223 -0
- package/docs/reference/API_GROUP.md +187 -0
- package/docs/reference/API_INDEX.md +57 -0
- package/docs/reference/API_INVENTORY.md +169 -0
- package/docs/reference/API_MASTER_PLAYER.md +323 -0
- package/docs/reference/API_MULTIPLAYER.md +97 -0
- package/docs/reference/API_STORE_INVENTORY.md +109 -0
- package/docs/reference/CONFIG.md +107 -0
- package/docs/reference/DTO_INDEX.md +2543 -0
- package/docs/reference/ENUMS.md +433 -0
- package/docs/reference/ERROR_HANDLING.md +159 -0
- package/docs/reference/EVENTS.md +188 -0
- package/docs/reference/PERMISSION_RULES.md +55 -0
- package/docs/reference/dto/AUTHENTICATE.md +619 -0
- package/docs/reference/dto/CHARACTER_PLAYER.md +3686 -0
- package/docs/reference/dto/CLOUDSCRIPT.md +400 -0
- package/docs/reference/dto/CONTENT.md +548 -0
- package/docs/reference/dto/DASHBOARD.md +1980 -0
- package/docs/reference/dto/GAME_PLAYER.md +3631 -0
- package/docs/reference/dto/GENERIC.md +151 -0
- package/docs/reference/dto/GROUP.md +2842 -0
- package/docs/reference/dto/INVENTORY.md +2385 -0
- package/docs/reference/dto/MASTER_PLAYER.md +6024 -0
- package/docs/reference/dto/MULTIPLAYER.md +850 -0
- package/docs/reference/dto/STORE_INVENTORY.md +1262 -0
- package/llms.txt +47 -0
- package/package.json +12 -3
- package/GNServerSettings.debug.json +0 -21
- package/docs/COCOS_CREATOR_INTEGRATION.md +0 -116
- package/examples/cocos-creator/GearNExample.ts.txt +0 -176
- package/srcSwift/Package.swift +0 -32
- package/srcSwift/Sources/GearN/runtime/GNNetwork.swift +0 -530
- package/srcSwift/Sources/GearN/runtime/GNNetworkAuthenticateApi.swift +0 -178
- package/srcSwift/Sources/GearN/runtime/GNNetworkCharacterPlayerApi.swift +0 -1162
- package/srcSwift/Sources/GearN/runtime/GNNetworkCloudScriptApi.swift +0 -154
- package/srcSwift/Sources/GearN/runtime/GNNetworkContentApi.swift +0 -208
- package/srcSwift/Sources/GearN/runtime/GNNetworkDashboardApi.swift +0 -240
- package/srcSwift/Sources/GearN/runtime/GNNetworkGamePlayerApi.swift +0 -1369
- package/srcSwift/Sources/GearN/runtime/GNNetworkGroupApi.swift +0 -1100
- package/srcSwift/Sources/GearN/runtime/GNNetworkInventoryApi.swift +0 -937
- package/srcSwift/Sources/GearN/runtime/GNNetworkMasterPlayerApi.swift +0 -2323
- package/srcSwift/Sources/GearN/runtime/GNNetworkMultiplayerApi.swift +0 -298
- package/srcSwift/Sources/GearN/runtime/GNNetworkStoreInventoryApi.swift +0 -397
- package/srcSwift/Sources/GearN/runtime/common/Action0.swift +0 -3
- package/srcSwift/Sources/GearN/runtime/common/Action1.swift +0 -3
- package/srcSwift/Sources/GearN/runtime/common/Action2.swift +0 -3
- package/srcSwift/Sources/GearN/runtime/common/Action3.swift +0 -3
- package/srcSwift/Sources/GearN/runtime/common/Action4.swift +0 -3
- package/srcSwift/Sources/GearN/runtime/common/GNArray.swift +0 -204
- package/srcSwift/Sources/GearN/runtime/common/GNData.swift +0 -108
- package/srcSwift/Sources/GearN/runtime/common/GNHashtable.swift +0 -200
- package/srcSwift/Sources/GearN/runtime/config/GNServerSettings.swift +0 -95
- package/srcSwift/Sources/GearN/runtime/constant/Commands.swift +0 -28
- package/srcSwift/Sources/GearN/runtime/constant/EventCode.swift +0 -10
- package/srcSwift/Sources/GearN/runtime/constant/OperationCode.swift +0 -252
- package/srcSwift/Sources/GearN/runtime/constant/ReturnCode.swift +0 -19
- package/srcSwift/Sources/GearN/runtime/constant/enumType/ExecuteResponseStatus.swift +0 -9
- package/srcSwift/Sources/GearN/runtime/constant/enumType/FriendStatus.swift +0 -8
- package/srcSwift/Sources/GearN/runtime/constant/enumType/GoogleLoginType.swift +0 -6
- package/srcSwift/Sources/GearN/runtime/constant/enumType/GroupStatus.swift +0 -8
- package/srcSwift/Sources/GearN/runtime/constant/enumType/InvalidMemberType.swift +0 -19
- package/srcSwift/Sources/GearN/runtime/constant/enumType/ItemType.swift +0 -6
- package/srcSwift/Sources/GearN/runtime/constant/enumType/MatchmakingMemberStatus.swift +0 -7
- package/srcSwift/Sources/GearN/runtime/constant/enumType/MatchmakingTicketStatus.swift +0 -9
- package/srcSwift/Sources/GearN/runtime/constant/enumType/OwnerType.swift +0 -10
- package/srcSwift/Sources/GearN/runtime/constant/enumType/PermissionDataItem.swift +0 -6
- package/srcSwift/Sources/GearN/runtime/constant/enumType/PushPlatformType.swift +0 -6
- package/srcSwift/Sources/GearN/runtime/constant/enumType/RequestRole.swift +0 -7
- package/srcSwift/Sources/GearN/runtime/constant/enumType/RequestType.swift +0 -16
- package/srcSwift/Sources/GearN/runtime/constant/enumType/StoreItemType.swift +0 -6
- package/srcSwift/Sources/GearN/runtime/constant/enumType/StoreReceiveType.swift +0 -9
- package/srcSwift/Sources/GearN/runtime/constant/errorCode/ErrorCode.swift +0 -58
- package/srcSwift/Sources/GearN/runtime/constant/parameterCode/ParameterCode.swift +0 -672
- package/srcSwift/Sources/GearN/runtime/entity/DataMember.swift +0 -196
- package/srcSwift/Sources/GearN/runtime/entity/GNMetadata.swift +0 -9
- package/srcSwift/Sources/GearN/runtime/entity/InvalidMember.swift +0 -11
- package/srcSwift/Sources/GearN/runtime/entity/OperationEvent.swift +0 -38
- package/srcSwift/Sources/GearN/runtime/entity/OperationHelper.swift +0 -28
- package/srcSwift/Sources/GearN/runtime/entity/OperationRequest.swift +0 -62
- package/srcSwift/Sources/GearN/runtime/entity/OperationResponse.swift +0 -98
- package/srcSwift/Sources/GearN/runtime/entity/models/AuthenticateModels.swift +0 -351
- package/srcSwift/Sources/GearN/runtime/entity/models/AuthenticateRequestModels.swift +0 -81
- package/srcSwift/Sources/GearN/runtime/entity/models/AuthenticateResponseModels.swift +0 -108
- package/srcSwift/Sources/GearN/runtime/entity/models/CharacterPlayerModels.swift +0 -1045
- package/srcSwift/Sources/GearN/runtime/entity/models/CharacterPlayerRequestModels.swift +0 -821
- package/srcSwift/Sources/GearN/runtime/entity/models/CharacterPlayerResponseModels.swift +0 -588
- package/srcSwift/Sources/GearN/runtime/entity/models/CloudScriptModels.swift +0 -187
- package/srcSwift/Sources/GearN/runtime/entity/models/CloudScriptRequestModels.swift +0 -84
- package/srcSwift/Sources/GearN/runtime/entity/models/CloudScriptResponseModels.swift +0 -59
- package/srcSwift/Sources/GearN/runtime/entity/models/ContentModels.swift +0 -195
- package/srcSwift/Sources/GearN/runtime/entity/models/ContentRequestModels.swift +0 -116
- package/srcSwift/Sources/GearN/runtime/entity/models/ContentResponseModels.swift +0 -81
- package/srcSwift/Sources/GearN/runtime/entity/models/DashboardModels.swift +0 -426
- package/srcSwift/Sources/GearN/runtime/entity/models/DashboardRequestModels.swift +0 -160
- package/srcSwift/Sources/GearN/runtime/entity/models/DashboardResponseModels.swift +0 -82
- package/srcSwift/Sources/GearN/runtime/entity/models/GamePlayerModels.swift +0 -1334
- package/srcSwift/Sources/GearN/runtime/entity/models/GamePlayerRequestModels.swift +0 -643
- package/srcSwift/Sources/GearN/runtime/entity/models/GamePlayerResponseModels.swift +0 -213
- package/srcSwift/Sources/GearN/runtime/entity/models/GenericModels.swift +0 -171
- package/srcSwift/Sources/GearN/runtime/entity/models/GroupModels.swift +0 -850
- package/srcSwift/Sources/GearN/runtime/entity/models/GroupRequestModels.swift +0 -485
- package/srcSwift/Sources/GearN/runtime/entity/models/GroupResponseModels.swift +0 -165
- package/srcSwift/Sources/GearN/runtime/entity/models/InventoryModels.swift +0 -679
- package/srcSwift/Sources/GearN/runtime/entity/models/InventoryRequestModels.swift +0 -413
- package/srcSwift/Sources/GearN/runtime/entity/models/InventoryResponseModels.swift +0 -141
- package/srcSwift/Sources/GearN/runtime/entity/models/MasterPlayerModels.swift +0 -378
- package/srcSwift/Sources/GearN/runtime/entity/models/MasterPlayerRequestModels.swift +0 -147
- package/srcSwift/Sources/GearN/runtime/entity/models/MasterPlayerResponseModels.swift +0 -318
- package/srcSwift/Sources/GearN/runtime/entity/models/MultiplayerModels.swift +0 -319
- package/srcSwift/Sources/GearN/runtime/entity/models/MultiplayerRequestModels.swift +0 -125
- package/srcSwift/Sources/GearN/runtime/entity/models/MultiplayerResponseModels.swift +0 -45
- package/srcSwift/Sources/GearN/runtime/entity/models/StoreInventoryModels.swift +0 -633
- package/srcSwift/Sources/GearN/runtime/entity/models/StoreInventoryRequestModels.swift +0 -173
- package/srcSwift/Sources/GearN/runtime/entity/models/StoreInventoryResponseModels.swift +0 -61
- package/srcSwift/Sources/GearN/runtime/entity/request/CustomOperationRequest.swift +0 -42
- package/srcSwift/Sources/GearN/runtime/entity/response/CustomOperationResponse.swift +0 -49
- package/srcSwift/Sources/GearN/runtime/entity/response/GetAuthInfoResponse.swift +0 -43
- package/srcSwift/Sources/GearN/runtime/entity/response/HealthCheckResponse.swift +0 -86
- package/srcSwift/Sources/GearN/runtime/entity/response/UploadFileResponse.swift +0 -15
- package/srcSwift/Sources/GearN/runtime/helper/CodeHelper.swift +0 -107
- package/srcSwift/Sources/GearN/runtime/helper/ConverterService.swift +0 -98
- package/srcSwift/Sources/GearN/runtime/helper/EnumUtility.swift +0 -34
- package/srcSwift/Sources/GearN/runtime/helper/GNSupport.swift +0 -41
- package/srcSwift/Sources/GearN/runtime/helper/GNUtils.swift +0 -66
- package/srcSwift/Sources/GearN/runtime/helper/MessagePackConverterService.swift +0 -21
- package/srcSwift/Sources/GearN/runtime/helper/StorageService.swift +0 -29
- package/srcSwift/Sources/GearN/runtime/logger/GNDebug.swift +0 -33
- package/srcSwift/Sources/GearN/runtime/networking/AuthenticateStatus.swift +0 -24
- package/srcSwift/Sources/GearN/runtime/networking/IPeer.swift +0 -8
- package/srcSwift/Sources/GearN/runtime/networking/NetworkingPeer.swift +0 -368
- package/srcSwift/Sources/GearN/runtime/networking/OperationPending.swift +0 -81
- package/srcSwift/Sources/GearN/runtime/networking/PeerBase.swift +0 -228
- package/srcSwift/Sources/GearN/runtime/networking/handler/IServerEventHandler.swift +0 -20
- package/srcSwift/Sources/GearN/runtime/networking/http/HttpPeer.swift +0 -226
- package/srcSwift/Sources/GearN/runtime/networking/http/HttpTypes.swift +0 -24
- package/srcSwift/Sources/GearN/runtime/networking/http/NetworkingHttpPeerBase.swift +0 -13
- package/srcSwift/Sources/GearN/runtime/networking/http/NetworkingPeerUrlSession.swift +0 -125
- package/srcSwift/Sources/GearN/runtime/networking/request/NetRequest.swift +0 -19
- package/srcSwift/Sources/GearN/runtime/networking/response/NetResponse.swift +0 -13
- package/srcSwift/Sources/GearN/runtime/networking/socket/NetworkingPeerSocketIOClient.swift +0 -244
- package/srcSwift/Sources/GearN/runtime/networking/socket/NetworkingSocketPeerBase.swift +0 -59
- package/srcSwift/Sources/GearN/runtime/networking/socket/SocketPeer.swift +0 -136
- package/tsconfig-build.cocos.json +0 -31
- package/webpack.config.cocos.mjs +0 -78
package/docs/COOKBOOK.md
ADDED
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
# Cookbook
|
|
2
|
+
|
|
3
|
+
16 scenario end-to-end phổ biến. Mỗi scenario có: problem, prerequisites, APIs used, code snippet, return path handling, pitfall.
|
|
4
|
+
|
|
5
|
+
> Tất cả snippet dùng import từ package name `@xmobitea/gn-typescript-client`. Ví dụ dùng dạng `async/await` (form `*Async()`). Các comment trong snippet là điểm nối vào logic ứng dụng của bạn.
|
|
6
|
+
|
|
7
|
+
## Index
|
|
8
|
+
|
|
9
|
+
1. [Init SDK + bootstrap tối thiểu](#scenario-1-init-sdk--bootstrap-tối-thiểu)
|
|
10
|
+
2. [Login by account + retrieve profile](#scenario-2-login-by-account--retrieve-profile)
|
|
11
|
+
3. [Refresh token và persist session](#scenario-3-refresh-token-và-persist-session)
|
|
12
|
+
4. [Login by social (Google/Apple/Facebook)](#scenario-4-login-by-social-googleapplefacebook)
|
|
13
|
+
5. [Connect socket + handle reconnect](#scenario-5-connect-socket--handle-reconnect)
|
|
14
|
+
6. [Subscribe friend updates](#scenario-6-subscribe-friend-updates)
|
|
15
|
+
7. [Subscribe + send group message](#scenario-7-subscribe--send-group-message)
|
|
16
|
+
8. [Group member lifecycle (join / leave / kick) + realtime update](#scenario-8-group-member-lifecycle-join--leave--kick--realtime-update)
|
|
17
|
+
9. [Matchmaking ticket flow](#scenario-9-matchmaking-ticket-flow)
|
|
18
|
+
10. [Inventory: query + grant item (server role)](#scenario-10-inventory-query--grant-item-server-role)
|
|
19
|
+
11. [Store: list catalog → purchase → validate receipt](#scenario-11-store-list-catalog--purchase--validate-receipt)
|
|
20
|
+
12. [CloudScript: execute function + customTags](#scenario-12-cloudscript-execute-function--customtags)
|
|
21
|
+
13. [Override authToken / secret (impersonate, cross-role)](#scenario-13-override-authtoken--secret-impersonate-cross-role)
|
|
22
|
+
14. [Admin login qua DashboardApi](#scenario-14-admin-login-qua-dashboardapi)
|
|
23
|
+
15. [Cleanup & shutdown flow](#scenario-15-cleanup--shutdown-flow)
|
|
24
|
+
16. [Diagnose `OperationNotAllow`](#scenario-16-diagnose-operationnotallow)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Scenario 1: Init SDK + bootstrap tối thiểu
|
|
29
|
+
|
|
30
|
+
**Problem:** Lần đầu khởi chạy ứng dụng — cần init SDK để bất kỳ API nào hoạt động.
|
|
31
|
+
|
|
32
|
+
**Prerequisites:** biết server address/port và `secretKey` backend cấp.
|
|
33
|
+
|
|
34
|
+
**APIs used:** `GNNetwork.init()`, `GNServerSettings`.
|
|
35
|
+
|
|
36
|
+
Code mẫu đầy đủ (có comment từng field): [README § 4](../README.MD#4-khởi-tạo-cơ-bản). Minimal pattern: [AI_CHEATSHEET § 1](AI_CHEATSHEET.md#1-mandatory-pattern-sao-chép--dùng).
|
|
37
|
+
|
|
38
|
+
### Return paths
|
|
39
|
+
|
|
40
|
+
- `init()` là void và **idempotent** — gọi lần thứ 2 chỉ log warning rồi return.
|
|
41
|
+
- Không có network call ở bước này; lỗi cấu hình thường gặp là gọi `settings.config({...})` thiếu field required trong `GNServerSettingsOptions`.
|
|
42
|
+
|
|
43
|
+
### Pitfalls
|
|
44
|
+
|
|
45
|
+
- `logType: LogType.All` trong production làm console spam. Dùng `LogType.Error` hoặc `LogType.Off`.
|
|
46
|
+
- Gọi lại `GNNetwork.init()` để đổi settings không có hiệu lực. Setter trên cùng `settings` object sau init chỉ ảnh hưởng một số request mới và không tự reconnect socket.
|
|
47
|
+
- Full anti-patterns: [RULES § 10](RULES.md#10-anti-patterns--pitfalls). Full field schema: [reference/CONFIG.md](reference/CONFIG.md).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Scenario 2: Login by account + retrieve profile
|
|
52
|
+
|
|
53
|
+
**Problem:** User nhập username/password → gọi login → nhận profile + cache auth token để request sau tự động attach.
|
|
54
|
+
|
|
55
|
+
**Prerequisites:** đã `init()`.
|
|
56
|
+
|
|
57
|
+
**APIs used:** [`GNNetwork.authenticate.loginByAccountAsync`](reference/API_AUTHENTICATE.md), [`AuthenticateModels.LoginByAccountRequestData`](reference/dto/AUTHENTICATE.md#loginbyaccountrequestdata), [`AuthenticateModels.InfoRequestParam`](reference/dto/AUTHENTICATE.md#inforequestparam).
|
|
58
|
+
|
|
59
|
+
### Code
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import {
|
|
63
|
+
GNNetwork,
|
|
64
|
+
ReturnCode,
|
|
65
|
+
ErrorCode,
|
|
66
|
+
AuthenticateModels,
|
|
67
|
+
} from "@xmobitea/gn-typescript-client";
|
|
68
|
+
|
|
69
|
+
async function loginWithAccount(username: string, password: string) {
|
|
70
|
+
const infoRequestParam = new AuthenticateModels.InfoRequestParam();
|
|
71
|
+
infoRequestParam.displayName = true;
|
|
72
|
+
infoRequestParam.avatar = true;
|
|
73
|
+
infoRequestParam.playerCurrencies = true;
|
|
74
|
+
infoRequestParam.tsCreate = true;
|
|
75
|
+
|
|
76
|
+
const request = new AuthenticateModels.LoginByAccountRequestData();
|
|
77
|
+
request.username = username;
|
|
78
|
+
request.password = password;
|
|
79
|
+
request.infoRequestParam = infoRequestParam;
|
|
80
|
+
|
|
81
|
+
const res = await GNNetwork.authenticate.loginByAccountAsync(request);
|
|
82
|
+
|
|
83
|
+
if (res.returnCode !== ReturnCode.Ok) {
|
|
84
|
+
console.error("transport error", res.returnCode, res.debugMessage);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (res.errorCode !== ErrorCode.Ok) {
|
|
88
|
+
console.warn("business error", res.errorCode, res.invalidMembers);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// AuthenticateApi success auto-caches authToken + userId into AuthenticateStatus/StorageService.
|
|
93
|
+
return res.responseData; // AuthenticateResponseData
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Return paths
|
|
98
|
+
|
|
99
|
+
- Happy: `ReturnCode.Ok` + `ErrorCode.Ok` → SDK cache `authToken` + `userId`; response có profile fields enabled trong `InfoRequestParam`.
|
|
100
|
+
- `ReturnCode.OperationTimeout` (-1) → retry sau nếu idempotent.
|
|
101
|
+
- `ErrorCode.AccountNotFound` (2) hoặc `ErrorCode.AccountPasswordWrong` → báo user.
|
|
102
|
+
- `ReturnCode.InvalidRequestParameters` với `invalidMembers` → có field request sai format (ví dụ username < 6 chars).
|
|
103
|
+
|
|
104
|
+
### Pitfalls
|
|
105
|
+
|
|
106
|
+
- Đọc `responseData` trước khi check đủ 2 tầng → có thể dùng payload chưa đáng tin.
|
|
107
|
+
- Set `infoRequestParam.customDatas = true` nhưng không set `customDataKeys` → backend có thể trả TẤT CẢ custom data, tốn bandwidth.
|
|
108
|
+
- `username`/`password` < 6 ký tự → backend reject với `ReturnCode.InvalidRequestParameters` trước khi query DB.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Scenario 3: Refresh token và persist session
|
|
113
|
+
|
|
114
|
+
**Problem:** Token sắp hết hạn (hoặc user mở lại app sau 1 ngày) → refresh để lấy token mới, tránh bắt login lại.
|
|
115
|
+
|
|
116
|
+
**Prerequisites:** đã có `authToken` cache từ lần login trước (stored ở `StorageService`).
|
|
117
|
+
|
|
118
|
+
**APIs used:** [`GNNetwork.authenticate.refreshAuthTokenAsync`](reference/API_AUTHENTICATE.md).
|
|
119
|
+
|
|
120
|
+
### Code
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { GNNetwork, ReturnCode, ErrorCode, AuthenticateModels } from "@xmobitea/gn-typescript-client";
|
|
124
|
+
|
|
125
|
+
async function ensureValidToken() {
|
|
126
|
+
// refreshAuthToken uses cached auth context — không cần truyền field nào
|
|
127
|
+
const res = await GNNetwork.authenticate.refreshAuthTokenAsync(
|
|
128
|
+
new AuthenticateModels.RefreshAuthTokenRequestData(),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (res.returnCode !== ReturnCode.Ok) return false;
|
|
132
|
+
if (res.errorCode !== ErrorCode.Ok) return false;
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// On app start:
|
|
138
|
+
if (!(await ensureValidToken())) {
|
|
139
|
+
// redirect về login screen
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Return paths
|
|
144
|
+
|
|
145
|
+
- Happy → `authToken` mới được SDK cache, session tiếp tục.
|
|
146
|
+
- `ReturnCode.OperationNotAuthorized` hoặc `ErrorCode.VerifyTokenError` → token cũ invalid/expired; cần login lại.
|
|
147
|
+
- `ErrorCode.PlayerBan` → user bị ban; xem `res.responseData.playerBan` (BanItem với tsExpire + reason).
|
|
148
|
+
|
|
149
|
+
### Pitfalls
|
|
150
|
+
|
|
151
|
+
- Không có `authToken` cache → request gửi đi thiếu auth, reject.
|
|
152
|
+
- Refresh thành công nhưng app vẫn dùng token tự lưu bên ngoài SDK → đồng bộ lại storage riêng của app từ `GNNetwork.getAuthenticateStatus()` nếu có.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Scenario 4: Login by social (Google/Apple/Facebook)
|
|
157
|
+
|
|
158
|
+
**Problem:** User chọn đăng nhập bằng Google (hoặc Apple, Facebook, Game Center, GooglePlayGameService …).
|
|
159
|
+
|
|
160
|
+
**Prerequisites:** đã init; đã có ID token từ social SDK native.
|
|
161
|
+
|
|
162
|
+
**APIs used:** `loginByGoogleAsync`, `loginByAppleAsync`, `loginByFacebookAsync`, `loginByGameCenterAsync`, `loginByGooglePlayGameServiceAsync` — cùng pattern. Xem [API_AUTHENTICATE.md](reference/API_AUTHENTICATE.md).
|
|
163
|
+
|
|
164
|
+
### Code
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import {
|
|
168
|
+
GNNetwork,
|
|
169
|
+
ReturnCode,
|
|
170
|
+
ErrorCode,
|
|
171
|
+
AuthenticateModels,
|
|
172
|
+
GoogleLoginType,
|
|
173
|
+
} from "@xmobitea/gn-typescript-client";
|
|
174
|
+
|
|
175
|
+
async function loginWithGoogle(idToken: string) {
|
|
176
|
+
const infoRequestParam = new AuthenticateModels.InfoRequestParam();
|
|
177
|
+
infoRequestParam.displayName = true;
|
|
178
|
+
infoRequestParam.avatar = true;
|
|
179
|
+
|
|
180
|
+
const request = new AuthenticateModels.LoginByGoogleRequestData();
|
|
181
|
+
request.token = idToken;
|
|
182
|
+
request.type = GoogleLoginType.IdToken;
|
|
183
|
+
request.createPlayerIfNotExists = true;
|
|
184
|
+
request.infoRequestParam = infoRequestParam;
|
|
185
|
+
|
|
186
|
+
const res = await GNNetwork.authenticate.loginByGoogleAsync(request);
|
|
187
|
+
|
|
188
|
+
if (res.returnCode !== ReturnCode.Ok || res.errorCode !== ErrorCode.Ok) return null;
|
|
189
|
+
|
|
190
|
+
// AuthenticateApi success auto-caches authToken + userId.
|
|
191
|
+
if (res.responseData.newlyCreated) console.log("new player created");
|
|
192
|
+
return res.responseData;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Return paths
|
|
197
|
+
|
|
198
|
+
- Happy → `authToken` + `newlyCreated` flag cho biết user mới hay đã tồn tại.
|
|
199
|
+
- `ErrorCode.VerifyTokenError` hoặc `ErrorCode.VerifyFailed` → ID token social invalid/expired.
|
|
200
|
+
- `ErrorCode.ExternalLinkedOtherAccount` hoặc `ErrorCode.ExternalLinkedOtherValue` → social id đã liên kết account khác.
|
|
201
|
+
|
|
202
|
+
### Pitfalls
|
|
203
|
+
|
|
204
|
+
- `createPlayerIfNotExists: false` với social id chưa từng login → `ErrorCode.AccountNotFound`. Set `true` cho flow "login hoặc register" tự động.
|
|
205
|
+
- Apple ID token có lifetime rất ngắn; lấy xong phải call ngay.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Scenario 5: Connect socket + handle reconnect
|
|
210
|
+
|
|
211
|
+
**Problem:** Cần realtime event (group message, friend update, …) — phải connect socket.
|
|
212
|
+
|
|
213
|
+
**Prerequisites:** đã login (có `authToken` cache); `useSocket: true` trong settings.
|
|
214
|
+
|
|
215
|
+
**APIs used:** `GNNetwork.connectSocket`, `GNNetwork.sendRequestAuthSocket`, `GNNetwork.subscriberOnConnectHandler`, `GNNetwork.subscriberOnDisconnectHandler`.
|
|
216
|
+
|
|
217
|
+
### Code
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { GNNetwork } from "@xmobitea/gn-typescript-client";
|
|
221
|
+
|
|
222
|
+
GNNetwork.subscriberOnConnectHandler(() => {
|
|
223
|
+
console.log("socket connected, sid=", GNNetwork.getSocketSId());
|
|
224
|
+
// SDK auto-auths the socket if an auth token is already cached.
|
|
225
|
+
// Call GNNetwork.sendRequestAuthSocket() manually only when login/token refresh happens after this connect.
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
GNNetwork.subscriberOnDisconnectHandler(() => {
|
|
229
|
+
console.warn("socket disconnected — SDK will auto-reconnect after reconnectDelay ms");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
GNNetwork.connectSocket(() => {
|
|
233
|
+
// optional one-shot callback on first connect
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Return paths
|
|
238
|
+
|
|
239
|
+
- Connect thành công → `onConnect` callback fire; `isSocketConnected()` trả `true`.
|
|
240
|
+
- Disconnect do network → SDK tự retry sau `reconnectDelay` (default 5000ms). Mỗi lần reconnect sẽ fire `onConnect` handler lại.
|
|
241
|
+
|
|
242
|
+
### Pitfalls
|
|
243
|
+
|
|
244
|
+
- Nếu socket connect trước khi login/token refresh, cần gọi `sendRequestAuthSocket()` sau khi token có trong cache để re-auth thủ công.
|
|
245
|
+
- `subscriberOnConnectHandler` gắn MULTIPLE callback, mỗi lần reconnect đều fire tất cả. Dùng `unscriberOnConnectHandler` khi cleanup.
|
|
246
|
+
- `connectSocket()` gọi trước `init()` → crash.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Scenario 6: Subscribe friend updates
|
|
251
|
+
|
|
252
|
+
**Problem:** Hiển thị UI danh sách bạn bè realtime (thêm/xóa/thay đổi status).
|
|
253
|
+
|
|
254
|
+
**Prerequisites:** socket đã connected + authenticated (scenario 5).
|
|
255
|
+
|
|
256
|
+
**APIs used:** [`OnGamePlayerFriendUpdateEventHandler`](reference/EVENTS.md#ongameplayerfriendupdateeventhandler).
|
|
257
|
+
|
|
258
|
+
### Code
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { OnGamePlayerFriendUpdateEventHandler, FriendStatus } from "@xmobitea/gn-typescript-client";
|
|
262
|
+
|
|
263
|
+
OnGamePlayerFriendUpdateEventHandler.onUpdate = (payload) => {
|
|
264
|
+
// payload.playerFriends: Array<GenericModels.FriendItem>
|
|
265
|
+
for (const f of payload.playerFriends ?? []) {
|
|
266
|
+
switch (f.status) {
|
|
267
|
+
case FriendStatus.Friend: {
|
|
268
|
+
upsertFriendInUI(f);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case FriendStatus.WaitingAccept:
|
|
272
|
+
case FriendStatus.SentRequestAndWaitingAccept: {
|
|
273
|
+
showFriendRequest(f);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case FriendStatus.NotFriend: {
|
|
277
|
+
removeFriendFromUI(f.friendId);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// Cleanup khi user logout hoặc navigate đi
|
|
285
|
+
function unsubscribe() {
|
|
286
|
+
OnGamePlayerFriendUpdateEventHandler.onUpdate = () => {};
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Return paths
|
|
291
|
+
|
|
292
|
+
- Event fire mỗi khi friend relation thay đổi — hot path, đừng làm heavy work trong handler.
|
|
293
|
+
- Không có ACK trả về; delivery là at-most-once khi socket up.
|
|
294
|
+
|
|
295
|
+
### Pitfalls
|
|
296
|
+
|
|
297
|
+
- `onUpdate` là field `static` — gán nhiều component sẽ **ghi đè lẫn nhau**, chỉ handler cuối cùng fire. Dùng wrapper pattern (xem [EVENTS.md](reference/EVENTS.md)) nếu cần fan-out.
|
|
298
|
+
- Khi socket tạm mất kết nối → event trong interval đó KHÔNG được replay. App nên resync bằng `GNNetwork.gamePlayer.getFriendList*` sau reconnect.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Scenario 7: Subscribe + send group message
|
|
303
|
+
|
|
304
|
+
**Problem:** Chat trong group — lắng nghe message đến + gửi message đi.
|
|
305
|
+
|
|
306
|
+
**Prerequisites:** đã login + connect socket + là member của `groupId`.
|
|
307
|
+
|
|
308
|
+
**APIs used:** [`OnGroupMessageUpdateEventHandler`](reference/EVENTS.md#ongroupmessageupdateeventhandler), [`GNNetwork.group.sendGroupMessageAsync`](reference/API_GROUP.md).
|
|
309
|
+
|
|
310
|
+
### Code
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import {
|
|
314
|
+
GNNetwork,
|
|
315
|
+
OnGroupMessageUpdateEventHandler,
|
|
316
|
+
ReturnCode,
|
|
317
|
+
ErrorCode,
|
|
318
|
+
GroupModels,
|
|
319
|
+
} from "@xmobitea/gn-typescript-client";
|
|
320
|
+
|
|
321
|
+
// 1. Subscribe incoming messages
|
|
322
|
+
OnGroupMessageUpdateEventHandler.onUpdate = (payload) => {
|
|
323
|
+
if (payload.groupId !== currentGroupId) return;
|
|
324
|
+
for (const msg of payload.groupMessages ?? []) {
|
|
325
|
+
appendMessageToUI(msg);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// 2. Send a new message
|
|
330
|
+
async function sendGroupChat(groupId: string, text: string) {
|
|
331
|
+
const request = new GroupModels.SendGroupMessageRequestData();
|
|
332
|
+
request.senderId = GNNetwork.getAuthenticateStatus().getUserId();
|
|
333
|
+
request.groupId = groupId;
|
|
334
|
+
request.message = text;
|
|
335
|
+
|
|
336
|
+
const res = await GNNetwork.group.sendGroupMessageAsync(request);
|
|
337
|
+
|
|
338
|
+
if (res.returnCode !== ReturnCode.Ok || res.errorCode !== ErrorCode.Ok) {
|
|
339
|
+
console.warn("send failed", res.errorCode, res.debugMessage);
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Return paths
|
|
347
|
+
|
|
348
|
+
- Response `sendGroupMessageAsync` trả về nhanh khi server đã nhận — event `OnGroupMessageUpdate` sẽ fire cho TẤT CẢ member trong group (bao gồm sender).
|
|
349
|
+
- `ErrorCode.PlayerNotMember` hoặc `ReturnCode.OperationNotAllow` → user không phải member hoặc secret thiếu permission gửi message.
|
|
350
|
+
- `ErrorCode.GroupNotFound` → `groupId` sai.
|
|
351
|
+
|
|
352
|
+
### Pitfalls
|
|
353
|
+
|
|
354
|
+
- Optimistic UI: nếu append message ngay sau khi gọi send thành công, và event handler cũng append → duplicate. Dùng messageId server trả về để dedupe.
|
|
355
|
+
- Event `OnGroupMessageUpdate` có thể payload nhiều message cùng lúc (khi backend batch) — luôn lặp `payload.groupMessages`.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Scenario 8: Group member lifecycle (join / leave / kick) + realtime update
|
|
360
|
+
|
|
361
|
+
**Problem:** User join/leave group, và mọi member khác thấy roster update realtime.
|
|
362
|
+
|
|
363
|
+
**Prerequisites:** đã login + connect socket.
|
|
364
|
+
|
|
365
|
+
**APIs used:** `GNNetwork.gamePlayer.joinGroupAsync`, `GNNetwork.gamePlayer.leaveGroupAsync`, `GNNetwork.group.server.removeMemberAsync`, [`OnGroupMemberUpdateEventHandler`](reference/EVENTS.md#ongroupmemberupdateeventhandler).
|
|
366
|
+
|
|
367
|
+
### Code
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import {
|
|
371
|
+
GNNetwork,
|
|
372
|
+
OnGroupMemberUpdateEventHandler,
|
|
373
|
+
ReturnCode,
|
|
374
|
+
ErrorCode,
|
|
375
|
+
GamePlayerModels,
|
|
376
|
+
} from "@xmobitea/gn-typescript-client";
|
|
377
|
+
|
|
378
|
+
OnGroupMemberUpdateEventHandler.onUpdate = (payload) => {
|
|
379
|
+
if (payload.groupId !== currentGroupId) return;
|
|
380
|
+
for (const m of payload.members ?? []) {
|
|
381
|
+
// MemberItem.status is backend-defined; update idempotently and resync roster after reconnect.
|
|
382
|
+
roster.upsert(m);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
async function join(groupId: string) {
|
|
387
|
+
const request = new GamePlayerModels.JoinGroupRequestData();
|
|
388
|
+
request.groupId = groupId;
|
|
389
|
+
|
|
390
|
+
const res = await GNNetwork.gamePlayer.joinGroupAsync(request);
|
|
391
|
+
if (res.returnCode !== ReturnCode.Ok || res.errorCode !== ErrorCode.Ok) {
|
|
392
|
+
if (res.errorCode === ErrorCode.GroupNotFound) return "group_not_found";
|
|
393
|
+
return "error";
|
|
394
|
+
}
|
|
395
|
+
return "ok";
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function leave(groupId: string) {
|
|
399
|
+
const request = new GamePlayerModels.LeaveGroupRequestData();
|
|
400
|
+
request.groupId = groupId;
|
|
401
|
+
|
|
402
|
+
await GNNetwork.gamePlayer.leaveGroupAsync(request);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Return paths
|
|
407
|
+
|
|
408
|
+
- Join OK → event `OnGroupMemberUpdate` fire với status `Joined`.
|
|
409
|
+
- `ErrorCode.GroupNotFound`, `ErrorCode.GamePlayerNotFound`, `ErrorCode.PlayerBan`, hoặc `ReturnCode.OperationNotAllow` → xử lý theo context.
|
|
410
|
+
|
|
411
|
+
### Pitfalls
|
|
412
|
+
|
|
413
|
+
- `removeMember` là method `.server` namespace — chỉ call được nếu có `secretKey` có `permission rules` có `group.removeMember.serverSelfEnable`. Từ client thường expose qua CloudScript.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Scenario 9: Matchmaking ticket flow
|
|
418
|
+
|
|
419
|
+
**Problem:** User bấm "Find match" → enqueue ticket → poll status → vào match khi ghép xong.
|
|
420
|
+
|
|
421
|
+
**Prerequisites:** đã login + connect socket.
|
|
422
|
+
|
|
423
|
+
**APIs used:** `GNNetwork.multiplayer.createMatchmakingTicketAsync`, `getMatchmakingTicketAsync`, `cancelMatchmakingTicketAsync`. Xem [API_MULTIPLAYER.md](reference/API_MULTIPLAYER.md).
|
|
424
|
+
|
|
425
|
+
### Code
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
import {
|
|
429
|
+
GNNetwork,
|
|
430
|
+
ReturnCode,
|
|
431
|
+
ErrorCode,
|
|
432
|
+
GNHashtable,
|
|
433
|
+
MultiplayerModels,
|
|
434
|
+
MatchmakingTicketStatus,
|
|
435
|
+
} from "@xmobitea/gn-typescript-client";
|
|
436
|
+
|
|
437
|
+
async function findMatch(queueName: string) {
|
|
438
|
+
const createRequest = new MultiplayerModels.CreateMatchmakingTicketRequestData();
|
|
439
|
+
createRequest.queueName = queueName;
|
|
440
|
+
createRequest.giveUpAfterSeconds = 60;
|
|
441
|
+
createRequest.attribute = GNHashtable.builder().build();
|
|
442
|
+
|
|
443
|
+
const createRes = await GNNetwork.multiplayer.createMatchmakingTicketAsync(createRequest);
|
|
444
|
+
if (createRes.returnCode !== ReturnCode.Ok || createRes.errorCode !== ErrorCode.Ok) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
const ticketId = createRes.responseData.ticketId;
|
|
448
|
+
|
|
449
|
+
// Poll mỗi 2s cho tới khi matched hoặc timeout
|
|
450
|
+
for (let i = 0; i < 30; i++) {
|
|
451
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
452
|
+
const pollRequest = new MultiplayerModels.GetMatchmakingTicketRequestData();
|
|
453
|
+
pollRequest.ticketId = ticketId;
|
|
454
|
+
pollRequest.returnMember = true;
|
|
455
|
+
|
|
456
|
+
const poll = await GNNetwork.multiplayer.getMatchmakingTicketAsync(pollRequest);
|
|
457
|
+
if (poll.returnCode !== ReturnCode.Ok || poll.errorCode !== ErrorCode.Ok) continue;
|
|
458
|
+
|
|
459
|
+
const ticket = poll.responseData.matchmakingTicket;
|
|
460
|
+
const status = ticket.status;
|
|
461
|
+
if (status === MatchmakingTicketStatus.Matched) {
|
|
462
|
+
return ticket.matchId;
|
|
463
|
+
}
|
|
464
|
+
if (status === MatchmakingTicketStatus.Canceled) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Timeout: cancel ticket để queue không giữ slot
|
|
470
|
+
const cancelRequest = new MultiplayerModels.CancelMatchmakingTicketRequestData();
|
|
471
|
+
cancelRequest.ticketId = ticketId;
|
|
472
|
+
|
|
473
|
+
await GNNetwork.multiplayer.cancelMatchmakingTicketAsync(cancelRequest);
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Return paths
|
|
479
|
+
|
|
480
|
+
- `MatchmakingTicketStatus.Matched` → có `matchId`, chuyển sang scene game.
|
|
481
|
+
- `WaitingForMembers`, `WaitingForMatch`, `WaitingForServer` → tiếp tục poll.
|
|
482
|
+
- `Canceled` → abort.
|
|
483
|
+
|
|
484
|
+
### Pitfalls
|
|
485
|
+
|
|
486
|
+
- Không cancel khi user hủy → ticket vẫn trong queue, occupy slot tới khi server timeout.
|
|
487
|
+
- Poll interval quá ngắn (<1s) → rate-limit reject.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Scenario 10: Inventory query + grant item (server role)
|
|
492
|
+
|
|
493
|
+
**Problem:** Client xem inventory; backend/CloudScript grant item cho player khác.
|
|
494
|
+
|
|
495
|
+
**Prerequisites:** client login; `secretKey` có `permission rules` là `admin` hoặc `server` cấu hình ở backend/CloudScript.
|
|
496
|
+
|
|
497
|
+
**APIs used:** [`GNNetwork.gamePlayer.getPlayerInventoryAsync`](reference/API_GAME_PLAYER.md) (client), `GNNetwork.gamePlayer.server.createPlayerItemAsync` (server).
|
|
498
|
+
|
|
499
|
+
### Code
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
import { GNNetwork, ReturnCode, ErrorCode, GamePlayerModels } from "@xmobitea/gn-typescript-client";
|
|
503
|
+
|
|
504
|
+
// Client: xem inventory của chính mình
|
|
505
|
+
async function loadMyInventory() {
|
|
506
|
+
const request = new GamePlayerModels.GetPlayerInventoryRequestData();
|
|
507
|
+
request.itemCatalogIds = ["weapon"];
|
|
508
|
+
|
|
509
|
+
const res = await GNNetwork.gamePlayer.getPlayerInventoryAsync(request);
|
|
510
|
+
if (res.returnCode !== ReturnCode.Ok || res.errorCode !== ErrorCode.Ok) return [];
|
|
511
|
+
return res.responseData.infoResponseParameters?.playerInventories ?? [];
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Server: create/grant 1 sword cho player targetUserId
|
|
515
|
+
async function grantSword(targetUserId: string) {
|
|
516
|
+
const request = new GamePlayerModels.ServerCreatePlayerItemRequestData();
|
|
517
|
+
request.userId = targetUserId;
|
|
518
|
+
request.catalogId = "weapon";
|
|
519
|
+
request.classId = "sword_001";
|
|
520
|
+
request.displayName = "Starter Sword";
|
|
521
|
+
request.amount = 1;
|
|
522
|
+
|
|
523
|
+
const res = await GNNetwork.gamePlayer.server.createPlayerItemAsync(request);
|
|
524
|
+
return res.returnCode === ReturnCode.Ok && res.errorCode === ErrorCode.Ok;
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Return paths
|
|
529
|
+
|
|
530
|
+
- Client query → không truyền `userId` nên backend resolve inventory của game player đang authenticate.
|
|
531
|
+
- Server grant → nếu `secretKey` sai/thiếu → `ReturnCode.SecretInvalid`; nếu secret hợp lệ nhưng thiếu flag → `ReturnCode.OperationNotAllow`.
|
|
532
|
+
|
|
533
|
+
### Pitfalls
|
|
534
|
+
|
|
535
|
+
- NÊN gọi `.server` trong backend Node service hoặc CloudScript.
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Scenario 11: Store list catalog → purchase → validate receipt
|
|
540
|
+
|
|
541
|
+
**Problem:** User mở shop → chọn item → mua → xác nhận nhận hàng.
|
|
542
|
+
|
|
543
|
+
**Prerequisites:** đã login.
|
|
544
|
+
|
|
545
|
+
**APIs used:** [`GNNetwork.storeInventory.getStoreItemsWithTagAsync`](reference/API_STORE_INVENTORY.md), `buyStoreItemAsync`, `server.validateGooglePlayStoreReceiptAsync` / `server.validateAppleAppStoreReceiptAsync`.
|
|
546
|
+
|
|
547
|
+
### Code
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
import { GNNetwork, ReturnCode, ErrorCode, OwnerType, StoreInventoryModels } from "@xmobitea/gn-typescript-client";
|
|
551
|
+
|
|
552
|
+
async function listCatalog() {
|
|
553
|
+
const infoRequestParam = new StoreInventoryModels.InfoRequestParam();
|
|
554
|
+
infoRequestParam.displayName = true;
|
|
555
|
+
infoRequestParam.tags = true;
|
|
556
|
+
|
|
557
|
+
const request = new StoreInventoryModels.GetStoreItemsWithTagRequestData();
|
|
558
|
+
request.key = "visible";
|
|
559
|
+
request.value = "true";
|
|
560
|
+
request.infoRequestParam = infoRequestParam;
|
|
561
|
+
request.skip = 0;
|
|
562
|
+
request.limit = 50;
|
|
563
|
+
|
|
564
|
+
const res = await GNNetwork.storeInventory.getStoreItemsWithTagAsync(request);
|
|
565
|
+
if (res.returnCode !== ReturnCode.Ok || res.errorCode !== ErrorCode.Ok) return [];
|
|
566
|
+
return res.responseData.results ?? [];
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function buyItem(storeItemId: string) {
|
|
570
|
+
const request = new StoreInventoryModels.BuyStoreItemRequestData();
|
|
571
|
+
request.storeId = storeItemId;
|
|
572
|
+
request.id = GNNetwork.getAuthenticateStatus().getUserId();
|
|
573
|
+
request.type = OwnerType.GamePlayer;
|
|
574
|
+
request.log = "shop_purchase";
|
|
575
|
+
|
|
576
|
+
const res = await GNNetwork.storeInventory.buyStoreItemAsync(request);
|
|
577
|
+
|
|
578
|
+
if (res.returnCode !== ReturnCode.Ok) return "transport_error";
|
|
579
|
+
switch (res.errorCode) {
|
|
580
|
+
case ErrorCode.Ok:
|
|
581
|
+
return "ok";
|
|
582
|
+
case ErrorCode.NotEnoughCurrency:
|
|
583
|
+
return "not_enough_currency";
|
|
584
|
+
case ErrorCode.StoreItemRemoved:
|
|
585
|
+
case ErrorCode.CanNotBuyThisStoreItem:
|
|
586
|
+
return "not_buyable";
|
|
587
|
+
default:
|
|
588
|
+
return "unhandled_" + res.errorCode;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Return paths
|
|
594
|
+
|
|
595
|
+
- `ErrorCode.Ok` → item đã grant vào inventory player; currency đã trừ.
|
|
596
|
+
- `ErrorCode.NotEnoughCurrency` (8) → cần topup.
|
|
597
|
+
- `ErrorCode.StoreItemRemoved` hoặc `ErrorCode.CanNotBuyThisStoreItem` → item không còn mua được ở state hiện tại.
|
|
598
|
+
|
|
599
|
+
### Pitfalls
|
|
600
|
+
|
|
601
|
+
- In-app purchase (IAP) có receipt native — validate qua method cụ thể theo store, ví dụ `storeInventory.server.validateGooglePlayStoreReceiptAsync` hoặc `validateAppleAppStoreReceiptAsync`, trước khi tin kết quả grant.
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Scenario 12: CloudScript execute function + customTags
|
|
606
|
+
|
|
607
|
+
**Problem:** Trigger function server-side custom (ví dụ "claim daily reward") từ client.
|
|
608
|
+
|
|
609
|
+
**Prerequisites:** đã login; CloudScript function đã publish ở backend.
|
|
610
|
+
|
|
611
|
+
**APIs used:** [`GNNetwork.cloudScript.executeFunctionAsync`](reference/API_CLOUDSCRIPT.md).
|
|
612
|
+
|
|
613
|
+
### Code
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
import { GNNetwork, ReturnCode, ErrorCode, CloudScriptModels, ExecuteResponseStatus, GNHashtable } from "@xmobitea/gn-typescript-client";
|
|
617
|
+
|
|
618
|
+
async function claimDailyReward() {
|
|
619
|
+
const request = new CloudScriptModels.ExecuteFunctionRequestData();
|
|
620
|
+
request.functionName = "claimDailyReward";
|
|
621
|
+
request.functionParameters = { date: new Date().toISOString().slice(0, 10) };
|
|
622
|
+
|
|
623
|
+
const res = await GNNetwork.cloudScript.executeFunctionAsync(
|
|
624
|
+
request,
|
|
625
|
+
undefined, // overrideAuthToken
|
|
626
|
+
undefined, // overrideSecretKey
|
|
627
|
+
GNHashtable.builder().add("source", "daily_popup").build(), // customTags
|
|
628
|
+
30, // timeout (seconds)
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
if (res.returnCode !== ReturnCode.Ok || res.errorCode !== ErrorCode.Ok) return null;
|
|
632
|
+
|
|
633
|
+
const exec = res.responseData;
|
|
634
|
+
if (exec.status !== ExecuteResponseStatus.Ok) {
|
|
635
|
+
console.warn("function failed", exec.errorMessage);
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
return exec.functionResult; // raw payload từ function
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Return paths
|
|
643
|
+
|
|
644
|
+
- `ExecuteResponseStatus.Ok` → function chạy xong, `functionResult` là value function trả về.
|
|
645
|
+
- `ExecuteResponseStatus.Exception` → function throw; `exec.errorMessage` chứa message.
|
|
646
|
+
- `ExecuteResponseStatus.FunctionNameNotFound` → function chưa publish/export hoặc sai tên.
|
|
647
|
+
|
|
648
|
+
### Pitfalls
|
|
649
|
+
|
|
650
|
+
- `customTags` là `GNHashtable`; dùng `GNHashtable.builder()` để giữ đúng public type. Giá trị nên là primitive hoặc string; không nhét object lồng.
|
|
651
|
+
- Timeout function mặc định 20s — function nặng (query DB lớn) nên tăng timeout param.
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## Scenario 13: Override authToken / secret (impersonate, cross-role)
|
|
656
|
+
|
|
657
|
+
**Problem:** Đang ở role admin/server, cần gọi API như một player cụ thể (impersonate) hoặc override secret key của request cụ thể.
|
|
658
|
+
|
|
659
|
+
**APIs used:** mọi method — async form nhận `overrideAuthToken`, `overrideSecretKey` là param thứ 2-3; callback form là param thứ 3-4 vì có `onResponse`.
|
|
660
|
+
|
|
661
|
+
### Code
|
|
662
|
+
|
|
663
|
+
```typescript
|
|
664
|
+
import { GNNetwork, ContentModels, MasterPlayerModels } from "@xmobitea/gn-typescript-client";
|
|
665
|
+
|
|
666
|
+
// Impersonate player để query profile của họ
|
|
667
|
+
async function fetchProfileAs(playerAuthToken: string, playerId: string) {
|
|
668
|
+
const infoRequestParam = new MasterPlayerModels.InfoRequestParam();
|
|
669
|
+
infoRequestParam.displayName = true;
|
|
670
|
+
infoRequestParam.avatar = true;
|
|
671
|
+
|
|
672
|
+
const request = new MasterPlayerModels.GetPlayerInformationRequestData();
|
|
673
|
+
request.userId = playerId;
|
|
674
|
+
request.infoRequestParam = infoRequestParam;
|
|
675
|
+
|
|
676
|
+
const res = await GNNetwork.masterPlayer.getPlayerInformationAsync(
|
|
677
|
+
request,
|
|
678
|
+
playerAuthToken, // overrideAuthToken — thay token cache
|
|
679
|
+
undefined, // overrideSecretKey — giữ secret key mặc định của role
|
|
680
|
+
undefined, // customTags
|
|
681
|
+
undefined, // timeout
|
|
682
|
+
);
|
|
683
|
+
return res.responseData;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Dùng secret key khác (ví dụ một game khác cùng backend)
|
|
687
|
+
async function callOtherGame(gameSecret: string) {
|
|
688
|
+
const request = new ContentModels.GetContentDataRequestData();
|
|
689
|
+
request.keys = ["config"];
|
|
690
|
+
|
|
691
|
+
return GNNetwork.content.getContentDataAsync(
|
|
692
|
+
request,
|
|
693
|
+
undefined,
|
|
694
|
+
gameSecret,
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Return paths
|
|
700
|
+
|
|
701
|
+
- Token override bypass cache; request này KHÔNG update cached token.
|
|
702
|
+
- Secret override bypass `secretKey` mặc định.
|
|
703
|
+
|
|
704
|
+
### Pitfalls
|
|
705
|
+
|
|
706
|
+
- Override không persist — mỗi request phải truyền lại.
|
|
707
|
+
- Truyền `overrideAuthToken = ""` (rỗng) → request đi KHÔNG CÓ auth header, thường fail với `ReturnCode.OperationNotAuthorized`. Muốn dùng auth cache hiện tại thì truyền `null`/`undefined`.
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Scenario 14: Admin login qua DashboardApi
|
|
712
|
+
|
|
713
|
+
**Problem:** Tool dashboard web — cần admin login bằng username/password admin, rồi query game list / analytics.
|
|
714
|
+
|
|
715
|
+
**Prerequisites:** đã init; biết admin username/password backend cấp.
|
|
716
|
+
|
|
717
|
+
**APIs used:** [`GNNetwork.dashboard.loginByAdminAccountAsync`](reference/API_DASHBOARD.md), `getGameListAsync`, `getAnalyticsAsync`, …
|
|
718
|
+
|
|
719
|
+
> **Lưu ý thiết kế:** Dashboard API CHỈ có namespace client (`GNNetwork.dashboard`, không có `.server` / `.admin`). Transport dùng `RequestRole.Client`. Admin context backend resolve TỪ auth token trả về sau `loginByAdminAccount` — token này khác token của player thường.
|
|
720
|
+
|
|
721
|
+
### Code
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
import { GNNetwork, ReturnCode, ErrorCode, DashboardModels } from "@xmobitea/gn-typescript-client";
|
|
725
|
+
|
|
726
|
+
async function adminLogin(username: string, password: string) {
|
|
727
|
+
const request = new DashboardModels.LoginByAdminAccountRequestData();
|
|
728
|
+
request.username = username;
|
|
729
|
+
request.password = password;
|
|
730
|
+
|
|
731
|
+
const res = await GNNetwork.dashboard.loginByAdminAccountAsync(request);
|
|
732
|
+
if (res.returnCode !== ReturnCode.Ok || res.errorCode !== ErrorCode.Ok) return false;
|
|
733
|
+
|
|
734
|
+
// Dashboard response carries authToken; SDK caches it for later dashboard calls.
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
async function listGames() {
|
|
739
|
+
const res = await GNNetwork.dashboard.getGameListAsync(new DashboardModels.GetGameListRequestData());
|
|
740
|
+
return res.responseData?.games ?? [];
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
### Return paths
|
|
745
|
+
|
|
746
|
+
- Happy → admin token được SDK cache; mọi dashboard method sau đó dùng token này.
|
|
747
|
+
- `ErrorCode.AccountNotFound` / `ErrorCode.AccountPasswordWrong` → giống player login.
|
|
748
|
+
|
|
749
|
+
### Pitfalls
|
|
750
|
+
|
|
751
|
+
- Trộn admin token với player flow → backend reject. Sau admin logout, clear token trước khi login player.
|
|
752
|
+
- Dashboard KHÔNG cần socket — có thể set `useSocket: false` trong settings cho web dashboard.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## Scenario 15: Cleanup & shutdown flow
|
|
757
|
+
|
|
758
|
+
**Problem:** User logout hoặc app shutdown — cần close socket + clear auth để session sau không leak state.
|
|
759
|
+
|
|
760
|
+
### Code
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
import { GNNetwork, AuthenticateStatus, OnGroupMessageUpdateEventHandler, OnGroupMemberUpdateEventHandler, OnGamePlayerFriendUpdateEventHandler, OnGamePlayerGroupUpdateEventHandler, OnCharacterPlayerFriendUpdateEventHandler, OnCharacterPlayerGroupUpdateEventHandler } from "@xmobitea/gn-typescript-client";
|
|
764
|
+
|
|
765
|
+
function logoutAndCleanup() {
|
|
766
|
+
// 1. Clear all event handler callbacks (chúng là static, không tự gc)
|
|
767
|
+
const noop = () => {};
|
|
768
|
+
OnGroupMessageUpdateEventHandler.onUpdate = noop;
|
|
769
|
+
OnGroupMemberUpdateEventHandler.onUpdate = noop;
|
|
770
|
+
OnGamePlayerFriendUpdateEventHandler.onUpdate = noop;
|
|
771
|
+
OnGamePlayerGroupUpdateEventHandler.onUpdate = noop;
|
|
772
|
+
OnCharacterPlayerFriendUpdateEventHandler.onUpdate = noop;
|
|
773
|
+
OnCharacterPlayerGroupUpdateEventHandler.onUpdate = noop;
|
|
774
|
+
|
|
775
|
+
// 2. Close socket
|
|
776
|
+
GNNetwork.disconnectSocket(() => {
|
|
777
|
+
console.log("socket closed");
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
// 3. Clear auth state
|
|
781
|
+
const empty = new AuthenticateStatus();
|
|
782
|
+
empty.setAuthToken("");
|
|
783
|
+
empty.setUserId("");
|
|
784
|
+
GNNetwork.setNewAuthenticateStatus(empty);
|
|
785
|
+
}
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
### Return paths
|
|
789
|
+
|
|
790
|
+
- `disconnectSocket` callback fire khi connection closed. Sau đó socket không auto-reconnect (stop reconnect loop).
|
|
791
|
+
- `setNewAuthenticateStatus` với token rỗng xóa state + localStorage entry.
|
|
792
|
+
|
|
793
|
+
### Pitfalls
|
|
794
|
+
|
|
795
|
+
- Không clear event handler → new session fire vào callback cũ trỏ tới UI component đã unmount, gây crash.
|
|
796
|
+
- Không đợi `disconnectSocket` callback mà shutdown process ngay → socket có thể không gửi packet `logout` tới server → backend phải đợi timeout để cleanup session.
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## Scenario 16: Diagnose `OperationNotAllow`
|
|
801
|
+
|
|
802
|
+
**Problem:** Một API call trả `ReturnCode.OperationNotAllow`. Cần xác định root cause và fix.
|
|
803
|
+
|
|
804
|
+
**Prerequisites:** biết method đang gọi, namespace (`client` / `.server` / `.admin`), target user (nếu có), secret key đang dùng.
|
|
805
|
+
|
|
806
|
+
**Key insight:** `OperationNotAllow` **không** phải thiếu secret hoặc operation không tồn tại (đó là `SecretInvalid` và `OperationInvalid`). Đây là: secret hợp lệ, operation tồn tại, nhưng **permission rule** của secret không cho phép operation trong target context. Xem [RULES § 5](RULES.md#5-phân-biệt-3-returncode-dễ-nhầm).
|
|
807
|
+
|
|
808
|
+
### Decision tree
|
|
809
|
+
|
|
810
|
+
```
|
|
811
|
+
Gặp returnCode === OperationNotAllow
|
|
812
|
+
│
|
|
813
|
+
├─ Bước 1: Xác định route đang gọi
|
|
814
|
+
│ • GNNetwork.<group>.<method>(...) → route = client
|
|
815
|
+
│ • GNNetwork.<group>.server.<method>(...) → route = server
|
|
816
|
+
│ • GNNetwork.<group>.admin.<method>(...) → route = admin
|
|
817
|
+
│
|
|
818
|
+
├─ Bước 2: Xác định target (chỉ áp dụng route client)
|
|
819
|
+
│ • Bỏ userId HOẶC userId trùng auth → target = self
|
|
820
|
+
│ • userId khác auth user → target = other-self
|
|
821
|
+
│ • Domain không có ownership (Content,
|
|
822
|
+
│ StoreInventory, Multiplayer, CloudScript) → bỏ qua bước 2
|
|
823
|
+
│
|
|
824
|
+
├─ Bước 3: Map route + target → flag cần (RULES § 6.1)
|
|
825
|
+
│ • client + self → selfEnable
|
|
826
|
+
│ • client + other-self → otherSelfEnable
|
|
827
|
+
│ • server → serverSelfEnable
|
|
828
|
+
│ • admin → adminSelfEnable
|
|
829
|
+
│
|
|
830
|
+
├─ Bước 4: Check secret active có flag đó cho operation đang gọi không?
|
|
831
|
+
│ → xem dashboard GearN Server hoặc hỏi backend admin
|
|
832
|
+
│ → danh sách operation: reference/PERMISSION_RULES.md + API_<GROUP>.md
|
|
833
|
+
│
|
|
834
|
+
└─ Bước 5: Nếu không có flag, chọn 1 trong 3 action:
|
|
835
|
+
(a) Dùng secret khác qua GNServerSettings.secretKey (cần restart process)
|
|
836
|
+
(b) Override per-request: truyền overrideSecretKey vào method
|
|
837
|
+
(c) Yêu cầu backend admin bật flag trong GearN Dashboard tương ứng cho secret hiện tại
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### Code
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
import {
|
|
844
|
+
GNNetwork,
|
|
845
|
+
ReturnCode,
|
|
846
|
+
InventoryModels,
|
|
847
|
+
} from "@xmobitea/gn-typescript-client";
|
|
848
|
+
|
|
849
|
+
async function grantItem(userId: string, itemId: string, amount: number) {
|
|
850
|
+
const req = new InventoryModels.SetAmountRequestData();
|
|
851
|
+
req.itemId = itemId;
|
|
852
|
+
req.amount = amount;
|
|
853
|
+
|
|
854
|
+
// Attempt 1: route admin với secret mặc định của app
|
|
855
|
+
let res = await GNNetwork.inventory.admin.setAmountAsync(req);
|
|
856
|
+
|
|
857
|
+
if (res.returnCode === ReturnCode.OperationNotAllow) {
|
|
858
|
+
console.warn("OperationNotAllow on admin route — secret thiếu adminSelfEnable cho inventory.setAmount");
|
|
859
|
+
console.warn("debugMessage:", res.debugMessage);
|
|
860
|
+
|
|
861
|
+
// Attempt 2: fallback override secret admin cho 1 request
|
|
862
|
+
const ADMIN_SECRET = process.env.GEARN_ADMIN_SECRET; // KHÔNG hardcode
|
|
863
|
+
if (ADMIN_SECRET) {
|
|
864
|
+
res = await GNNetwork.inventory.admin.setAmountAsync(
|
|
865
|
+
req,
|
|
866
|
+
undefined, // overrideAuthToken
|
|
867
|
+
ADMIN_SECRET, // overrideSecretKey
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (res.returnCode !== ReturnCode.Ok) {
|
|
873
|
+
// Vẫn fail sau fallback → báo ops, KHÔNG retry
|
|
874
|
+
throw new Error(`grant failed: returnCode=${res.returnCode}, debug=${res.debugMessage}`);
|
|
875
|
+
}
|
|
876
|
+
return res.responseData;
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### Return paths
|
|
881
|
+
|
|
882
|
+
- `OperationNotAllow` → **không retry cùng secret**, đó là config fix.
|
|
883
|
+
- Sau fallback `overrideSecretKey` vẫn `OperationNotAllow` → secret admin cũng thiếu flag, phải liên hệ backend admin thêm flag trong GearN Dashboard.
|
|
884
|
+
- Nếu chuyển sang `SecretInvalid` sau fallback → override secret sai format / không khớp game context (khác lỗi, xem [RULES § 5](RULES.md#5-phân-biệt-3-returncode-dễ-nhầm)).
|
|
885
|
+
|
|
886
|
+
### Pitfalls
|
|
887
|
+
|
|
888
|
+
- **Nhầm `OperationNotAllow` với `SecretInvalid`** → sửa sai hướng, đổi secret vô nghĩa khi rule chưa được bật.
|
|
889
|
+
- **Dùng `.server` / `.admin` từ frontend app để vượt permission** → trust boundary leak, secret bị expose. Route phải match caller thực tế (xem [RULES § 3](RULES.md#3-route--trust-boundary-của-caller)).
|
|
890
|
+
- **Retry tự động khi gặp `OperationNotAllow`** → không bao giờ giải quyết được, chỉ tốn request budget.
|
|
891
|
+
- **Giả định rule `selfEnable` tự bật** → backend admin config per-operation, không default bật tất cả.
|
|
892
|
+
- **Không check self vs other-self khi ở route `client`** → ví dụ gọi `masterPlayer.getPlayerInformationAsync(otherId)` mà nghĩ vẫn là self → sai flag → debug lâu.
|
|
893
|
+
|
|
894
|
+
### Liên kết
|
|
895
|
+
|
|
896
|
+
- Mapping rule đầy đủ: [RULES § 6.1](RULES.md#61-mapping-route-target--required-flag)
|
|
897
|
+
- Scenario điển hình (5 case): [RULES § 6.3](RULES.md#63-scenario-điển-hình-trigger-operationnotallow)
|
|
898
|
+
- Checklist nhanh: [RULES § 6.4](RULES.md#64-checklist-nhanh-khi-gặp-returncodeoperationnotallow)
|
|
899
|
+
- Danh sách operation per-domain: [reference/PERMISSION_RULES.md](reference/PERMISSION_RULES.md)
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## Pattern chung xuyên scenarios
|
|
904
|
+
|
|
905
|
+
| Pattern | Khi nào | Tham khảo |
|
|
906
|
+
|---------|---------|-----------|
|
|
907
|
+
| Check `returnCode` → `errorCode` → đọc `responseData` | Mọi `*Async()` | [ERROR_HANDLING.md](reference/ERROR_HANDLING.md) |
|
|
908
|
+
| SDK auto-cache `authToken` / `userId` sau authenticate success | Login/refresh player; dashboard login cache `authToken` | Scenario 2, 3, 4, 14 |
|
|
909
|
+
| Subscribe event handler trước `connectSocket`; chỉ gọi `sendRequestAuthSocket` khi cần re-auth thủ công | Realtime | Scenario 5, 6, 7, 8 |
|
|
910
|
+
| `.server` / `.admin` chỉ ở backend, không bao giờ ở frontend | Mọi cross-role call | Scenario 10, 13 |
|
|
911
|
+
| `customTags` để backend log/route | Khi cần phân loại source | Scenario 12 |
|
|
912
|
+
| Clear event handler + `disconnectSocket` + `setNewAuthenticateStatus(empty)` khi logout | Unload/logout | Scenario 15 |
|