{"record":{"id":"0ba4f008bf7d608e","repo":"tonhowtf/omniget","slug":"spotify-sdk-device-not-ready","errorCode":null,"errorMessage":"Spotify SDK device not ready","messagePattern":"Spotify SDK device not ready","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/study-music/spotify-sdk.svelte.ts","lineNumber":238,"sourceCode":"          };\n          document.head.appendChild(script);\n        }\n      })();\n    }).catch((e) => {\n      this.loadingPromise = null;\n      throw e;\n    });\n\n    return this.loadingPromise;\n  }\n\n  async play(opts: {\n    uris?: string[];\n    contextUri?: string;\n    positionMs?: number;\n  }): Promise<void> {\n    await this.ensureLoaded();\n    if (!this.deviceId) throw new Error(\"Spotify SDK device not ready\");\n    await pluginInvoke(\"study\", \"study:spotify:playback:play\", {\n      deviceId: this.deviceId,\n      uris: opts.uris ?? [],\n      contextUri: opts.contextUri,\n      positionMs: opts.positionMs,\n    });\n  }\n\n  async pause(): Promise<void> {\n    if (this.player) await this.player.pause();\n  }\n\n  async resume(): Promise<void> {\n    if (this.player) await this.player.resume();\n  }\n\n  async togglePlay(): Promise<void> {\n    if (this.player) await this.player.togglePlay();","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src/lib/study-music/spotify-sdk.svelte.ts#L220-L256","documentation":"Raised by fetch_access_token when the playbackAccessToken object lacks a string 'value' field. The value is the opaque token payload embedded in authenticated clip URLs; without it the library cannot construct the download URL, so it returns an error after having already extracted the signature.","triggerScenarios":"token_obj.get(\"value\").and_then(|v| v.as_str()) returns None — 'value' missing, null, or not a string inside the playbackAccessToken object.","commonSituations":"GQL schema change to the playbackAccessToken contract; partial/degraded token responses from Twitch for restricted clips; proxy or middleware stripping fields; incorrect manual GQL query omitting the value selector.","solutions":["Log token_obj and verify the GQL query requests the value field inside playbackAccessToken.","Update field names/pointers if Twitch renamed the field.","Check the clip's accessibility (geo/auth restrictions can yield empty token payloads).","Retry once — transient partial tokens do occur — then fail with the payload logged."],"exampleFix":"// before\nlet value = token_obj\n    .get(\"value\")\n    .and_then(|v| v.as_str())\n    .ok_or_else(|| anyhow!(\"Token sem value\"))?\n    .to_string();\n// after\nlet value = token_obj.get(\"value\")\n    .and_then(|v| v.as_str())\n    .ok_or_else(|| anyhow!(\"Token sem value: {}\", token_obj))? // log payload\n    .to_string();","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn value_of(token_obj: &serde_json::Value) -> Option<&str> {\n    token_obj.get(\"value\").and_then(|v| v.as_str()).filter(|v| !v.is_empty())\n}","tryCatchPattern":"// Require both parts of the token before building URLs\nlet (Some(sig), Some(val)) = (signature_of(&t), value_of(&t)) else {\n    anyhow::bail!(\"incomplete playback token: {}\", t);\n};","preventionTips":["Validate the complete token object (signature + value) before use","Log the token payload on failure to spot schema drift","Keep the GQL query selectors for value/signature in sync with parsing code","Restrict usage to clips confirmed publicly playable"],"tags":["twitch","token","api-response","rust"],"backgroundTag":"unexpected-response-shape","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}