ReverseFaceSearch commited on
Commit
f24eb4d
·
verified ·
1 Parent(s): 71aaa8a

Update script.js

Browse files
Files changed (1) hide show
  1. script.js +115 -4
script.js CHANGED
@@ -2,6 +2,12 @@
2
  "use strict";
3
 
4
  const destination = "https://www.socialsleuth.xyz/face-search";
 
 
 
 
 
 
5
  const uploadCard = document.getElementById("upload-card");
6
  const photoInput = document.getElementById("photo-input");
7
  const uploadEmpty = document.getElementById("upload-empty");
@@ -13,6 +19,8 @@
13
  const startSearch = document.getElementById("start-search");
14
 
15
  let previewUrl = "";
 
 
16
 
17
  const isSupportedImage = (file) => {
18
  if (!file) return false;
@@ -22,10 +30,12 @@
22
  };
23
 
24
  const openFilePicker = () => {
 
25
  photoInput.click();
26
  };
27
 
28
- const showError = () => {
 
29
  uploadError.hidden = false;
30
  };
31
 
@@ -38,8 +48,13 @@
38
  showError();
39
  return;
40
  }
 
 
 
 
41
 
42
  clearError();
 
43
  if (previewUrl) URL.revokeObjectURL(previewUrl);
44
  previewUrl = URL.createObjectURL(file);
45
 
@@ -79,16 +94,112 @@
79
  });
80
 
81
  uploadCard.addEventListener("drop", (event) => {
 
82
  displayFile(event.dataTransfer.files && event.dataTransfer.files[0]);
83
  });
84
 
85
  changePhoto.addEventListener("click", (event) => {
86
  event.stopPropagation();
 
87
  openFilePicker();
88
  });
89
 
90
- startSearch.addEventListener("click", () => {
91
- if (!startSearch.disabled) window.location.href = destination;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  });
93
 
94
  document.querySelectorAll(".faq-question").forEach((button) => {
@@ -115,4 +226,4 @@
115
  window.addEventListener("beforeunload", () => {
116
  if (previewUrl) URL.revokeObjectURL(previewUrl);
117
  });
118
- })();
 
2
  "use strict";
3
 
4
  const destination = "https://www.socialsleuth.xyz/face-search";
5
+ const handoffEndpoint = "https://www.socialsleuth.xyz/api/face-search/handoff";
6
+ const maxDimensionPx = 1600;
7
+ const targetMaxBytes = 1.5 * 1024 * 1024;
8
+ const backendMaxBytes = 2 * 1024 * 1024;
9
+ const uploadHardLimitBytes = 50 * 1024 * 1024;
10
+ const jpegQualitySteps = [0.82, 0.75, 0.68, 0.6, 0.5, 0.42];
11
  const uploadCard = document.getElementById("upload-card");
12
  const photoInput = document.getElementById("photo-input");
13
  const uploadEmpty = document.getElementById("upload-empty");
 
19
  const startSearch = document.getElementById("start-search");
20
 
21
  let previewUrl = "";
22
+ let selectedFile = null;
23
+ let isSubmitting = false;
24
 
25
  const isSupportedImage = (file) => {
26
  if (!file) return false;
 
30
  };
31
 
32
  const openFilePicker = () => {
33
+ if (isSubmitting) return;
34
  photoInput.click();
35
  };
36
 
37
+ const showError = (message = "Please choose a JPG or PNG image.") => {
38
+ uploadError.textContent = message;
39
  uploadError.hidden = false;
40
  };
41
 
 
48
  showError();
49
  return;
50
  }
51
+ if (file.size > uploadHardLimitBytes) {
52
+ showError("That photo is too large to process. Please choose another image.");
53
+ return;
54
+ }
55
 
56
  clearError();
57
+ selectedFile = file;
58
  if (previewUrl) URL.revokeObjectURL(previewUrl);
59
  previewUrl = URL.createObjectURL(file);
60
 
 
94
  });
95
 
96
  uploadCard.addEventListener("drop", (event) => {
97
+ if (isSubmitting) return;
98
  displayFile(event.dataTransfer.files && event.dataTransfer.files[0]);
99
  });
100
 
101
  changePhoto.addEventListener("click", (event) => {
102
  event.stopPropagation();
103
+ if (isSubmitting) return;
104
  openFilePicker();
105
  });
106
 
