cduss Claude Opus 4.6 (1M context) commited on
Commit
73d3dcb
·
1 Parent(s): 33e8c26

Pass OAuth clientId directly to oauthLoginUrl, bypass meta tag

Browse files

The hub library's meta tag lookup was failing on Docker SDK.
Now: fetch clientId from /api/oauth-config, pass it to ReachyMini
constructor, which passes it directly to oauthLoginUrl({ clientId }).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. static/app.js +40 -75
  2. static/reachy-mini.js +5 -2
static/app.js CHANGED
@@ -1,56 +1,37 @@
1
  import { ReachyMini } from "./reachy-mini.js";
2
 
3
- // ─── State ───
4
- const robot = new ReachyMini({ enableMicrophone: false });
5
  const $ = id => document.getElementById(id);
6
 
 
7
  let imageId = null;
8
  let capturedBlob = null;
9
  const selected = new Set();
10
 
11
- let oauthReady = false;
12
-
13
  // ─── Init ───
14
  document.addEventListener("DOMContentLoaded", async () => {
15
- // Ensure OAuth meta tag exists (Docker SDK provides env vars, not meta tags).
16
- // Backend injects it in HTML, but as belt-and-suspenders also fetch from API.
17
- if (!document.querySelector('meta[name="hf-oauth-client-id"]')?.content) {
18
- try {
19
- const res = await fetch("/api/oauth-config");
20
- const cfg = await res.json();
21
- console.log("OAuth config:", cfg);
22
- if (cfg.clientId) {
23
- const m1 = document.createElement("meta");
24
- m1.name = "hf-oauth-client-id";
25
- m1.content = cfg.clientId;
26
- document.head.appendChild(m1);
27
- const m2 = document.createElement("meta");
28
- m2.name = "hf-oauth-scopes";
29
- m2.content = cfg.scopes || "openid profile";
30
- document.head.appendChild(m2);
31
- } else {
32
- console.error("OAUTH_CLIENT_ID env var is not set on this Space.",
33
- "spaceHost:", cfg.spaceHost, "hasOAuth:", cfg.hasOAuth);
34
- }
35
- } catch (e) {
36
- console.warn("Could not fetch OAuth config:", e);
37
- }
38
  }
39
 
40
- oauthReady = !!document.querySelector('meta[name="hf-oauth-client-id"]')?.content;
 
41
 
42
- if (oauthReady && await robot.authenticate()) {
 
 
43
  showApp();
44
- } else if (!oauthReady) {
45
- // No OAuth — skip login, go straight to app with upload-only mode
46
- $("loginScreen").hidden = true;
47
- $("appScreen").hidden = false;
48
- console.warn("OAuth not available — upload-only mode");
49
  }
50
  initEvents();
51
  });
52
 
