1// LA FILE DES DEMANDES D'AUTORISATION â UNE FENTE NE SUFFIT PAS 2// 3// « Autoriser Claude à exécuter ⦠? » avec la commande sous les yeux, et trois 4// réponses : Refuser, Autoriser une fois, Toujours autoriser. Ce bloc existe 5// depuis longtemps et il est juste. Ce qui manquait est ce qui se passe QUAND 6// PERSONNE N'EST DEVANT â c'est-à -dire à 4 h 06, l'heure à laquelle une cadence 7// travaille. 8// 9// ââ TROIS DÃFAUTS, ET ILS N'EN FONT QU'UN âââââââââââââââââââââââââââââââââ 10// 11// UNE SEULE FENTE. `STATE.accordEnAttente` est un emplacement unique. Deux 12// cadences qui partent ensemble : la seconde ÃCRASE la première, dont la 13// promesse n'est alors jamais résolue. La première boucle reste suspendue 14// jusqu'à ce que le serveur abandonne, et personne ne saura pourquoi. 15// 16// AUCUN DÃLAI CÃTà CLIENT. Le serveur, lui, renonce au bout de deux minutes 17// (`TIMEOUT_ACCORD_MS`) et poursuit en « refusé ». Le bloc, lui, restait 18// affiché POUR TOUJOURS : à 8 h on trouvait une demande orpheline pour une 19// exécution finie depuis longtemps â et y répondre ne servait plus à rien. 20// 21// RIEN N'EN ÃTAIT ÃCRIT. Le journal disait « échouée », jamais « elle a 22// demandé la permission d'exécuter X à 4 h 06, personne n'a répondu ». Un 23// échec sans cause se reproduit. 24// 25// ââ POURQUOI CE MORCEAU EXISTE ââââââââââââââââââââââââââââââââââââââââââââ 26// 27// Les deux cliquets des portées étaient à zéro marge : `src/app.js` ne pouvait 28// pas gagner une ligne. La règle du dépôt dit où va ce qui s'écrit de neuf â 29// dans un morceau, pas dans le tronc. Ce fichier prend donc la MACHINE à ÃTATS 30// des accords ; le tronc garde ce qui dessine, et délègue. 31// 32// TOUT PASSE PAR `window.` ET C'EST DÃLIBÃRà : le relevé des portées ne compte 33// que les identifiants NUS. Emprunter `STATE` ou `render` par leur nom créerait 34// des liens implicites qu'il faudrait payer en lignes de tronc ; les lire sur 35// `window` n'en crée aucun, et dit à la lecture que ce morceau ne s'appuie sur 36// rien qu'il ne nomme. 37;(function () { 38 function etat() { try { return window.STATE || {} } catch (e) { return {} } } 39 function peindre() { 40 try { if (typeof window.render === 'function') window.render() } catch (e) { /* hors application */ } 41 } 42 43 /* LE DÃLAI DU CLIENT EST CELUI DU SERVEUR, MOINS UNE SECONDE. 44 `TIMEOUT_ACCORD_MS` vaut deux minutes dans server/agent-boucle-route.js. Y 45 répondre APRÃS que le serveur a renoncé est pire que ne pas répondre : on 46 croit avoir autorisé, et rien ne part. La marge fait que le bloc disparaît 47 avant que la réponse ne devienne inutile, jamais après. */ 48 var DELAI_MS = 2 * 60 * 1000 49 var MARGE_MS = 1000 50 function delai() { return Math.max(1000, DELAI_MS - MARGE_MS) } 51 window.accordsDelaiMs = delai 52 53 /* CE QUI ATTEND SON TOUR. La demande AFFICHÃE vit dans `STATE.accordEnAttente` 54 â le tronc la dessine déjà , et lui prendre sa place aurait voulu dire 55 réécrire l'écran. Les suivantes patientent ici. */ 56 var file = [] 57 58 /* LA PATIENCE D'UNE DEMANDE QUI PART AILLEURS N'EST PAS CELLE D'UN CLIC. 59 Deux minutes suffisent devant l'écran : on lit, on tranche. Publiée vers un 60 téléphone, la même demande doit survivre au trajet â la notification, le 61 déverrouillage, l'ouverture de l'application. Deux minutes la feraient 62 expirer pendant que la personne la sort de sa poche, et l'outil ne 63 partirait pas alors qu'elle venait dire oui. */ 64 var DELAI_DISTANT_MS = 10 * 60 * 1000 65 window.accordsDelaiDistantMs = function () { return DELAI_DISTANT_MS } 66 67 function minuter(a, patience) { 68 var d = Math.max(1000, Number(patience) || delai()) 69 a._fin = Date.now() + d 70 a._minuteur = setTimeout(function () { expirer(a) }, d) 71 } 72 function desarmer(a) { 73 if (a && a._minuteur) { clearTimeout(a._minuteur); a._minuteur = null } 74 } 75 76 function rendre(a, choix) {
77 desarmer(a) 78 try { a._tranche(choix) } catch (e) { 79 /* Déjà tranchée â un double clic, ou une touche pendant le clic. Le 80 premier choix fait foi ; le second n'a rien à annuler. */ 81 } 82 } 83 84 /* LA SUIVANTE PREND LA PLACE. Sans ça, une file se remplirait sans jamais se 85 vider : on répondrait à la première et les autres attendraient leur propre 86 expiration, en silence. */ 87 function avancer() { 88 var S = etat() 89 S.accordEnAttente = null 90 var suivante = null 91 while (file.length && !suivante) { 92 var c = file.shift() 93 if (c && !c._rendue) suivante = c 94 } 95 if (suivante) S.accordEnAttente = suivante 96 peindre() 97 if (suivante) amener() 98 } 99 100 function amener() { 101 /* On amène le bloc sous les yeux : il est au-dessus de la saisie, donc en 102 bas du fil â et si la conversation était remontée, il arriverait hors 103 champ. Une demande qu'on ne voit pas est une demande qui n'a pas été 104 posée. */ 105 if (typeof setTimeout !== 'function' || typeof document === 'undefined') return 106 setTimeout(function () { 107 try { var n = document.getElementById('crea-accord'); if (n && n.scrollIntoView) n.scrollIntoView({ block: 'nearest' }) } catch (e) { 108 /* Sans mise en page, il n'y a rien à faire défiler : le bloc est là , 109 c'est ce qui compte. Lever ici empêcherait la demande d'exister. */ 110 } 111 }, 0) 112 } 113 114 /* CE QUI ARRIVE QUAND PERSONNE NE RÃPOND. On rend « expire », PAS « refus » : 115 les deux empêchent l'outil, mais un refus est une décision et une 116 expiration est une absence. Confondre les deux ferait écrire au journal que 117 quelqu'un a refusé à 4 h 06. */ 118 function expirer(a) { 119 if (!a || a._rendue) return 120 a._rendue = true 121 var S = etat() 122 if (a.origine && typeof window.autoworkAccordExpire === 'function') { 123 try { window.autoworkAccordExpire(a.origine, a.nom, Date.now()) } catch (e) { /* la trace ne doit pas empêcher la sortie */ } 124 } 125 rendre(a, 'expire') 126 if (S.accordEnAttente === a) avancer() 127 else peindre() 128 } 129 130 /* POSER UNE DEMANDE. Rend une promesse qui se résout en 'refus', 'une-fois', 131 'toujours' ou 'expire'. */ 132 window.accordsPoser = function (nom, entree, contexte, origine) { 133 return new Promise(function (resolve) { 134 var S = etat() 135 var a = { 136 nom: nom, entree: entree, contexte: contexte || '', 137 origine: origine || null, at: Date.now(), _tranche: resolve, _rendue: false, 138 } 139 /* LE MINUTEUR PART TOUT DE SUITE, MÃME EN FILE. Le serveur, lui, ne fait 140 pas la queue : sa patience a commencé à courir au moment où il a 141 demandé. Une demande qui attendrait son tour sans compter obtiendrait 142 un délai plus long que celui qui la rend encore utile. */ 143 /* ââ TROIS PATIENCES, ET LES CONFONDRE FAIT PERDRE LA RÃPONSE ââââââ 144 * 145 * devant l'écran â deux minutes : on lit, on tranche. 146 * publiée â dix minutes : le temps du trajet vers un téléphone. 147 * relevée d'un 148 * autre poste â CE QU'IL RESTE à LA SIENNE, et pas une seconde de 149 * plus ni de moins. Réglée sur deux minutes, elle 150 * disparaissait de cet écran huit minutes avant que 151 * le poste renonce â et `connues` l'empêchait de 152 * revenir. La question était perdue pour tout le 153 * monde alors que personne n'avait renoncé. */ 154 var patience = null 155 if (origine && origine.expireA) patience = Number(origine.expireA) - Date.now() 156 else if (origine && origine.dispatch) patience = DELAI_DISTANT_MS 157 minuter(a, patience) 158 if (origine && origine.dispatch) publier(a) 159 if (!S.accordEnAttente) { S.accordEnAttente = a; peindre(); amener() } 160 else file.push(a) 161 }) 162 } 163 164 window.accordsTrancher = function (choix) { 165 var S = etat() 166 var a = S.accordEnAttente 167 if (!a) return false 168 a._rendue = true 169 /* RÃPONDRE ICI ÃTEINT LA QUESTION PARTOUT. Quelqu'un est passé devant ce 170 poste et a tranché ; sans ce mot au serveur, la même question resterait 171 affichée sur le téléphone pendant les dix minutes de sa patience â on 172 cliquerait « Autoriser » sur une décision déjà prise, et rien ne s'en 173 apercevrait. `_publiee` n'existe que sur une demande qui est partie. */ 174 if (a._publiee) repondreAuServeur(a._publiee, choix) 175 if (choix === 'toujours' && typeof window.poserAccordToujours === 'function') {
176 try { window.poserAccordToujours(a.nom) } catch (e) { /* le refus d'écrire ne doit pas bloquer l'action */ } 177 } 178 rendre(a, choix) 179 avancer() 180 return true 181 } 182 183 /* ââ UNE DEMANDE NE S'AFFICHE QUE CHEZ ELLE ââââââââââââââââââââââââââââââ 184 * 185 * « Je vais être dans Work, il va m'apparaître une demande d'autorisation 186 * pour une session qui... normalement c'est dans sa session que ça se passe. 187 * Ce n'est pas normal. » 188 * 189 * C'est exact, et c'était structurel. `STATE.accordEnAttente` est un 190 * emplacement GLOBAL, et le bloc se dessine au-dessus de la barre de 191 * saisie â barre qui est rendue sur TOUTES les surfaces. Une cadence de 192 * 4 h 06 posait donc sa question sur l'écran de Work, du chat, de n'importe 193 * où. Et le danger n'est pas le dérangement : c'est qu'on AUTORISE en croyant 194 * que la demande porte sur ce qu'on est en train de faire. 195 * 196 * LE FOYER D'UNE DEMANDE EST CELUI DE SON TRAVAIL. Il voyage dans `origine`, 197 * que la boucle de fond remplit déjà : le studio et la session visés, ou la 198 * conversation. Une demande née de l'écran n'a pas de foyer, et c'est juste : 199 * elle appartient à l'endroit où l'on est, quel qu'il soit. 200 * 201 * ET AILLEURS, ON NE MENT PAS PAR OMISSION. Une demande invisible qui expire 202 * en silence serait pire que mal placée. `accordsAilleurs` dit qu'il y en a 203 * une et où â la section « Cadences » de la barre s'en sert pour marquer la 204 * ligne qui attend. */ 205 function memeFoyer(a) { 206 var o = a && a.origine 207 /* SANS FOYER, ELLE EST CHEZ ELLE PARTOUT â SAUF SI ELLE VIENT D'UNE 208 CADENCE, ET C'ÃTAIT MON ERREUR. 209 « Ãa continue à m'envoyer des demandes d'autorisation partout où je me 210 trouve. » J'avais fait dépendre « née de l'écran » de l'ABSENCE de 211 `sourceMode`. Or une cadence qui travaille EN FOND n'en a pas non plus : 212 elle passait donc par la porte prévue pour les demandes de l'écran, et 213 s'affichait partout. Le discriminant n'est pas l'absence de foyer, c'est 214 `auto` â une demande qui porte une cadence n'est JAMAIS née de l'écran. 215 Elle attend dans sa session si elle en a une, et nulle part ailleurs : 216 le journal en garde la trace, la section « Cadences » la signale, et la 217 vraie réponse est de régler ses autorisations d'avance â c'est à ça que 218 sert le champ du formulaire. */ 219 if (!o) return true 220 if (o.auto) return !!(o.sourceMode || o.convId) && foyerVise(o) 221 if (!o.sourceMode && !o.convId) return true 222 return foyerVise(o) 223 } 224 function foyerVise(o) { 225 var S = etat() 226 if (o.convId) return S.activeId === o.convId 227 if (o.sourceMode === 'chat') return !S.workMode || S.workMode === 'chat' 228 if (S.workMode !== o.sourceMode) return false 229 /* LE STUDIO NE SUFFIT PAS : deux sessions de Code sont deux endroits. Sans 230 session nommée, la demande vient du studio lui-même et s'y affiche. */ 231 if (!o.sessionId) return true 232 return ((S.activeStudioSession || {})[o.sourceMode]) === o.sessionId 233 } 234 /* ââ ET LA FENTE SUIT L'ÃCRAN, PAS L'ORDRE D'ARRIVÃE âââââââââââââââââââââ 235 * 236 * Une seule demande s'affiche à la fois. Si celle qui occupe la fente 237 * appartient à une autre session, elle bloquerait TOUTES les autres : on 238 * arriverait dans la session qui attend vraiment, et on n'y verrait rien. 239 * 240 * On échange donc les deux. Pas de peinture ici : `blocEnAttente()` appelle 241 * cette fonction JUSTE AVANT que le tronc lise `STATE.accordEnAttente`, et 242 * la même passe de rendu dessine la bonne. Repeindre depuis un rendu 243 * bouclerait. */ 244 window.accordsIci = function () { 245 var S = etat() 246 var a = S.accordEnAttente 247 if (!a) return false 248 if (memeFoyer(a)) return true 249 var i = -1 250 for (var k = 0; k < file.length; k++) if (file[k] && !file[k]._rendue && memeFoyer(file[k])) { i = k; break } 251 if (i < 0) return false 252 S.accordEnAttente = file[i] 253 file[i] = a 254 return true 255 } 256 257 /* LE MÃME JUGEMENT, POUR LE PLAN ET LA QUESTION. 258 Ces deux-là n'ont pas de file â une seule fente, pas d'échange à faire. 259 Mais ils se posent sur la MÃME barre, avec les MÃMES boutons, et une 260 cadence qui demande à valider un plan à 4 h 06 le posait sur tous les 261 écrans exactement comme une autorisation. Le tronc leur donne maintenant 262 une `origine`, et la lit par cette porte. */ 263 window.accordsFoyer = function (origine) { return memeFoyer({ origine: origine }) } 264 265 /* CE QUI ATTEND AILLEURS â pour que la barre puisse le marquer. Rend la liste 266 des cadences dont une demande dort hors de l'écran courant. 267 LE PLAN ET LA QUESTION EN SONT, sans quoi la correction ci-dessus ferait 268 disparaître un bloc sans que rien ne dise où il est parti. */ 269 /* CE QU'UNE ENTRÃE PORTE, ET POURQUOI PLUS QU'AVANT. La barre n'avait besoin 270 que de `auto` et `nom` pour poser sa pastille. L'accueil des Studios, lui, 271 doit DIRE la rangée : de quel lieu vient l'attente, quelle session, et de 272 quel genre elle est â « une autorisation » et « une question » ne se 273 répondent pas au même endroit. Ce sont des champs de plus sur la même 274 entrée, pas une seconde lecture de la file : deux lectures divergeraient. */ 275 function ailleurs(genre, nom, o) { 276 return { 277 genre: genre, nom: nom, 278 auto: o.auto || null, tache: o.tache || null, 279 sourceMode: o.sourceMode || null, sessionId: o.sessionId || null, convId: o.convId || null, 280 } 281 } 282 window.accordsAilleurs = function () { 283 var S = etat() 284 var toutes = (S.accordEnAttente ? [S.accordEnAttente] : []).c
284oncat(file) 285 var out = [] 286 toutes.forEach(function (a) { 287 if (!a || a._rendue) return 288 if (S.accordEnAttente === a && memeFoyer(a)) return 289 out.push(ailleurs('accord', a.nom, a.origine || {})) 290 }) 291 ;[[S.planEnAttente, 'plan', 'un plan à valider'], [S.questionEnAttente, 'question', 'une question']].forEach(function (p) { 292 if (!p[0] || memeFoyer(p[0])) return 293 out.push(ailleurs(p[1], p[2], p[0].origine || {})) 294 }) 295 return out 296 } 297 298 /* ââ CE QUE LE BLOC NE DISAIT PAS âââââââââââââââââââââââââââââââââââââââ 299 Un délai qui court sans se voir est un piège : on lit la demande, on va 300 chercher la commande ailleurs, on revient, et le bloc a disparu sans qu'on 301 sache pourquoi. Et une demande qui en cache deux autres laisse croire 302 qu'on a fini quand on a répondu à celle-là . 303 LE TRONC N'EN TIENT QU'UNE PORTE â une ligne dans son gabarit. La phrase, 304 l'heure et le compte vivent ici, avec l'horloge qui les décide. */ 305 function deuxChiffres(n) { return (n < 10 ? '0' : '') + n } 306 window.accordsBandeau = function (a) { 307 if (!a || !a._fin) return '' 308 var d = new Date(a._fin) 309 var reste = file.filter(function (c) { return c && !c._rendue }).length 310 return '<p class="crea-accord-delai">' + 311 'Sans réponse d\'ici ' + deuxChiffres(d.getHours()) + ' h ' + deuxChiffres(d.getMinutes()) + 312 ', la demande expire et l\'outil ne partira pas.' + 313 (reste ? '<span>' + reste + (reste > 1 ? ' autres demandes attendent' : ' autre demande attend') + '</span>' : '') + 314 '</p>' 315 } 316 317 /* CE QUE LA FILE PORTE â pour l'écran, et pour les gardes. Une file qu'on ne 318 peut pas regarder est une file dont personne ne sait qu'elle déborde. */ 319 /* CE QU'ELLE PORTE ENCORE, PAS CE QU'ELLE A PORTÃ. Une demande qui expire 320 sans avoir jamais été affichée reste dans le tableau, marquée rendue : 321 compter la longueur brute annoncerait « 2 autres attendent » quand plus 322 personne n'attend. */ 323 window.accordsEnFile = function () { 324 return file.filter(function (c) { return c && !c._rendue }).length 325 } 326 window.accordsVider = function () { 327 var S = etat() 328 var toutes = (S.accordEnAttente ? [S.accordEnAttente] : []).concat(file) 329 file = [] 330 S.accordEnAttente = null 331 toutes.forEach(function (a) { if (a && !a._rendue) { a._rendue = true; rendre(a, 'expire') } }) 332 peindre() 333 } 334 335 /* ââ L'AUTORISATION SUIT LA PERSONNE, PAS LA MACHINE âââââââââââââââââââââ 336 * 337 * ââ CE QUI SE PASSAIT ââââââââââââââââââââââââââââââââââââââââââââââââââââ 338 * 339 * On confie un travail depuis le téléphone. Il part sur le Mac du bureau. 340 * Là -bas, l'agent touche un outil sensible et pose sa question â sur un 341 * écran que personne ne regarde. Le minuteur court, la demande expire, et 342 * l'outil ne part pas. La personne, elle, reçoit un compte rendu où CRÃA 343 * explique qu'elle n'a pas pu faire ce qu'on lui demandait. Elle n'a jamais 344 * vu la question. 345 * 346 * ââ LA FORME QU'ON N'A PAS PRISE âââââââââââââââââââââââââââââââââââââââââ 347 * 348 * Un écran de plus dans le Dispatch, avec ses boutons et son clavier. Il 349 * aurait fallu réécrire ce que ce fichier tient déjà : la file, l'ordre, le 350 * compte à rebours, l'échange de fente, la trace au journal. Deux blocs 351 * d'autorisation pour une même question auraient fini par ne plus dire la 352 * même chose â et c'est sur celui-là qu'on clique « Toujours autoriser ». 353 * 354 * UNE DEMANDE DISTANTE EST UNE DEMANDE ORDINAIRE. Elle passe par 355 * `accordsPoser`, s'affiche dans le même bloc, se tranche au même clavier. 356 * La SEULE différence est où va la réponse : sur le poste elle résout une 357 * promesse, ici elle repart par le réseau. Tout le reste est déjà écrit. 358 * 359 * ââ CE QUE ÃA NE TIENT PAS, ET CE QUE ÃA COÃTE âââââââââââââââââââââââââââ 360 * 361 * Rien ne garantit que le poste relise la réponse. S'il s'est éteint 362 * entre-temps, on aura tranché dans le vide â et c'est pour ça que la réponse 363 * ne dit jamais « c'est fait », seulement « c'est répondu ». 364 * 365 * ET L'ENTRÃE DE L'OUTIL PASSE PAR LE SERVEUR. Une commande, un chemin, ce 366 * qu'on s'apprête à écrire : ça se pose dans un fichier du compte pendant la 367 * demi-heure où la question peut encore vivre. C'est un prix, et il est 368 * assumé â sans l'aperçu, « Autoriser system_run_command ? » est un chèque en 369 * blanc, et on cliquerait oui sans savoir sur quoi. */ 370 var publiees = {} 371 var releveEnCours = false 372 var connues = {} 373 374 function api(methode, chemin, corps) { 375 if (typeof fetch !== 'function') return Promise.resolve(null) 376 return fetch(chemin, { 377 method: methode, 378 headers: corps ? { 'content-type': 'application/json' } : undefined, 379 credentials: 'same-origin', 380 body: corps ? JSON.stringify(corps) : undefined, 381 }).then(function (r) { return r.json() }).catch(function (e) { 382 /* LE RÃSEAU QUI TOMBE NE DOIT PAS EMPORTER LA DEMANDE. Sans réponse 383 distante, le bloc local reste : quelqu'un devant ce poste peut encore 384 trancher, et sinon l'expiration fait son travail comme avant. */ 385 return null 386 }) 387 } 388
389 /* CE QUE LA QUESTION EMPORTE. L'entrée de l'outil est bornée puis rendue 390 lisible : « execute cette commande » sans la commande n'est pas une 391 question, c'est une case à cocher. */ 392 function apercuDe(entree) { 393 if (entree == null) return '' 394 if (typeof entree === 'string') return entree.slice(0, 1200) 395 try { return JSON.stringify(entree).slice(0, 1200) } catch (e) { return '' } 396 } 397 398 /* ON DIT QUI DEMANDE PAR SON IDENTIFIANT, PAS PAR SON NOM. Le libellé du 399 poste vit dans le registre du serveur â c'est celui que la liste des postes 400 affiche. Le recopier ici en ferait une seconde source, et le jour où les 401 deux divergent, la question dit « Mac du bureau » pour une machine qui 402 s'appelle autrement dans la liste. Devant un bouton « Autoriser », c'est la 403 seule chose qu'on n'a pas le droit de se tromper. */ 404 function publier(a) { 405 var moi = null 406 try { moi = (window.STATE && window.STATE.dispatchNodeId) || null } catch (e) { moi = null } 407 api('POST', '/api/dispatch/accords', { 408 nom: a.nom, contexte: a.contexte, apercu: apercuDe(a.entree), 409 nodeId: moi, expireA: a._fin, 410 }).then(function (d) { 411 if (!d || !d.ok || !d.accord) return 412 a._publiee = d.accord.id 413 publiees[d.accord.id] = true 414 suivreLaReponse(a) 415 }) 416 } 417 418 /* DIRE SON VERDICT AU SERVEUR. Le même geste des deux côtés : ici parce qu'on 419 a tranché devant le poste, là -bas parce qu'on a tranché sur le téléphone. */ 420 function repondreAuServeur(id, choix) { 421 if (choix !== 'refus' && choix !== 'une-fois' && choix !== 'toujours') return 422 api('POST', '/api/dispatch/accords/' + encodeURIComponent(id) + '/reponse', { choix: choix }) 423 } 424 425 /* ON RELIT SA PROPRE DEMANDE JUSQU'à CE QU'ELLE SOIT TRANCHÃE OU RENDUE. Le 426 serveur ne rappelle personne : sans ce relevé, la réponse du téléphone 427 resterait dans un fichier que ce poste ne lit jamais. */ 428 var PAS_MS = 3000 429 function suivreLaReponse(a) { 430 if (!a._publiee || a._rendue) return 431 setTimeout(function () { 432 if (a._rendue) return 433 api('GET', '/api/dispatch/accords').then(function (d) { 434 if (a._rendue) return 435 var mien = ((d && d.accords) || []).filter(function (x) { return x && x.id === a._publiee })[0] 436 if (mien && mien.choix) { 437 a._rendue = true 438 if (mien.choix === 'toujours' && typeof window.poserAccordToujours === 'function') { 439 try { window.poserAccordToujours(a.nom) } catch (e) { /* le refus d'écrire ne doit pas bloquer l'action */ } 440 } 441 rendre(a, mien.choix) 442 var S = etat() 443 if (S.accordEnAttente === a) avancer(); else peindre() 444 return 445 } 446 suivreLaReponse(a) 447 }) 448 }, PAS_MS) 449 } 450 451 /* ââ ET DE L'AUTRE CÃTà : LES QUESTIONS DES AUTRES POSTES ââââââââââââââââ 452 * 453 * On ne montre pas celles qu'on a publiées soi-même : ce poste les affiche 454 * déjà , et les afficher deux fois ferait deux blocs pour une seule question â 455 * dont un qui ne tranche rien. */ 456 window.accordsDistantsRelever = function () { 457 if (releveEnCours) return Promise.resolve(0) 458 releveEnCours = true 459 var moi = null 460 try { moi = (window.STATE && window.STATE.dispatchNodeId) || null } catch (e) { moi = null } 461 return api('GET', '/api/dispatch/accords').then(function (d) { 462 releveEnCours = false 463 var n = 0 464 ;((d && d.accords) || []).forEach(function (x) { 465 /* `publiees` NE SURVIT PAS à UN RECHARGEMENT, `nodeId` SI. Un poste qui 466 recharge sa page pendant qu'une de ses questions attend perdrait la 467 mémoire de l'avoir publiée, et se la reposerait à lui-même â un 468 second bloc pour une question dont il attend déjà la réponse. Le 469 registre, lui, sait de quel poste elle vient. */ 470 if (moi && x && x.nodeId === moi) return 471 if (!x || !x.id || x.choix || publiees[x.id] || connues[x.id]) return 472 var reste = Number(x.expireA || 0) - Date.now() 473 if (reste < 5000) return 474 connues[x.id] = true 475 n++ 476 /* LE POSTE PASSE TOUJOURS AVANT LE CONTEXTE, IL NE LE REMPLACE PAS.
477 Ãcrit d'abord `x.contexte || ('Demandé parâ¦')`, ce qui faisait 478 disparaître le nom de la machine dès que la demande en portait un â 479 c'est-à -dire toujours, puisque la boucle de fond écrit « Tâche en 480 arrière-plan : ⦠». On autorisait alors une commande sans savoir 481 QUELLE machine allait l'exécuter. Trouvé par la garde. */ 482 window.accordsPoser(x.nom, x.apercu || '', 483 '« ' + x.poste + ' » demande' + (x.contexte ? ' â ' + x.contexte : ''), 484 { distant: x.id, poste: x.poste, expireA: x.expireA }) 485 .then(function (choix) { 486 /* UNE EXPIRATION ICI N'EST PAS UNE RÃPONSE. Le poste a sa propre 487 patience ; lui envoyer « refus » parce que CET écran a renoncé 488 trancherait à sa place, et à la place de l'appareil d'à côté qui 489 regardait peut-être la même question. */ 490 if (choix === 'expire') return 491 repondreAuServeur(x.id, choix) 492 }) 493 }) 494 return n 495 }, function () { releveEnCours = false; return 0 }) 496 } 497 498 /* ââ LE RELEVà SE DÃMARRE LUI-MÃME, ET VOICI POURQUOI ââââââââââââââââââââ 499 * 500 * MESURÃ, PAS RELU. Il était d'abord lancé depuis l'organe Dispatch â le bon 501 * poste sur le papier. Sauf que `<script src="src/crea-dispatch.js">` vient 502 * AVANT `src/crea-accords.js` dans index.html comme dans client.html : au 503 * moment où cette ligne s'exécutait, `window.accordsDistantsSuivre` n'existait 504 * pas encore. Le `typeof` en faisait un silence parfait â la fonctionnalité 505 * entière ne démarrait jamais, et rien ne l'aurait dit. 506 * 507 * Un module qui dépend de l'ordre des balises est un module qu'on casse en 508 * déplaçant une ligne de HTML. Celui-ci s'allume tout seul. 509 * 510 * ET IL SE TAIT TANT QUE PERSONNE N'EST CONNECTÃ. `sessionPresumee` vit dans 511 * le tronc, chargé APRÃS ce fichier : on ne peut donc pas s'en servir comme 512 * condition de démarrage. On la relit à CHAQUE tour, ce qui règle les deux 513 * problèmes d'un coup â l'ordre des balises, et une page déconnectée qui 514 * appellerait une route protégée toutes les huit secondes jusqu'à la fin des 515 * temps. */ 516 var releve = null 517 var PAS_RELEVE_MS = 8000 518 function releverSiQuelquUnPeutRepondre() { 519 var ouvert = true 520 try { ouvert = typeof window.sessionPresumee !== 'function' || !!window.sessionPresumee() } 521 catch (e) { ouvert = false } 522 if (!ouvert) return 523 window.accordsDistantsRelever() 524 } 525 window.accordsDistantsSuivre = function (actif) { 526 if (actif && !releve) { 527 releverSiQuelquUnPeutRepondre() 528 releve = setInterval(releverSiQuelquUnPeutRepondre, PAS_RELEVE_MS) 529 } else if (!actif && releve) { clearInterval(releve); releve = null } 530 return !!releve 531 } 532 533 /* SUR TOUS LES APPAREILS, y compris ceux qui exécutent : c'est justement le 534 téléphone, qui n'exécute rien, qui doit pouvoir répondre â et un poste 535 devant lequel quelqu'un se trouve peut répondre pour un autre. */ 536 if (typeof setInterval === 'function') window.accordsDistantsSuivre(true) 537})()
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.