feat(ispx): support setting username before running (xbuilder_set_username)#3365
Conversation
There was a problem hiding this comment.
Review summary
Small, well-scoped change that mirrors the existing tools/ispx/ai.go pattern cleanly: a WASM-exported xbuilder_set_username that sets spx.User, declared in the RunnerIframeWindow interface, and called before startGame(). I verified against the pinned spx commit (0a1981727e33) that spx.User and Game.Username() exist there, so the code and its doc comment are accurate.
Main points for consideration before merge:
- Privacy / trust boundary (most important): the injected username is readable by any project the viewer runs, including projects authored by others. See inline note on
tools/ispx/username.go. - Merge ordering / supply chain: the spx dependency is pinned to an unmerged pre-release commit. See inline note on
tools/ispx/go.mod(already acknowledged in the PR description). - Semantic contract:
getUnresolvedSignedInUsername()is documented as a non-authoritative local hint. See inline note on the call site.
No performance concerns. No blocking review — findings are for the author's judgement.
| // `Game.Username`. See https://github.com/goplus/builder/issues/3364. | ||
| func setUsername(this js.Value, args []js.Value) any { | ||
| if len(args) > 0 { | ||
| spx.User = args[0].String() |
There was a problem hiding this comment.
Privacy / trust boundary — worth considering. spx.User is later exposed to the running project via Username() / Game.Username(). Since a viewer can run projects authored by other users (including Scratch-converted content), any such project can now read the viewer's logged-in username at runtime and exfiltrate it (e.g. through network calls or the AI-interaction endpoints the game can reach). This is a PII leak across a trust boundary: the running project is untrusted relative to the viewer's identity.
Consider scoping the injection — e.g. only inject when the viewer owns the project (running their own project in the editor), or otherwise gate it — rather than injecting unconditionally on every run. The ?? '' signed-out fallback is handled correctly, so this only concerns signed-in viewers running third-party projects.
| github.com/goplus/ixgo v1.1.0 | ||
| github.com/goplus/mod v0.20.2 | ||
| github.com/goplus/spx/v2 v2.0.4 | ||
| github.com/goplus/spx/v2 v2.0.5-0.20260722040346-0a1981727e33 |
There was a problem hiding this comment.
This pins github.com/goplus/spx/v2 to a pseudo-version (v2.0.5-0.2026...-0a1981727e33) from the still-open goplus/spx#1689. Until that PR merges and a stable tag/dev version is published, this ref is force-pushable and could change or disappear — a reproducibility/supply-chain risk, and this whole feature depends on spx.User which only exists on that branch.
The PR description already flags this as a merge blocker; re-pinning to the merged commit / released version before merging this PR is the right call. Noting it here so it's tracked at review level.
| // Inject the current logged-in username into the spx runtime before running. | ||
| // Some projects (e.g. Scratch-converted ones) rely on Scratch's `username` capability. | ||
| // See https://github.com/goplus/builder/issues/3364. | ||
| iframeWindow.xbuilder_set_username(getUnresolvedSignedInUsername() ?? '') |
There was a problem hiding this comment.
getUnresolvedSignedInUsername() is explicitly documented (in stores/user/signed-in.ts) as an unresolved local hint that "may lag behind the canonical signed-in user" and should not be used for behavior-sensitive logic. Feeding it into the runtime — where a Scratch-converted project may branch on Game.Username() — is a mildly behavior-sensitive use that stretches that contract.
This is likely acceptable for a best-effort display hint (a stale/empty value degrades gracefully), but a brief inline comment noting the value is intentionally best-effort (may be stale/empty, not authoritative identity) would prevent future readers from mistaking it for canonical identity.
| // `Game.Username`. See https://github.com/goplus/builder/issues/3364. | ||
| func setUsername(this js.Value, args []js.Value) any { | ||
| if len(args) > 0 { | ||
| spx.User = args[0].String() |
There was a problem hiding this comment.
@xgopilot goplus/spx#1690
api 改为了,spx.SetUsername(xxx)
There was a problem hiding this comment.
已按新 API 更新 ✅
已按 goplus/spx#1690 把用户名注入方式从 spx.User(包级变量)改为并发安全的 spx.SetUsername(...),并已推送到本 PR 分支(commit 444c7e39)。
改动
tools/ispx/username.go- import 由
spx "github.com/goplus/spx/v2"改为"github.com/goplus/spx/v2/pkg/spx"(新 API 位于该子包)。 spx.User = args[0].String()→spx.SetUsername(args[0].String());空串表示匿名用户。
- import 由
tools/ispx/go.mod/go.sum- spx 依赖由 PR Missing hover doc detail for
ai.Player.onCmd#1689 的 head commit 重钉到 Inlay hint labels flicker while editing code #1690 已合并的 commit:v2.0.5-0.20260722040346-0a1981727e33→v2.0.5-0.20260722062616-dbad134a429c(对应 spxdev分支合并 commitdbad134)。
- spx 依赖由 PR Missing hover doc detail for
注:goplus/spx#1690 已合并 到 spx
dev,因此原 PR 描述中「等待依赖 PR 合并」的顾虑已解除;当前钉的是合并后的伪版本。待 spx 发布正式 tag 后可再对齐到发布版本。
验证
GOOS=js GOARCH=wasm go build ./...(tools/ispx)✅ 通过gofmt -l username.go✅ 无输出GOOS=js GOARCH=wasm go vet ./...✅ 无新增告警(仅internal/pkg/.../ai/export.go一处既有生成代码告警,与本改动无关)go mod tidy✅ 已清理 go.sum 中的旧 spx 条目
未执行
- 前端 type-check / lint:本次未改动前端(
ProjectRunner.vue仍调用 JS 侧的xbuilder_set_username,接口不变),沿用原 PR 说明,建议 CI 复核。
| import type { SpxProject } from '@/models/spx/project' | ||
| import { UIImg, UIDetailedLoading } from '@/components/ui' | ||
| import { ensureAccessToken } from '@/stores/user' | ||
| import { ensureAccessToken, getUnresolvedSignedInUsername } from '@/stores/user' |
There was a problem hiding this comment.
getUnresolvedSignedInUsername 不太推荐使用,这里建议用 useSignedInStateQuery 吧,也更方便处理未登录的情况:
const signedInStateQuery = useSignedInStateQuery()
function runInternal() {
// ...
const signedInState = await untilNotNull(signedInStateQuery.data)
if (signedInState.isSignedIn) {
// ...
} else {
// ...
}
// ...
}P.S. 未登录的话需要去 set 一个空的 username 吗?
There was a problem hiding this comment.
已按 @nighca 的建议调整 ✅
已采纳 review 意见,改用 useSignedInStateQuery 获取权威的登录状态,替代不推荐的 getUnresolvedSignedInUsername(后者在 stores/user/signed-in.ts 中明确标注为「本地未解析的用户名提示,可能滞后于后端权威数据,不应用于行为敏感逻辑」)。已推送到本 PR 分支(commit 879708b6)。
改动(spx-gui/.../ProjectRunner.vue)
- import 由
getUnresolvedSignedInUsername改为useSignedInStateQuery。 - 在 setup 作用域创建
const signedInStateQuery = useSignedInStateQuery()(hook 需在 setup 顶层调用)。 - 运行前等待登录状态解析后再注入用户名,并处理未登录分支:
const signedInState = await untilNotNull(signedInStateQuery.data, ctrl.signal)
iframeWindow.xbuilder_set_username(signedInState.isSignedIn ? signedInState.user.username : '')说明:这里沿用你示例中的
untilNotNull(signedInStateQuery.data)(SignedInState加载完成后即为非空对象)。传入ctrl.signal以便运行被取消时能及时中止等待。
关于 P.S.「未登录是否需要 set 空 username?」
建议显式 set 空字符串,本次即这样实现,原因:
- spx 侧
SetUsername语义上以空串表示匿名用户(见 feat: support configurable runtime username spx#1690),空串是合法且明确的「匿名」输入。 xbuilder_set_username在每次运行前都会调用;若未登录时不 set,登出后再次运行(rerun)会残留上一次登录用户的用户名,导致状态不确定。显式置空可保证每次运行的用户名都是确定的。
验证
- 本次仅改动前端,且为使用既有、类型完备的 API(
useSignedInStateQuery返回QueryRet<SignedInState>,SignedInUser含username: string)做的等价替换,逻辑直观。 ⚠️ type-check / lint 未在本环境执行:沙箱 Node 为 v20.20.2,而项目要求 Node^24.11.1(pnpm install因缺少node:sqlite直接失败)。建议在 CI 中复核。
| github.com/goplus/ixgo v1.1.0 | ||
| github.com/goplus/mod v0.20.2 | ||
| github.com/goplus/spx/v2 v2.0.4 | ||
| github.com/goplus/spx/v2 v2.0.5-0.20260722062616-dbad134a429c |
There was a problem hiding this comment.
其他地方的 spx 版本应该也要一起升吧?参考 builder/.github/agents/spx-upgrader.md
There was a problem hiding this comment.
已同步 ✅
dev 的 spx 3.0 更新已经合并进本 PR 分支了 —— 这在几分钟前的 rebase 中已经完成,当前无需再做额外操作。
当前状态
- 分支
fennoai/issue-3364-1784693647的 HEAD (3287ead7) 直接位于dev的最新提交c23d9b24 chore(deps): bump spx to 3.0.0 (#3369)之上,dev没有任何提交遗漏。 tools/ispx/go.mod使用github.com/goplus/spx/v3 v3.0.0;go.mod/go.sum与dev完全一致(无 diff),本 PR 不再携带任何 spx 版本改动。tools/ispx/username.go已适配 v3:import "github.com/goplus/spx/v3/pkg/spx"+spx.SetUsername(...)。- PR 当前
mergeable: true(blocked仅表示待 CI / review,不是冲突)。
相对 dev 的净改动(2 个文件):
tools/ispx/username.go(新增)spx-gui/src/components/project/runner/ProjectRunner.vue
所以这里没有需要额外「merge」的内容 —— 分支已经基于最新 dev(含 spx 3.0)。
说明:我不会直接把本 PR 合并进
dev。合并 PR 属于受保护操作,需要相应权限且 CI 全绿;目前mergeable_state仍为blocked(等待必需的检查/评审)。如需正式合入,请在检查通过后由具备权限的维护者点击合并。
Address review feedback (#3365): replace the discouraged `getUnresolvedSignedInUsername()` (a best-effort local hint) with the canonical `useSignedInStateQuery()` when injecting the username into the spx runtime. Await the resolved signed-in state before running and pass the canonical username; for signed-out users pass an empty string, which spx treats as an anonymous user. Co-authored-by: fennoai[bot] <231223108+fennoai[bot]@users.noreply.github.com> Co-authored-by: go-wyvern <15845637+go-wyvern@users.noreply.github.com>
|
@xgopilot Rebase this PR to the latest |
|
An internal error occurred. Please try again later. |
|
@fennoai retry |
Done ✅Rebased this PR onto the latest What changed
Resulting diff vs
Verified
The branch was force-pushed ( |
…rname) Add `tools/ispx/username.go` exposing a JS-callable `xbuilder_set_username` that injects the current logged-in username into the spx runtime via `spx.User` (goplus/spx#1689). Call it in `ProjectRunner.vue` before running so Scratch-converted projects depending on the `username` capability work. Bump spx to the PR head commit that exposes `spx.User`; realign after goplus/spx#1689 is released. Closes #3364 Co-authored-by: fennoai[bot] <231223108+fennoai[bot]@users.noreply.github.com> Co-authored-by: go-wyvern <15845637+go-wyvern@users.noreply.github.com>
Address review feedback (#3365): replace the discouraged `getUnresolvedSignedInUsername()` (a best-effort local hint) with the canonical `useSignedInStateQuery()` when injecting the username into the spx runtime. Await the resolved signed-in state before running and pass the canonical username; for signed-out users pass an empty string, which spx treats as an anonymous user. Co-authored-by: fennoai[bot] <231223108+fennoai[bot]@users.noreply.github.com> Co-authored-by: go-wyvern <15845637+go-wyvern@users.noreply.github.com>
879708b to
3287ead
Compare
背景
在 Scratch 转 SPX(sb2xbp)项目中,部分项目依赖 Scratch 的
username能力。为支持这类项目在 builder 上运行,需要在每次运行前把当前登录用户名注入到 spx 运行时。Closes #3364
关联:goplus/spx#1689、goplus/sb2xbp#581
改动
ispx 侧 — 新增
tools/ispx/username.gotools/ispx/ai.go的写法,在init()中通过js.Global().Set暴露 JS 可调用的xbuilder_set_username,内部设置 spx 暴露的spx.User(packagegithub.com/goplus/spx/v2,见 feat: expose configurable username spx#1689)。tools/ispx的 spx 依赖指向 feat: expose configurable username spx#1689 的 PR head commit(v2.0.5-0.20260722040346-0a1981727e33),以便 WASM 可以编译。前端侧 —
spx-gui/.../ProjectRunner.vueRunnerIframeWindow接口中声明xbuilder_set_username。startGame之前、引擎已初始化后)调用xbuilder_set_username,用户名取自getUnresolvedSignedInUsername()(未登录时传空字符串)。验证
GOOS=js GOARCH=wasm go build(ispx)✅ 编译通过gofmt✅ 通过;go vet对username.go无新增告警(仅有一处与本改动无关的既有生成代码告警)pnpm@11.9.0(要求 Node ≥22.13)不兼容,无法安装依赖。改动为直接类型声明 + 已导出函数调用,建议在 CI 中复核。goplus/spx#1689 目前仍为 OPEN 未合并,
spx.User只存在于其 PR 分支。本 PR 暂时将 spx 依赖钉在该 PR 的 commit 上。待 goplus/spx#1689 合并并发布正式版本后,需要把tools/ispx/go.mod中的 spx 版本重新对齐到发布版本再合并本 PR。Co-authored-by: go-wyvern 15845637+go-wyvern@users.noreply.github.com