@rip-lang/api 0.7.2 → 0.7.4

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.rip CHANGED
@@ -90,18 +90,18 @@ createContext = (req, params = {}) ->
90
90
 
91
91
  ctx =
92
92
  json: (data, status = 200, headers = {}) ->
93
- for k, v of headers then out.set(k, v)
94
93
  out.set 'Content-Type', 'application/json'
94
+ for k, v of headers then out.set(k, v)
95
95
  new Response JSON.stringify(data), { status, headers: out }
96
96
 
97
97
  text: (str, status = 200, headers = {}) ->
98
- for k, v of headers then out.set(k, v)
99
98
  out.set 'Content-Type', 'text/plain; charset=UTF-8'
99
+ for k, v of headers then out.set(k, v)
100
100
  new Response str, { status, headers: out }
101
101
 
102
102
  html: (str, status = 200, headers = {}) ->
103
- for k, v of headers then out.set(k, v)
104
103
  out.set 'Content-Type', 'text/html; charset=UTF-8'
104
+ for k, v of headers then out.set(k, v)
105
105
  new Response str, { status, headers: out }
106
106
 
107
107
  redirect: (location, status = 302) ->
package/middleware.rip CHANGED
@@ -3,13 +3,14 @@
3
3
  # ==============================================================================
4
4
  #
5
5
  # Usage:
6
- # import { cors, logger, compress, sessions } from '@rip-lang/api/middleware'
6
+ # import { cors, logger, compress, sessions, prettyJson } from '@rip-lang/api/middleware'
7
7
  # import { session } from '@rip-lang/api'
8
8
  #
9
9
  # use logger()
10
10
  # use cors origin: 'https://myapp.com'
11
11
  # use compress()
12
12
  # use sessions()
13
+ # use prettyJson # Pretty JSON for iOS mobile browsers
13
14
  #
14
15
  # before ->
15
16
  # session.userId = 123 # Works anywhere via AsyncLocalStorage
@@ -390,3 +391,73 @@ export bodyLimit = (opts = {}) ->
390
391
  return c.json { error: message }, 413
391
392
 
392
393
  await next()
394
+
395
+ # ==============================================================================
396
+ # prettyJson — Pretty JSON for iOS Mobile Browsers
397
+ # ==============================================================================
398
+ #
399
+ # Renders JSON as syntax-highlighted HTML for iOS mobile browsers.
400
+ # Solves the iOS Safari/Chrome "download" footer issue when viewing JSON.
401
+ #
402
+ # Usage:
403
+ # use prettyJson
404
+ #
405
+ # Only activates when:
406
+ # 1. Request is from iOS (iPhone/iPad)
407
+ # 2. Browser is directly viewing (Accept: text/html, not API call)
408
+ #
409
+
410
+ export prettyJson = (c, next) ->
411
+ originalJson = c.json.bind(c)
412
+
413
+ c.json = (data, status = 200, headers = {}) ->
414
+ if _shouldRenderHtmlJson(c.req.raw)
415
+ return _renderHtmlJson(data, status)
416
+ originalJson(data, status, headers)
417
+
418
+ next!()
419
+
420
+ _shouldRenderHtmlJson = (req) ->
421
+ ua = req.headers.get('user-agent') or ''
422
+ accept = req.headers.get('accept') or ''
423
+ dest = req.headers.get('sec-fetch-dest') or ''
424
+
425
+ isIOS = /iPhone|iPad|iPod/i.test(ua)
426
+ acceptsHtml = accept.includes('text/html')
427
+ isNavigation = dest is 'document' or (acceptsHtml and not accept.startsWith('application/json'))
428
+
429
+ isIOS and isNavigation
430
+
431
+ _renderHtmlJson = (data, status) ->
432
+ json = JSON.stringify(data, null, 2)
433
+
434
+ # HTML escape then syntax highlight
435
+ escaped = json
436
+ .replace(/&/g, '&')
437
+ .replace(/</g, '&lt;')
438
+ .replace(/>/g, '&gt;')
439
+
440
+ highlighted = escaped
441
+ .replace(/"([^"]+)":/g, '<span class="k">"$1"</span>:')
442
+ .replace(/: "([^"]*)"/g, ': <span class="s">"$1"</span>')
443
+ .replace(/: (-?\d+\.?\d*)/g, ': <span class="n">$1</span>')
444
+ .replace(/: (true|false)/g, ': <span class="b">$1</span>')
445
+ .replace(/: (null)/g, ': <span class="b">$1</span>')
446
+
447
+ html = """<!DOCTYPE html>
448
+ <html><head>
449
+ <meta charset="utf-8">
450
+ <meta name="viewport" content="width=device-width,initial-scale=1">
451
+ <title>JSON</title>
452
+ <style>
453
+ *{box-sizing:border-box}
454
+ body{font:14px/1.5 ui-monospace,monospace;background:#1a1a2e;color:#e0e0e0;margin:0;padding:16px}
455
+ pre{margin:0;white-space:pre-wrap;word-break:break-word}
456
+ .k{color:#82aaff}.s{color:#c3e88d}.n{color:#f78c6c}.b{color:#89ddff}
457
+ </style>
458
+ </head><body><pre>#{highlighted}</pre></body></html>"""
459
+
460
+ new Response html, {
461
+ status: status
462
+ headers: new Headers({ 'content-type': 'text/html; charset=utf-8' })
463
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/api",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Pure Rip API framework — elegant, fast, zero dependencies",
5
5
  "type": "module",
6
6
  "main": "api.rip",