53
- // ─── Auth (same as hand tracker) ───
54
  function showApp() {
55
  $("loginScreen").hidden = true;
56
  $("robotScreen").hidden = false;
@@ -59,13 +40,7 @@ function showApp() {
59
  }
60
 
61
  function initEvents() {
62
- $("loginBtn").addEventListener("click", async () => {
63
- if (!oauthReady) {
64
- alert("OAuth not configured on this Space. Check OAUTH_CLIENT_ID env var.\nOpen /api/oauth-config to debug.");
65
- return;
66
- }
67
- await robot.login();
68
- });
69
  $("logoutBtn").addEventListener("click", () => {
70
  robot.logout();
71
  $("robotScreen").hidden = true;
@@ -98,17 +73,15 @@ function initEvents() {
98
  if ($("fileInput").files.length) uploadFile($("fileInput").files[0]);
99
  });
100
 
101
- // Param sliders
102
  for (const id of ["paramStrength", "paramLow", "paramHigh", "paramLevel", "paramBlock"]) {
103
- const sl = $(id);
104
- const val = $(id + "Val");
105
  sl.addEventListener("input", () => { val.textContent = sl.value; });
106
  }
107
 
108
  buildFilterGrid();
109
  }
110
 
111
- // ─── Robot connection (same as hand tracker) ───
112
  async function connectSignaling() {
113
  try {
114
  await robot.connect();
@@ -117,22 +90,24 @@ async function connectSignaling() {
117
  }
118
  }
119
 
120
- robot.addEventListener("robotsChanged", (e) => {
121
- const list = e.detail.robots;
122
- const container = $("robotList");
123
- if (list.length === 0) {
124
- container.innerHTML = '<p class="muted">No robots online</p>';
125
- return;
126
- }
127
- container.innerHTML = "";
128
- for (const r of list) {
129
- const btn = document.createElement("button");
130
- btn.className = "robot-btn";
131
- btn.textContent = r.meta?.name || r.id;
132
- btn.addEventListener("click", () => selectRobot(r));
133
- container.appendChild(btn);
134
- }
135
- });
 
 
136
 
137
  async function selectRobot(r) {
138
  $("robotScreen").hidden = true;
@@ -154,12 +129,10 @@ async function selectRobot(r) {
154
  async function captureFrame() {
155
  const video = $("videoEl");
156
  if (!video.videoWidth) return;
157
-
158
  const canvas = $("captureCanvas");
159
  canvas.width = video.videoWidth;
160
  canvas.height = video.videoHeight;
161
  canvas.getContext("2d").drawImage(video, 0, 0);
162
-
163
  capturedBlob = await new Promise(r => canvas.toBlob(r, "image/jpeg", 0.92));
164
  showCapturedImage(capturedBlob);
165
  }
@@ -174,7 +147,6 @@ async function showCapturedImage(blob) {
174
  $("capturedImg").src = url;
175
  $("processedImg").src = url;
176
  $("downloadLink").hidden = true;
177
-
178
  const fd = new FormData();
179
  fd.append("file", blob, "capture.jpg");
180
  try {
@@ -185,7 +157,6 @@ async function showCapturedImage(blob) {
185
  alert("Upload failed: " + e.message);
186
  return;
187
  }
188
-
189
  $("workSection").hidden = false;
190
  selected.clear();
191
  updateFilterUI();
@@ -215,9 +186,8 @@ function buildFilterGrid() {
215
  }
216
 
217
  function updateFilterUI() {
218
- for (const btn of document.querySelectorAll(".filter-btn")) {
219
  btn.classList.toggle("active", selected.has(btn.dataset.id));
220
- }
221
  $("processBtn").disabled = selected.size === 0;
222
  }
223
 
@@ -237,19 +207,14 @@ async function processImage() {
237
  const btn = $("processBtn");
238
  btn.disabled = true;
239
  btn.textContent = "Processing...";
240
-
241
  const params = {
242
- strength: +$("paramStrength").value,
243
- low: +$("paramLow").value,
244
- high: +$("paramHigh").value,
245
- level: +$("paramLevel").value,
246
  block: +$("paramBlock").value,
247
  };
248
-
249
  const fd = new FormData();
250
  fd.append("filters", [...selected].join(","));
251
  fd.append("params", JSON.stringify(params));
252
-
253
  try {
254
  const res = await fetch("/process/" + imageId, { method: "POST", body: fd });
255
  if (!res.ok) throw new Error(await res.text());
 
1
  import { ReachyMini } from "./reachy-mini.js";
2
 
 
 
3
  const $ = id => document.getElementById(id);
4
 
5
+ let robot = null;
6
  let imageId = null;
7
  let capturedBlob = null;
8
  const selected = new Set();
9
 
 
 
10
  // ─── Init ───
11
  document.addEventListener("DOMContentLoaded", async () => {
12
+ // Fetch OAuth client ID from backend (Docker SDK puts it in env vars, not meta tags)
13
+ let clientId = null;
14
+ try {
15
+ const res = await fetch("/api/oauth-config");
16
+ const cfg = await res.json();
17
+ console.log("OAuth config:", cfg);
18
+ clientId = cfg.clientId || null;
19
+ } catch (e) {
20
+ console.warn("Could not fetch OAuth config:", e);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
+ // Create robot with explicit clientId (bypasses meta tag entirely)
24
+ robot = new ReachyMini({ enableMicrophone: false, clientId });
25
 
26
+ attachRobotEvents();
27
+
28
+ if (clientId && await robot.authenticate()) {
29
  showApp();
 
 
 
 
 
30
  }
31
  initEvents();
32
  });
33
 
34
+ // ─── Auth ───
35
  function showApp() {
36
  $("loginScreen").hidden = true;
37
  $("robotScreen").hidden = false;
 
40
  }
41
 
42
  function initEvents() {
43
+ $("loginBtn").addEventListener("click", () => robot.login());
 
 
 
 
 
 
44
  $("logoutBtn").addEventListener("click", () => {
45
  robot.logout();
46
  $("robotScreen").hidden = true;
 
73
  if ($("fileInput").files.length) uploadFile($("fileInput").files[0]);
74
  });
75
 
 
76
  for (const id of ["paramStrength", "paramLow", "paramHigh", "paramLevel", "paramBlock"]) {
77
+ const sl = $(id), val = $(id + "Val");
 
78
  sl.addEventListener("input", () => { val.textContent = sl.value; });
79
  }
80
 
81
  buildFilterGrid();
82
  }
83
 
84
+ // ─── Robot connection ───
85
  async function connectSignaling() {
86
  try {
87
  await robot.connect();
 
90
  }
91
  }
92
 
93
+ function attachRobotEvents() {
94
+ robot.addEventListener("robotsChanged", (e) => {
95
+ const list = e.detail.robots;
96
+ const container = $("robotList");
97
+ if (list.length === 0) {
98
+ container.innerHTML = '<p class="muted">No robots online</p>';
99
+ return;
100
+ }
101
+ container.innerHTML = "";
102
+ for (const r of list) {
103
+ const btn = document.createElement("button");
104
+ btn.className = "robot-btn";
105
+ btn.textContent = r.meta?.name || r.id;
106
+ btn.addEventListener("click", () => selectRobot(r));
107
+ container.appendChild(btn);
108
+ }
109
+ });
110
+ }
111
 
112
  async function selectRobot(r) {
113
  $("robotScreen").hidden = true;
 
129
  async function captureFrame() {
130
  const video = $("videoEl");
131
  if (!video.videoWidth) return;
 
132
  const canvas = $("captureCanvas");
133
  canvas.width = video.videoWidth;
134
  canvas.height = video.videoHeight;
135
  canvas.getContext("2d").drawImage(video, 0, 0);
 
136
  capturedBlob = await new Promise(r => canvas.toBlob(r, "image/jpeg", 0.92));
137
  showCapturedImage(capturedBlob);
138
  }
 
147
  $("capturedImg").src = url;
148
  $("processedImg").src = url;
149
  $("downloadLink").hidden = true;
 
150
  const fd = new FormData();
151
  fd.append("file", blob, "capture.jpg");
152
  try {
 
157
  alert("Upload failed: " + e.message);
158
  return;
159
  }
 
160
  $("workSection").hidden = false;
161
  selected.clear();
162
  updateFilterUI();
 
186
  }
187
 
188
  function updateFilterUI() {
189
+ for (const btn of document.querySelectorAll(".filter-btn"))
190
  btn.classList.toggle("active", selected.has(btn.dataset.id));
 
191
  $("processBtn").disabled = selected.size === 0;
192
  }
193
 
 
207
  const btn = $("processBtn");
208
  btn.disabled = true;
209
  btn.textContent = "Processing...";
 
210
  const params = {
211
+ strength: +$("paramStrength").value, low: +$("paramLow").value,
212
+ high: +$("paramHigh").value, level: +$("paramLevel").value,
 
 
213
  block: +$("paramBlock").value,
214
  };
 
215
  const fd = new FormData();
216
  fd.append("filters", [...selected].join(","));
217
  fd.append("params", JSON.stringify(params));
 
218
  try {
219
  const res = await fetch("/process/" + imageId, { method: "POST", body: fd });
220
  if (!res.ok) throw new Error(await res.text());
static/reachy-mini.js CHANGED
@@ -154,11 +154,12 @@ function sdpHasAudioSendRecv(sdp) {
154
 
155
  export class ReachyMini extends EventTarget {
156
 
157
- /** @param {{ signalingUrl?: string, enableMicrophone?: boolean }} [options] */
158
  constructor(options = {}) {
159
  super();
160
  this._signalingUrl = options.signalingUrl || 'https://cduss-reachy-mini-central.hf.space';
161
  this._enableMicrophone = options.enableMicrophone !== false;
 
162
 
163
  this._state = 'disconnected'; // 'disconnected' | 'connected' | 'streaming'
164
  this._robots = []; // latest robot list from signaling
@@ -268,7 +269,9 @@ export class ReachyMini extends EventTarget {
268
 
269
  /** Redirect the browser to the HuggingFace OAuth login page. */
270
  async login() {
271
- window.location.href = await oauthLoginUrl();
 
 
272
  }
273
 
274
  /** Clear stored HF credentials and disconnect everything. */
 
154
 
155
  export class ReachyMini extends EventTarget {
156
 
157
+ /** @param {{ signalingUrl?: string, enableMicrophone?: boolean, clientId?: string }} [options] */
158
  constructor(options = {}) {
159
  super();
160
  this._signalingUrl = options.signalingUrl || 'https://cduss-reachy-mini-central.hf.space';
161
  this._enableMicrophone = options.enableMicrophone !== false;
162
+ this._clientId = options.clientId || null;
163
 
164
  this._state = 'disconnected'; // 'disconnected' | 'connected' | 'streaming'
165
  this._robots = []; // latest robot list from signaling
 
269
 
270
  /** Redirect the browser to the HuggingFace OAuth login page. */
271
  async login() {
272
+ const opts = {};
273
+ if (this._clientId) opts.clientId = this._clientId;
274
+ window.location.href = await oauthLoginUrl(opts);
275
  }
276
 
277
  /** Clear stored HF credentials and disconnect everything. */