fix zgame laluparty launch params

This commit is contained in:
zhx 2026-06-29 19:44:25 +08:00
parent e5654e9b7c
commit 68cabb493d
3 changed files with 35 additions and 30 deletions

View File

@ -301,6 +301,10 @@ func (s *Service) LaunchGame(ctx context.Context, command LaunchCommand) (Launch
}
token = "gt_" + randomHex(24)
}
if strings.EqualFold(game.AdapterType, gamedomain.AdapterZGameV1) {
// ZGame 文档要求 js_code 是客户服务器派发的短期凭证;不能把 App access token 泄漏到三方 H5 URL。
token = "gt_" + randomHex(24)
}
sessionTTL := s.config.LaunchSessionTTL
// 三方游戏会在 H5 运行期间持续回调 token厂商适配器可以把默认 15 分钟启动 TTL 拉长。
if strings.EqualFold(game.AdapterType, gamedomain.AdapterYomiV4) {
@ -995,24 +999,15 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
return parsed.String(), nil
}
if strings.EqualFold(game.AdapterType, gamedomain.AdapterZGameV1) {
// ZGame 默认 channelName=zgame_demo 会走 iframe parent postMessageApp WebView 必须切到 wh 模式,让 SDK 从 URL 读取 openId/js_code。
// token/code/js_code/session 四个别名仍指向同一个短期启动令牌,兼容厂商不同入口读取字段
// laluparty 是厂商文档约定通道H5 通过 App 注入的 ThirdPlatformBridge 取用户信息和 login js_code。
// 启动 URL 仍带 open_id/js_code供 App Bridge 复用同一份短期凭证,避免客户端再发起一次派码请求
config := zgameConfigFromPlatform(game)
openID := externalUserID(session, config.UIDMode)
query.Set("channelName", "wh")
query.Set("channel_name", "wh")
query.Set("cv", "Android")
query.Set("openId", openID)
query.Set("channelName", "laluparty")
query.Set("open_id", openID)
query.Set("userId", openID)
query.Set("uid", openID)
query.Set("userName", openID)
query.Set("user_name", openID)
query.Set("balance", "0")
query.Set("token", token)
query.Set("code", token)
query.Set("openId", openID)
query.Set("js_code", token)
query.Set("session", token)
query.Set("jscode", token)
query.Set("gameId", strings.TrimSpace(session.ProviderGameID))
query.Set("game_id", strings.TrimSpace(session.ProviderGameID))
callbackBase := config.CallbackBaseValue()

View File

@ -598,21 +598,19 @@ func TestLaunchGameBuildsZGameURL(t *testing.T) {
if parsed.Scheme+"://"+parsed.Host+parsed.Path != "https://zgame.example/h5/1001" {
t.Fatalf("zgame launch url must use per-game configured url: %s", result.LaunchURL)
}
jsCode := parsed.Query().Get("js_code")
if jsCode == "" || !strings.HasPrefix(jsCode, "gt_") || jsCode == token {
t.Fatalf("zgame launch must expose a scoped js_code instead of app token: %q", jsCode)
}
if repo.session.LaunchTokenHash != stableHash(jsCode) {
t.Fatalf("zgame launch session must be bound to scoped js_code")
}
expected := map[string]string{
"channelName": "wh",
"channel_name": "wh",
"cv": "Android",
"channelName": "laluparty",
"openId": "420001",
"open_id": "420001",
"userId": "420001",
"uid": "420001",
"userName": "420001",
"user_name": "420001",
"balance": "0",
"token": token,
"code": token,
"js_code": token,
"session": token,
"js_code": jsCode,
"jscode": jsCode,
"gameId": "1001",
"game_id": "1001",
"callbackBase": "https://api.example.test/api/v1/zgame/callback",
@ -632,6 +630,11 @@ func TestLaunchGameBuildsZGameURL(t *testing.T) {
t.Fatalf("zgame launch query %s mismatch: got %q want %q url=%s", key, got, want, result.LaunchURL)
}
}
for _, key := range []string{"token", "code", "session", "cv", "channel_name"} {
if got := parsed.Query().Get(key); got != "" {
t.Fatalf("zgame launch query must not expose %s=%q url=%s", key, got, result.LaunchURL)
}
}
}
func TestLaunchGameRequiresBaishunLaunchURL(t *testing.T) {

View File

@ -59,6 +59,10 @@ func zgameConfigFromPlatform(value any) zgameAdapterConfig {
var config zgameAdapterConfig
_ = json.Unmarshal([]byte(strings.TrimSpace(raw)), &config)
config.UIDMode = strings.ToLower(strings.TrimSpace(config.UIDMode))
if config.UIDMode == "" {
// laluparty 游戏侧展示和签名都用短号作为 open_id历史配置未写 uid_mode 时按文档默认走 display_user_id。
config.UIDMode = "display_user_id"
}
config.DefaultLang = strings.TrimSpace(config.DefaultLang)
config.LaunchURLTemplate = strings.TrimSpace(config.LaunchURLTemplate)
config.CallbackBase = strings.TrimSpace(firstNonEmpty(config.CallbackBase, config.CallbackBaseAlias))
@ -159,7 +163,7 @@ func (s *Service) handleZGameLogin(ctx context.Context, app string, config zgame
if err != nil {
return zgameAdapterError(zgameCodeFromError(err), zgameMessageFromError(err), true, "")
}
raw, contentType := zgameJSON(zgameCodeOK, "success", zgameLoginData(session, config, jsCode, snapshot, s.now()))
raw, contentType := zgameJSON(zgameCodeOK, "success", zgameLoginData(session, config, openID, jsCode, snapshot, s.now()))
return raw, contentType, strconv.Itoa(zgameCodeOK), true, "", nil
}
@ -339,7 +343,7 @@ func (s *Service) validZGameSession(ctx context.Context, app string, platformCod
if token == "" {
return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "session invalid")
}
// 当前 ZGame 不要求 App 原生生成一次性 jscode优先消费 App access token远程桥接脚本即可免发版接入
// 新版启动链路使用服务端短期 js_code保留 JWT 兼容旧测试链接,避免已打开的 H5 立即失效
if strings.Count(token, ".") == 2 {
if session, err := s.appSessionFromAccessToken(app, token); err == nil {
return session, nil
@ -377,8 +381,11 @@ func zgameSignatureValid(openID string, session string, signature string, secret
return subtle.ConstantTimeCompare([]byte(strings.ToLower(signature)), []byte(expected)) == 1
}
func zgameLoginData(session gamedomain.LaunchSession, config zgameAdapterConfig, token string, snapshot yomiUserSnapshot, now time.Time) map[string]any {
openID := externalUserID(session, config.UIDMode)
func zgameLoginData(session gamedomain.LaunchSession, config zgameAdapterConfig, requestedOpenID string, token string, snapshot yomiUserSnapshot, now time.Time) map[string]any {
openID := strings.TrimSpace(requestedOpenID)
if openID == "" {
openID = externalUserID(session, config.UIDMode)
}
return map[string]any{
"session": token,
"session_expired": zgameSessionTTLMS(session, now),