107
+ const loadImage = (file) =>
108
+ new Promise((resolve, reject) => {
109
+ const url = URL.createObjectURL(file);
110
+ const image = new Image();
111
+ image.onload = () => resolve({ image, url });
112
+ image.onerror = () => {
113
+ URL.revokeObjectURL(url);
114
+ reject(new Error("IMAGE_DECODE_FAILED"));
115
+ };
116
+ image.src = url;
117
+ });
118
+
119
+ const canvasToBlob = (canvas, quality) =>
120
+ new Promise((resolve, reject) => {
121
+ canvas.toBlob(
122
+ (blob) => (blob ? resolve(blob) : reject(new Error("CANVAS_ENCODE_FAILED"))),
123
+ "image/jpeg",
124
+ quality
125
+ );
126
+ });
127
+
128
+ // Converted from production's validated imageCompression.js settings so
129
+ // common phone photos fit the existing 2MB temporary-session limit.
130
+ const prepareImageForHandoff = async (file) => {
131
+ if (file.size <= 1024 * 1024) return file;
132
+
133
+ let sourceUrl = "";
134
+ try {
135
+ const loaded = await loadImage(file);
136
+ const image = loaded.image;
137
+ sourceUrl = loaded.url;
138
+ const longEdge = Math.max(image.naturalWidth || 0, image.naturalHeight || 0);
139
+ const scale = longEdge > maxDimensionPx ? maxDimensionPx / longEdge : 1;
140
+ const width = Math.max(1, Math.round((image.naturalWidth || maxDimensionPx) * scale));
141
+ const height = Math.max(1, Math.round((image.naturalHeight || maxDimensionPx) * scale));
142
+ const canvas = document.createElement("canvas");
143
+ canvas.width = width;
144
+ canvas.height = height;
145
+ const context = canvas.getContext("2d");
146
+ if (!context) return file;
147
+ context.imageSmoothingEnabled = true;
148
+ context.imageSmoothingQuality = "high";
149
+ context.drawImage(image, 0, 0, width, height);
150
+
151
+ let blob = null;
152
+ for (const quality of jpegQualitySteps) {
153
+ blob = await canvasToBlob(canvas, quality);
154
+ if (blob.size <= targetMaxBytes) break;
155
+ }
156
+ if (!blob || blob.size >= file.size) return file;
157
+
158
+ const baseName = String(file.name || "photo").replace(/\.[^./]+$/, "") || "photo";
159
+ return new File([blob], `${baseName}.jpg`, {
160
+ type: "image/jpeg",
161
+ lastModified: Date.now(),
162
+ });
163
+ } finally {
164
+ if (sourceUrl) URL.revokeObjectURL(sourceUrl);
165
+ }
166
+ };
167
+
168
+ startSearch.addEventListener("click", async () => {
169
+ if (startSearch.disabled || isSubmitting || !selectedFile) return;
170
+
171
+ isSubmitting = true;
172
+ startSearch.disabled = true;
173
+ startSearch.textContent = "OPTIMIZING PHOTO…";
174
+ clearError();
175
+
176
+ try {
177
+ const preparedFile = await prepareImageForHandoff(selectedFile);
178
+ if (!isSupportedImage(preparedFile) || preparedFile.size > backendMaxBytes) {
179
+ throw new Error("IMAGE_TOO_LARGE");
180
+ }
181
+
182
+ startSearch.textContent = "TRANSFERRING PHOTO…";
183
+ const form = new FormData();
184
+ form.append("image", preparedFile, preparedFile.name || "photo.jpg");
185
+ const response = await fetch(handoffEndpoint, {
186
+ method: "POST",
187
+ body: form,
188
+ credentials: "omit",
189
+ });
190
+ const data = await response.json().catch(() => null);
191
+ if (!response.ok || !data?.ok || typeof data.handoffToken !== "string") {
192
+ throw new Error(data?.error || "HANDOFF_FAILED");
193
+ }
194
+
195
+ startSearch.textContent = "OPENING SEARCH…";
196
+ window.location.assign(`${destination}?handoff=${encodeURIComponent(data.handoffToken)}`);
197
+ } catch (_) {
198
+ isSubmitting = false;
199
+ startSearch.disabled = false;
200
+ startSearch.textContent = "START SEARCH";
201
+ showError("We couldn't transfer that photo. Please try again.");
202
+ }
203
  });
204
 
205
  document.querySelectorAll(".faq-question").forEach((button) => {
 
226
  window.addEventListener("beforeunload", () => {
227
  if (previewUrl) URL.revokeObjectURL(previewUrl);
228
  });
229
+ })();