gitoriaLog in with ident

tracker

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit34f2c15b34f2c15btracker#15: TVmaze merge in the sync (gaps only: new episodes/seasons, empty titles/air dates; numbering check), fake TVmaze episodes + gatemre34f2c15b/components/main.hl

4.3 KB

  1. // components/main.hl — THE SHELL: the application header (brand; top right the login: ident's "Log in with
  2. // ident" button or Log out) and the slot every page renders into. All CSS is in styles.hl. Copied from
  3. // calendar.worldapi.org's components/main.hl unchanged in shape (tracker.worldapi.org#1, rejected once for
  4. // centering the sign-in on the page instead of using the header selector).
  5. // THE SELECTOR: `<ident-selector key=IDENT_API_KEY>` from <ident>/selector.js, as in calendar/gitoria. Choosing an
  6. // identity fires `ident-login` (one-time code); /login.js hands it to the hidden input #identcode, whose `change`
  7. // goes to the face trackerLogin (server-side exchange, no reload). The element's class is `in` / `out`; /login.js
  8. // mirrors it into the selector's `loggedIn`.
  9. // LOGIN: a plain link to <ident>/login?key=&return=<public url>/login/callback (project.hl loginCallback).
  10. // After a login or logout in this tab the page is fetched again (navigate('/')).
  11. import { navigate } from 'hl:web'
  12. import { appTitle } from '../project.hl'
  13. import { identKey, selectorScript, loginHref, userIdOfSession, exchangeCode, ensureUser } from '../users.hl'
  14. import { randomBytes } from 'hl:crypto'
  15. slot = null
  16. session = null
  17. loggedIn = userIdOfSession(session) != null
  18. loggedOut = userIdOfSession(session) == null
  19. buttonHref = loginHref
  20. selectorClass = loggedIn ? 'in' : 'out'
  21. selectorKey = identKey
  22. identScript = selectorScript
  23. loginError = ''
  24. hasLoginError = false
  25. // OFFLINE (tracker.worldapi.org#10): the service worker opens `/` from its cache without a network; the shell then
  26. // says so. hl:web gives a page no connection state and no "mounted" hook, so a tick asks the browser: `netProbe` runs
  27. // an endless 1 s CSS animation (styles.hl), and each `animationiteration` reads navigator.onLine — a write only when
  28. // it changed. (An `img` error or a socket event would race the hydration and be missed.)
  29. offline = false
  30. View {
  31. body {
  32. applicationHeader {
  33. a { class = "brand" href = "/" appTitle }
  34. userBox { id = "userbox"
  35. identSelector { id = "selector" key = selectorKey class = selectorClass }
  36. if (loggedIn) {
  37. button { id = "logout" type = "button" class = "quiet" "Log out" on click(e) { emit doLogout(e) } }
  38. }
  39. if (loggedOut) {
  40. a { id = "loginbutton" class = "button" href = buttonHref "Log in with ident" }
  41. }
  42. input { id = "identcode" type = "hidden" on change(e) { emit gotCode(e) } }
  43. }
  44. }
  45. if (hasLoginError) { p { id = "loginerror" class = "message banner" loginError } }
  46. if (offline) { offlineNote { id = "offline" "You are offline. Your shows and lists need the network." } }
  47. netProbe { "aria-hidden" = "true" on animationiteration(e) { emit netTick(e) } }
  48. main { slot }
  49. script { src = identScript }
  50. script { src = "/login.js" }
  51. }
  52. }
  53. showIn = () => {
  54. loggedIn = true
  55. loggedOut = false
  56. selectorClass = 'in'
  57. loginError = ''
  58. hasLoginError = false
  59. }
  60. showOut = () => {
  61. loggedIn = false
  62. loggedOut = true
  63. selectorClass = 'out'
  64. }
  65. // the selector's code (via /login.js and the hidden input) → the server exchanges it
  66. on gotCode(e) {
  67. let code = e.target.value
  68. e.target.value = ''
  69. if (code == null || code == '') { return null }
  70. let r = emit server trackerLogin(code)
  71. if (r == null || r.error != null) {
  72. loginError = r != null ? r.error : 'the login failed'
  73. hasLoginError = true
  74. return null
  75. }
  76. showIn()
  77. navigate('/')
  78. }
  79. on client trackerSignedIn(tag) {
  80. showIn()
  81. navigate('/')
  82. }
  83. on client trackerSignedOut(tag) {
  84. showOut()
  85. navigate('/')
  86. }
  87. on netTick(e) {
  88. let down = window.navigator.onLine == false
  89. if (down != offline) { offline = down }
  90. }
  91. on doLogout(e) {
  92. emit server trackerLogOut()
  93. showOut()
  94. navigate('/')
  95. }
  96. // ---- the faces (the trailing `session` is always the server's: hybriel #16) ----
  97. on server trackerLogin(code, session) {
  98. if (session == null) { return { error = 'no session — reload the page' } }
  99. let x = exchangeCode(code)
  100. if (x.error != null) { return { error = x.error } }
  101. let u = ensureUser(x.identity)
  102. if (u == null) { return { error = 'could not store the user' } }
  103. let tag = randomBytes(16)
  104. session.user = { id = u.id }
  105. session.data.tag = tag
  106. emit client trackerSignedIn(tag)
  107. return { ok = true }
  108. }
  109. on server trackerLogOut(session) {
  110. if (session == null) { return { error = 'no session' } }
  111. emit client trackerSignedOut(session.data.tag)
  112. session.user = null
  113. return { ok = true }
  114. }

Branches

Latest commits

  • 34f2c15btracker#15: TVmaze merge in the sync (gaps only: new episodes/seasons, empty titles/air dates; numbering check), fake TVmaze episodes + gatemre
  • cbdc4ea7tracker#12: link icons TMDB/IMDb/TVDB/TVmaze; sync fills missing ids (TVmaze lookup); movies fetched via /movie/mre
  • b105bcd8tracker#11: Hybriel master ff51cf46 (checks no longer vanish), mobile-first styles, carets, follow button, sign-in modal, inverted check, orange castmre
  • 31b758aatracker#10: installable app (manifest, service worker, offline shell), own icon + faviconmre
  • 2fa9d997tracker#9: TMDB sync (followed shows: seasons, episodes, posters), tools/sync-tmdb.hl + daily run 04:00 UTC, fake TMDB in gatemre
  • 49e1f61edeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • 54070a4etracker#8: /my/unwatched + /my/schedule (301 from old), S01E01, title (year), 1 episode, watched-set lookup (unwatched 15s -> 1s)mre
  • 3251488atracker#7: /my/shows (followed shows, newest follow first, poster, title, last watched SxxEyy); gate can take screenshots (TRACKER_GATE_SHOTS)mre
  • 44b7d9f9tracker#6: /schedule — upcoming episodes of followed shows, soonest firstmre
  • 91c9fc8ctracker#5: /unwatched — unwatched released episodes of followed shows, newest firstmre
  • a97c0295tracker#4: show page /shows/:slug (header, seasons, episodes, watch checks) + tools/relink-episode-seasons.hlmre
  • 05f407c5tracker: no border on any button except inverted ones (Log out, ident status and identities too); header brand weight 100mre
  • 17375427tracker#2: tools/migrate.hl + tools/verify.hl — old MongoDB data into mpackdb with new idsmre
  • 31aac936tracker#3: no border on the header and on filled buttons; inverted buttons keep theirsmre
  • 2ad9d29ctracker#1: login exactly like calendar (identity selector in the header, empty homepage)mre
  • 3691e176tracker#1: empty tracker with the ident login (state of 2026-09-27)mre