instaserve 1.1.19 → 1.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/api.txt CHANGED
@@ -41,7 +41,7 @@ POST /upload
41
41
  Upload a file.
42
42
  Body (JSON):
43
43
  {
44
- "bucket": "my-bucket-name",
44
+ "bucket": "my-bucket-name", // Optional if env var set
45
45
  "key": "filename.jpg",
46
46
  "body": "base64_encoded_string_contents",
47
47
  "contentType": "image/jpeg"
@@ -49,13 +49,13 @@ POST /upload
49
49
 
50
50
  POST /files
51
51
  List files in a bucket.
52
- Body (JSON): { "bucket": "my-bucket-name" }
52
+ Body (JSON): { "bucket": "my-bucket-name" } // Bucket optional if env var set
53
53
  Returns: List of file objects (key, size, lastModified).
54
54
 
55
55
  GET /download?bucket=my-bucket-name&key=filename.jpg
56
- Download a file directly.
56
+ Download a file directly. Bucket optional if env var set.
57
57
  (Available via the 'download' route handler)
58
58
 
59
59
  GET /delete?bucket=my-bucket-name&key=filename.jpg
60
- Delete a file.
60
+ Delete a file. Bucket optional if env var set.
61
61
  (Available via the 'delete' route handler)
package/lib/auth0.js CHANGED
@@ -41,7 +41,7 @@ export default {
41
41
  if (token) {
42
42
  global.sqlite.prepare("UPDATE users SET token = NULL WHERE token = ?").run(token);
43
43
  }
44
- res.setHeader("Set-Cookie", "token=; Path=/; HttpOnly; Secure; SameSite=Lax");
44
+ res.setHeader("Set-Cookie", "token=; Path=/; HttpOnly; Secure; SameSite=Strict");
45
45
  res.writeHead(302, { Location: LOGGEDOUT_REDIRECT });
46
46
  res.end();
47
47
  },
package/lib/r2.js CHANGED
@@ -5,6 +5,7 @@ import { URL } from 'url';
5
5
  const cloudflareAccessKeyId = process.env.CLOUDFLARE_ACCESS_KEY_ID || process.env.cloudflareAccessKeyId;
6
6
  const cloudflareSecretAccessKey = process.env.CLOUDFLARE_SECRET_ACCESS_KEY || process.env.cloudflareSecretAccessKey;
7
7
  const cloudflareAccountId = process.env.CLOUDFLARE_ACCOUNT_ID || process.env.cloudflareAccountId;
8
+ const cloudflareBucket = process.env.CLOUDFLARE_BUCKET || process.env.cloudflareBucket || process.env.CLOUDFLARE_BUCKET_NAME || process.env.cloudflareBucketName;
8
9
 
9
10
  if (!cloudflareAccessKeyId || !cloudflareSecretAccessKey || !cloudflareAccountId) {
10
11
  console.warn("Cloudflare environment variables not set! Please set CLOUDFLARE_ACCESS_KEY_ID, CLOUDFLARE_SECRET_ACCESS_KEY, and CLOUDFLARE_ACCOUNT_ID.");
@@ -152,6 +153,7 @@ const getSecrets = ({ cloudflareAccessKeyId, cloudflareSecretAccessKey, cloudfla
152
153
  );
153
154
 
154
155
  async function uploadToR2(bucket, key, body, contentType, secrets) {
156
+ bucket = bucket || cloudflareBucket;
155
157
  if (!body) body = Buffer.alloc(0);
156
158
  const signed = signR2('PUT', bucket, key, body, ...getSecrets(secrets));
157
159
  signed.headers['Content-Type'] = contentType || 'application/octet-stream';
@@ -160,6 +162,7 @@ async function uploadToR2(bucket, key, body, contentType, secrets) {
160
162
  }
161
163
 
162
164
  async function downloadFromR2(bucket, key, secrets) {
165
+ bucket = bucket || cloudflareBucket;
163
166
  const signed = signR2('GET', bucket, key, null, ...getSecrets(secrets));
164
167
  const { hostname, pathname, search } = new URL(signed.url);
165
168
 
@@ -187,11 +190,13 @@ async function downloadFromR2(bucket, key, secrets) {
187
190
  }
188
191
 
189
192
  async function deleteFromR2(bucket, key, secrets) {
193
+ bucket = bucket || cloudflareBucket;
190
194
  await requestR2(signR2('DELETE', bucket, key, null, ...getSecrets(secrets)));
191
195
  return { success: true };
192
196
  }
193
197
 
194
198
  async function listR2Files(bucket, prefix, secrets) {
199
+ bucket = bucket || cloudflareBucket;
195
200
  const qs = prefix ? `list-type=2&prefix=${encodeURIComponent(prefix)}` : 'list-type=2';
196
201
 
197
202
  const data = await requestR2(signR2('GET', bucket, '', null, ...getSecrets(secrets), qs));
@@ -204,6 +209,7 @@ async function listR2Files(bucket, prefix, secrets) {
204
209
  }
205
210
 
206
211
  function getSignedDownloadUrl(bucket, key, secrets) {
212
+ bucket = bucket || cloudflareBucket;
207
213
  return signR2('GET', bucket, key, null, ...getSecrets(secrets)).url;
208
214
  }
209
215
 
@@ -229,8 +235,30 @@ export const fileRoutes = {
229
235
  },
230
236
  download: async (req, res, data) => {
231
237
  try {
232
- return await downloadFromR2(data.bucket, data.key, data.secrets || defaultSecrets);
233
- } catch (e) { return { error: e.message }; }
238
+ const r2Msg = await downloadFromR2(data.bucket, data.key, data.secrets || defaultSecrets);
239
+ res.writeHead(r2Msg.statusCode, r2Msg.statusMessage, r2Msg.headers);
240
+
241
+ await new Promise((resolve, reject) => {
242
+ r2Msg.pipe(res);
243
+ r2Msg.on('error', (err) => {
244
+ res.destroy(err);
245
+ reject(err);
246
+ });
247
+ res.on('finish', resolve);
248
+ res.on('error', reject);
249
+ });
250
+
251
+ // returning nothing to prevent sendResponse from interfering
252
+ return;
253
+ } catch (e) {
254
+ // If headers weren't sent yet, we can return error JSON
255
+ if (!res.headersSent) {
256
+ return { error: e.message };
257
+ }
258
+ // If headers were sent, we can't cleanly return JSON,
259
+ // but the stream error handling above usually takes care of destroying the socket.
260
+ console.error("Download stream error:", e);
261
+ }
234
262
  },
235
263
  delete: async (req, res, data) => {
236
264
  try {
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "instaserve",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
4
4
  "description": "Instant web stack",
5
5
  "main": "module.mjs",
6
6
  "bin": "./instaserve",
7
7
  "scripts": {
8
- "start": "node instaserve",
8
+ "start": "node instaserve -api ./routes-full.js -secure -port 3001 -public ./public",
9
9
  "test": "./test_api.sh"
10
10
  },
11
11
  "type": "module",
package/public/index.html CHANGED
@@ -258,7 +258,7 @@
258
258
 
259
259
  <h3 class="section-title">R2 Storage</h3>
260
260
  <div style="margin-top: 0.5rem;">
261
- <input type="text" id="r2-bucket" placeholder="Bucket Name" class="input-field">
261
+ <input type="text" id="r2-bucket" placeholder="Bucket Name (Optional)" class="input-field">
262
262
  <input type="file" id="r2-file" style="margin-bottom: 0.5rem; width: 100%;">
263
263
  <div style="display: flex; gap: 0.5rem;">
264
264
  <button onclick="uploadFile()" class="btn-sm">Upload File</button>
@@ -321,7 +321,7 @@
321
321
  const bucket = val('r2-bucket');
322
322
  const fileInput = document.getElementById('r2-file');
323
323
  const file = fileInput.files[0];
324
- if (!bucket || !file) return log('Please enter a bucket and select a file.');
324
+ if (!file) return log('Please select a file.');
325
325
 
326
326
  const reader = new FileReader();
327
327
  reader.onload = async () => {
@@ -338,7 +338,7 @@
338
338
 
339
339
  async function listFiles() {
340
340
  const bucket = val('r2-bucket');
341
- if (!bucket) return log('Please enter a bucket.');
341
+
342
342
  await api('POST', '/files', { bucket });
343
343
  }
344
344
  </script>
package/test_api.sh CHANGED
@@ -139,6 +139,65 @@ else
139
139
  echo " ❌ FAIL: $RESPONSE"
140
140
  fi
141
141
 
142
+ # File API Tests
143
+ TEST_FILE_KEY="test_file.txt"
144
+ TEST_FILE_CONTENT="Hello R2 File Test"
145
+ # Use base64 without newlines
146
+ TEST_FILE_B64=$(echo -n "$TEST_FILE_CONTENT" | base64 | tr -d '\n')
147
+
148
+ echo ""
149
+ echo "--- File API Tests ---"
150
+
151
+ # Test 5.1: Upload File
152
+ echo "- Testing POST /upload..."
153
+ RESPONSE=$(curl -k -s -X POST "$BASE_URL/upload" \
154
+ -H "Cookie: token=$TEST_TOKEN" \
155
+ -H "Content-Type: application/json" \
156
+ -d "{\"key\": \"$TEST_FILE_KEY\", \"body\": \"$TEST_FILE_B64\", \"contentType\": \"text/plain\"}")
157
+
158
+ if [[ "$RESPONSE" == *"true"* ]]; then
159
+ echo " ✅ PASS"
160
+ else
161
+ echo " ❌ FAIL: $RESPONSE"
162
+ fi
163
+
164
+ # Test 5.2: Download File
165
+ echo "- Testing GET /download..."
166
+ RESPONSE=$(curl -k -s "$BASE_URL/download?key=$TEST_FILE_KEY" \
167
+ -H "Cookie: token=$TEST_TOKEN")
168
+
169
+ if [[ "$RESPONSE" == *"$TEST_FILE_CONTENT"* ]]; then
170
+ echo " ✅ PASS: Content matches"
171
+ else
172
+ echo " ❌ FAIL: Content mismatch or error. Got: $RESPONSE"
173
+ fi
174
+
175
+ # Test 5.3: List Files
176
+ echo "- Testing POST /files (List)..."
177
+ RESPONSE=$(curl -k -s -X POST "$BASE_URL/files" \
178
+ -H "Cookie: token=$TEST_TOKEN" \
179
+ -H "Content-Type: application/json" \
180
+ -d "{}")
181
+
182
+ if [[ "$RESPONSE" == *"$TEST_FILE_KEY"* ]]; then
183
+ echo " ✅ PASS: Found file in list"
184
+ else
185
+ echo " ❌ FAIL: $RESPONSE"
186
+ fi
187
+
188
+ # Test 5.4: Delete File
189
+ echo "- Testing POST /delete..."
190
+ RESPONSE=$(curl -k -s -X POST "$BASE_URL/delete" \
191
+ -H "Cookie: token=$TEST_TOKEN" \
192
+ -H "Content-Type: application/json" \
193
+ -d "{\"key\": \"$TEST_FILE_KEY\"}")
194
+
195
+ if [[ "$RESPONSE" == *"true"* ]]; then
196
+ echo " ✅ PASS"
197
+ else
198
+ echo " ❌ FAIL: $RESPONSE"
199
+ fi
200
+
142
201
  # 7. Test Login Redirect
143
202
  echo "- Testing GET /login (Expect 302 Redirect to Auth0)..."
144
203
  LOGIN_HTTP_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" "$BASE_URL/login")