cotutor 0.4.3 → 0.4.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.
Files changed (252) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +32 -1
  3. package/dist/app/cli/root-ca.d.ts +8 -0
  4. package/dist/app/cli/root-ca.js +33 -0
  5. package/dist/app/cli/serve.d.ts +11 -2
  6. package/dist/app/cli/serve.js +49 -3
  7. package/dist/app/http/access.js +1 -1
  8. package/dist/app/http/app.js +4 -0
  9. package/dist/app/http/parent-home.js +5 -0
  10. package/dist/app/http/stage.js +2 -2
  11. package/dist/app/mock/mock.js +3 -0
  12. package/dist/stage/fonts/Assistant/Assistant-Bold.woff2 +0 -0
  13. package/dist/stage/fonts/Assistant/Assistant-Medium.woff2 +0 -0
  14. package/dist/stage/fonts/Assistant/Assistant-Regular.woff2 +0 -0
  15. package/dist/stage/fonts/Assistant/Assistant-SemiBold.woff2 +0 -0
  16. package/dist/stage/fonts/Cascadia/CascadiaCode-Regular.woff2 +0 -0
  17. package/dist/stage/fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
  18. package/dist/stage/fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
  19. package/dist/stage/fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
  20. package/dist/stage/fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
  21. package/dist/stage/fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
  22. package/dist/stage/fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
  23. package/dist/stage/fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
  24. package/dist/stage/fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
  25. package/dist/stage/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
  26. package/dist/stage/fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
  27. package/dist/stage/fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
  28. package/dist/stage/fonts/Liberation/LiberationSans-Regular.woff2 +0 -0
  29. package/dist/stage/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
  30. package/dist/stage/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
  31. package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
  32. package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
  33. package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
  34. package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
  35. package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
  36. package/dist/stage/fonts/Virgil/Virgil-Regular.woff2 +0 -0
  37. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-019d66dcad46dc156b162d267f981c20.woff2 +0 -0
  38. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-04b718e5623574919c8b0dea5f301444.woff2 +0 -0
  39. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-069e77aac84590e2e991d0a0176d34f2.woff2 +0 -0
  40. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-06c77b8c66e51ed6c63ccb502dd8b8af.woff2 +0 -0
  41. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-08e0dc436ad0ad61ba5558db0674d762.woff2 +0 -0
  42. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-093b9ef39a46ceae95a1df18a0a3a326.woff2 +0 -0
  43. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-095c169f3314805276f603a362766abd.woff2 +0 -0
  44. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-09850c4077f3fffe707905872e0e2460.woff2 +0 -0
  45. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0986d134c05864f5025962eef9f994a0.woff2 +0 -0
  46. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0b5d723fdc4e249c140f0909e87d03b4.woff2 +0 -0
  47. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0f626226ba1272e832aea87bafd9720e.woff2 +0 -0
  48. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0f7fb1e0d5015bb1371343153ecf7ce3.woff2 +0 -0
  49. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0fa55a080fcd0f9dc2e0b0058b793df8.woff2 +0 -0
  50. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0facdf1ea213ba40261022f5d5ed4493.woff2 +0 -0
  51. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-10a7ae9a371830a80c3d844acf1c02d7.woff2 +0 -0
  52. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-112c051027b2d766c19a519f6ee1f4f7.woff2 +0 -0
  53. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-11c345711937f0ba4b8f7b6b919c8440.woff2 +0 -0
  54. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-12b52b58eb3df36804b9a654ec9ee194.woff2 +0 -0
  55. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-13ae07ed2e272d26d59bc0691cd7117a.woff2 +0 -0
  56. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-13d2887ec8ee73c43acdabc52a05af7b.woff2 +0 -0
  57. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-145aa02cdd91946e67dc934e1acffe75.woff2 +0 -0
  58. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-15dc6d811c9cd078f9086a740d5a1038.woff2 +0 -0
  59. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-173945821411c09f70c95f98d590e697.woff2 +0 -0
  60. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-1b611157cd46bb184d4fa4dae2d6a2b8.woff2 +0 -0
  61. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-1e6fd68f1f3902ce48ce8c69df385622.woff2 +0 -0
  62. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-1ee544f0f1dac422545c505baa788992.woff2 +0 -0
  63. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-1fdc0c67ed57263a80fd108c1f6ccf24.woff2 +0 -0
  64. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-203b0e569e3b14aac86a003dc3fa523e.woff2 +0 -0
  65. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-20cc1bbf50e7efb442756cb605672c1f.woff2 +0 -0
  66. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-20e7bf72fa05de9adf7dbcc7bf51dde6.woff2 +0 -0
  67. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-21430ee05a1248901da8d0de08744d47.woff2 +0 -0
  68. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-23686f7f29da6e8008c36dd3a80c83d6.woff2 +0 -0
  69. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-23ad2d71b280f00b1363b95b7bea94eb.woff2 +0 -0
  70. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-23f228f3999c01983860012330e4be08.woff2 +0 -0
  71. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-24476a126f129212beb33f66853ea151.woff2 +0 -0
  72. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-24a21c1e4449222e8d1898d69ff3a404.woff2 +0 -0
  73. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-25b7f38e18f035f96cb5e547bd2bd08c.woff2 +0 -0
  74. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-29cec36cd205b211da97acabaa62f055.woff2 +0 -0
  75. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2a26d20a23b00898ce82f09d2ee47c3f.woff2 +0 -0
  76. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2adbc89c11e65905393d3dfc468b9d5b.woff2 +0 -0
  77. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2b7441d46298788ac94e610ffcc709b6.woff2 +0 -0
  78. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2b77e8ebfb2367ab2662396a60e7d320.woff2 +0 -0
  79. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2cf96d082d35ea3d8106851223ad0d16.woff2 +0 -0
  80. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2d43040e86ff03ba677f6f9c04cd0805.woff2 +0 -0
  81. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2e33e8dc771ef5e1d9127d60a6b73679.woff2 +0 -0
  82. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-33432927cd87d40cfe393c7482bf221f.woff2 +0 -0
  83. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-353f33792a8f60dc69323ddf635a269e.woff2 +0 -0
  84. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-36925dfe329a45086cbb7fc5c20d45ac.woff2 +0 -0
  85. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3717077e38f98d89eae729b6c14e56dc.woff2 +0 -0
  86. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3756e81d3e149cf6099163ee79944fec.woff2 +0 -0
  87. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-395c35dd584b56b0789f58a0559beaf1.woff2 +0 -0
  88. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3c9de2ae0ea4bc91a510942dfa4be8d2.woff2 +0 -0
  89. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3cc70dbb64df5b21f1326cc24dee2195.woff2 +0 -0
  90. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3e1f8f654357353bf0e04ba5c34b5f7f.woff2 +0 -0
  91. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3e63ed8162808a9e425ed80a8bc79114.woff2 +0 -0
  92. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3eaa538115d76932653c21d8dc28f207.woff2 +0 -0
  93. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4095eb84ef3874e2600247bee0b04026.woff2 +0 -0
  94. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-41521fade99856108931b4768b1b2648.woff2 +0 -0
  95. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-450da755d5bcb70906e1295e559b9602.woff2 +0 -0
  96. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-452225341522a7942f0f6aab1a5c91a3.woff2 +0 -0
  97. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4535823663ad81405188a528d8f2b1a2.woff2 +0 -0
  98. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4806e761d750087c2d734fc64596eaff.woff2 +0 -0
  99. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4a0fdb40036e87b40aa08dd30584cb85.woff2 +0 -0
  100. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4a38cc3e9cf104e69ba246d37f8cf135.woff2 +0 -0
  101. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4aca6a43e59aceee2166b0c7e4e85ef1.woff2 +0 -0
  102. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4bfaa8ffa64c5ee560aa2daba7c9cbd3.woff2 +0 -0
  103. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4dc6d5f188d5c96d44815cd1e81aa885.woff2 +0 -0
  104. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4ddc14ed3eb0c3e46364317dfc0144a3.woff2 +0 -0
  105. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4ee10ae43505e2e0bc62656ced49c0fa.woff2 +0 -0
  106. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4f50e5136e136527280bc902c5817561.woff2 +0 -0
  107. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-51502f1206be09c565f1547c406e9558.woff2 +0 -0
  108. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-51a0e808bbc8361236ac521a119758a3.woff2 +0 -0
  109. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-52a84a22fd1369bffeaf21da2d6158dc.woff2 +0 -0
  110. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5330a2119a716e4e7224ed108b085dac.woff2 +0 -0
  111. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-543fa46ace099a7099dad69123399400.woff2 +0 -0
  112. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-544fc28abe2c5c30e62383fd4dac255f.woff2 +0 -0
  113. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-54acdfc2166ad7fcbd074f75fd4a56ba.woff2 +0 -0
  114. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5572b3513ba8df57a3d5d7303ee6b11b.woff2 +0 -0
  115. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-56467a5c8840c4d23a60b2f935114848.woff2 +0 -0
  116. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-56a32a7689abd0326e57c10c6c069bb4.woff2 +0 -0
  117. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-57862b464a55b18c7bf234ce22907d73.woff2 +0 -0
  118. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-583d166e56ba0de4b77eabb47ef67839.woff2 +0 -0
  119. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5882ffa04f32584d26109137e2da4352.woff2 +0 -0
  120. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-58fd02350d0bc52cf1ca3bb32ce9766e.woff2 +0 -0
  121. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5935a5775af3d5c6307ac667bd9ae74e.woff2 +0 -0
  122. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-59e9ff77b0efaf684bc09274fb6908c9.woff2 +0 -0
  123. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5a1ce3117cfe90c48e8fb4a9a00f694d.woff2 +0 -0
  124. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5a45d991244d4c7140217e1e5f5ca4f4.woff2 +0 -0
  125. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5a7fac4b8b23a6e4e5ba0c9bf1756c91.woff2 +0 -0
  126. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5b0ed6971aaab9c8ad563230bd5471a7.woff2 +0 -0
  127. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5d2898fbc097a7e24c6f38d80587621e.woff2 +0 -0
  128. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-603aefd23e350ba7eb124273e3c9bcf1.woff2 +0 -0
  129. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-60a3089806700d379f11827ee9843b6b.woff2 +0 -0
  130. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-60a41c7e1c68f22424e6d22df544bc82.woff2 +0 -0
  131. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-642b26e2e5f5fb780b51b593dbc8c851.woff2 +0 -0
  132. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-66493ba5a8367f2928812f446f47b56a.woff2 +0 -0
  133. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-670ba603758d94268e8606f240a42e12.woff2 +0 -0
  134. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-671a2c20b1eb9e4ef8a192833940e319.woff2 +0 -0
  135. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-684d65f1793cac449dde5d59cb3c47fb.woff2 +0 -0
  136. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-69c09cc5fa3e55c74fc4821f76909cc3.woff2 +0 -0
  137. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-6ae5b42180ad70b971c91e7eefb8eba2.woff2 +0 -0
  138. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-6e092f71c1e634059ada0e52abadce67.woff2 +0 -0
  139. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-6f3256af8454371776bc46670d33cc65.woff2 +0 -0
  140. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-6fe5c5973cc06f74b2387a631ea36b88.woff2 +0 -0
  141. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-70c2eb8d64e71a42a834eb857ea9df51.woff2 +0 -0
  142. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-70e811fd7994e61f408c923de6ddd078.woff2 +0 -0
  143. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7197d6fda6cba7c3874c53d6381ca239.woff2 +0 -0
  144. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-72252d73220fa3cd856677888cee1635.woff2 +0 -0
  145. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-72536a3d71b694a0d53dd90ddceae41e.woff2 +0 -0
  146. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-726303e0774b4e678bff8c2deb6ca603.woff2 +0 -0
  147. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-733171b4ffcd17ea1fe1c0ba627173bf.woff2 +0 -0
  148. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-739bc1a567439c7cffcd1614644593d2.woff2 +0 -0
  149. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-73e309718fd16cea44b4d54a33581811.woff2 +0 -0
  150. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7494dc504ae00ee9cd0505f990f88c5d.woff2 +0 -0
  151. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-74e2263a91439c25b91d5132ce9f4d62.woff2 +0 -0
  152. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-761d05e3cd968cf574166867998ef06a.woff2 +0 -0
  153. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7718fe60986d8b42b1be9c5ace5ccf25.woff2 +0 -0
  154. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-774d4f764a1299da5d28ec2f2ffe0d69.woff2 +0 -0
  155. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-79d494361ae093b69e74ee9dbe65bfd4.woff2 +0 -0
  156. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-79f007c1c6d07557120982951ea67998.woff2 +0 -0
  157. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7a07ddc0f0c0f5f4a9bad6ee3dda66b5.woff2 +0 -0
  158. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7ab2bed91166a9dca83a5ebfbe2a7f38.woff2 +0 -0
  159. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7ccce86603f80a099ddb0cb21d4ae3e3.woff2 +0 -0
  160. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7e4bde7e9c7f84cd34d8a845e384c746.woff2 +0 -0
  161. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7e929f262f30c8ee78bf398150b1a7cd.woff2 +0 -0
  162. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7eb9fffd1aa890d07d0f88cc82e6cfe4.woff2 +0 -0
  163. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7f855356ab893b0d2b9c1c83b8116f0e.woff2 +0 -0
  164. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-829615148e6357d826b9242eb7fbbd1e.woff2 +0 -0
  165. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-866fa7613df6b3fd272bcfd4530c0bb9.woff2 +0 -0
  166. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-87599f94b6cc129d505b375798d0d751.woff2 +0 -0
  167. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8c2f33cee3993174f7e87c28e4bf42ee.woff2 +0 -0
  168. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8d3bcabb847b56243b16afe62adaaf21.woff2 +0 -0
  169. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8de5b863cb50dfefdd07cb11c774d579.woff2 +0 -0
  170. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8e9f97f01034820170065b2921b4fb5e.woff2 +0 -0
  171. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8f476c4c99813d57cbe6eca4727388ad.woff2 +0 -0
  172. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-903bb6865f3452e2fda42e3a25547bc5.woff2 +0 -0
  173. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-91ddb2969bf2d31ba02ad82998d1314c.woff2 +0 -0
  174. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-938d90c10ff8c20386af7f242c05d6b0.woff2 +0 -0
  175. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-93fc8f28a33234bcadf1527cafabd502.woff2 +0 -0
  176. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-95429962233afd82db1c27df1500a28c.woff2 +0 -0
  177. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9544732d2e62d1a429674f8ee41b5d3a.woff2 +0 -0
  178. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9592bfc861f07bcb8d75c196b370e548.woff2 +0 -0
  179. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-95bfd249da4902577b4b7d76ebdd0b44.woff2 +0 -0
  180. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-968cffdc8ee679da094e77ebf50f58ef.woff2 +0 -0
  181. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-97f7f48ce90c9429bf32ae51469db74d.woff2 +0 -0
  182. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-982b630266d87db93d2539affb1275c6.woff2 +0 -0
  183. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-98f2ad84457de7f3740d9920b8fa8667.woff2 +0 -0
  184. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-99a16ef6a64934d5781933dbd9c46b2e.woff2 +0 -0
  185. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9cfb2a77a4e45025105ad29a1748b90d.woff2 +0 -0
  186. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9d81066dd2b337c938df6e90380a00dc.woff2 +0 -0
  187. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9eb5a99df4e76ac3363453ac9ca288b1.woff2 +0 -0
  188. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a004ddfcb26e67bd6e678c8ed19e25ce.woff2 +0 -0
  189. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a0ca5df4258213d7fc9fce80f65ce760.woff2 +0 -0
  190. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a1f916d6039285c4ffb900cd654e418f.woff2 +0 -0
  191. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a203b91dad570bf05a58c3c3ddb529bf.woff2 +0 -0
  192. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a365e82ed54697a52f27adcea1315fe8.woff2 +0 -0
  193. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a4c34be6d42152e64b0df90bc4607f64.woff2 +0 -0
  194. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a7accba310e821da5505f71c03b76bdb.woff2 +0 -0
  195. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-aa0d470430e6391eca720c7cfa44446f.woff2 +0 -0
  196. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-aa5c9ca6cf4fba00433b7aa3fa10671a.woff2 +0 -0
  197. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ac9ceb44437becc3e9c4dbfebab7fc2d.woff2 +0 -0
  198. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b1220a3c61f85cc0408deedb4c5f57a2.woff2 +0 -0
  199. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b358f7a51ece39a3247942b1feabdb29.woff2 +0 -0
  200. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b57aaedfd8ebdf3931f25119dc6a5eb2.woff2 +0 -0
  201. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b5c1596551c256e0e9cf02028595b092.woff2 +0 -0
  202. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b6d128682ee29e471486354d486a1b90.woff2 +0 -0
  203. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b6fd38ca30869792244804b04bc058da.woff2 +0 -0
  204. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b7d203b051eff504ff59ddca7576b6a9.woff2 +0 -0
  205. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b96d9226ce77ec94ceca043d712182e6.woff2 +0 -0
  206. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ba3de316d63c7e339987b16f41a0b879.woff2 +0 -0
  207. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-bafff7a14c27403dcc6cf1432e8ea836.woff2 +0 -0
  208. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-bd77e3c7f9e0b072d96af37f73d1aa32.woff2 +0 -0
  209. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-be549ab72f0719d606a5c01e2c0219b6.woff2 +0 -0
  210. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c16ed9740b85badf16e86ea782a3062f.woff2 +0 -0
  211. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c1f94158256bb1f3bf665b053d895af9.woff2 +0 -0
  212. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c40533fdf4cc57177b12803598af7e59.woff2 +0 -0
  213. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c4a687ac4f0c2766eefc9f77ed99cddf.woff2 +0 -0
  214. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c69f61a4ab18d0488c8d1fc12e7028e8.woff2 +0 -0
  215. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c8b71798409ccc126ee264a00aadcf21.woff2 +0 -0
  216. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c99eda15fc26a2941579560f76c3a5cf.woff2 +0 -0
  217. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cb17fc3db95f6d139afc9d31a8e93293.woff2 +0 -0
  218. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cbaaefaaf326668277aa24dfa93c4d28.woff2 +0 -0
  219. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cd145ce4a0ea18469358df53c207bc1b.woff2 +0 -0
  220. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cdbce89e82cc1ab53a2decbf5819278f.woff2 +0 -0
  221. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ce4884f96f11589608b76b726a755803.woff2 +0 -0
  222. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cf2cc71752631e579e35b0e423bf2638.woff2 +0 -0
  223. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cf6ff4e0f491ca0cf3038187a997b9b4.woff2 +0 -0
  224. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cfb211578629b7e8153b37240de6a9d5.woff2 +0 -0
  225. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-d0cf73942fea1c74edbdf0b3011f4656.woff2 +0 -0
  226. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-d2666cbed13462c5dc36fa2f15c202ca.woff2 +0 -0
  227. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-d3716376641d615e2995605b29bca7b6.woff2 +0 -0
  228. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-dac48066b5883d8b4551fc584f0c2a3e.woff2 +0 -0
  229. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-dbea1af6dcd9860be40c3d18254338f5.woff2 +0 -0
  230. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e11567fd2accf9957cd0d3c2be937d87.woff2 +0 -0
  231. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e2ead7ea7da0437f085f42ffc05f8d13.woff2 +0 -0
  232. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e3fcf5180fd466c8915c4e8069491054.woff2 +0 -0
  233. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e480d9c614742d05f0e78f274f1e69e6.woff2 +0 -0
  234. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e4bca6cfa53e499cae0a6be4894a90e9.woff2 +0 -0
  235. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e51ef413167c6e14e0c0fdcc585f2fc9.woff2 +0 -0
  236. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e5f453bb04da18eed01675eeebd88bf8.woff2 +0 -0
  237. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e656f091b9dc4709722c9f4b84d3c797.woff2 +0 -0
  238. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ec181b795ac1fb5a50d700b6e996d745.woff2 +0 -0
  239. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ee8bae97908d5147b423f77ad0d3c1bb.woff2 +0 -0
  240. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f0f13b5c60e0af5553bd359f5513be1b.woff2 +0 -0
  241. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f2b54d4e7be0eaefe1c2c56836fa5368.woff2 +0 -0
  242. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f56414bf9bced67990def8660e306759.woff2 +0 -0
  243. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f5d079153c99a25b9be5b8583c4cc8a7.woff2 +0 -0
  244. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f6032fc06eb20480f096199713f70885.woff2 +0 -0
  245. package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f8ee5d36068a42b51d0e4a1116cfcec1.woff2 +0 -0
  246. package/dist/ui/kid/kid-page.js +2 -0
  247. package/dist/ui/kid/offline-sw.d.ts +1 -0
  248. package/dist/ui/kid/offline-sw.js +42 -0
  249. package/dist/ui/parent/parent-home-page.js +8 -5
  250. package/install.ps1 +66 -0
  251. package/install.sh +84 -0
  252. package/package.json +13 -10
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # 更新说明
2
+
3
+ 写给家长:每个版本改了什么。升级跑 `cotutor update`(Windows 上 `cotutor.cmd update`),详见 README 的「数据与升级」。
4
+
5
+ ## 0.4.4
6
+
7
+ - 装得快多了:安装从 275 个包变成 15 个,Windows 上第一次装从 3 分钟左右变成 1 分钟左右
8
+ - 平板第一次用:在家长端「扫码打开」点「装证书」,用平板相机扫一下就能装根证书
9
+ - 电脑关了或睡着时,孩子在平板上打开看到「老师们还没到」,不再是浏览器的报错;电脑好了自己回来
10
+ - 有了一行安装命令(Mac、Windows 都有),README 里也写了怎么卸载
11
+
12
+ ## 0.4.3
13
+
14
+ - 自动备份:服务开着时每天一份,升级前一份,可以恢复;录音占地方,不在备份里
15
+ - 家长端首页多了「还差几步」,第一次打开就知道接下来做什么;选一下年级老师就知道孩子上几年级
16
+ - 遇到问题时,家长端「设置」能生成诊断信息,不含孩子的对话和 key,先看再发
17
+ - `cotutor init --grade` 自动算好学期,`cotutor doctor --fix` 一次修掉能自动修的
18
+
19
+ ## 0.4.2
20
+
21
+ - 百炼欠费、key 失效或连不上时,图标和家长端会说清楚,孩子那边会请爸爸妈妈来看,不再一直转
22
+ - 升级只要一条命令 cotutor update,几个孩子一次升好,孩子在通话时不动
23
+ - 用 Homebrew 装 Node 的电脑:Node 升级后服务和图标不会再起不来
24
+ - 有新版本时能直接看到这次改了什么
25
+
26
+ ## 0.4.1
27
+
28
+ - 有新版本时,图标菜单和家长端首页会提醒,孩子端不说,也不会自动升级
29
+ - 第一次装好后台服务时自动打开家长端
30
+ - 升级后图标不再误报“要重启”
31
+ - Windows 上给家长看的命令带 `.cmd`,新电脑的 PowerShell 不会拦
32
+ - `cotutor cert` 发现这台电脑没信任根证书时会提醒
33
+ - 新增 `INSTALL.md`:可以让 AI 助手照着替你安装
34
+
35
+ ## 0.4.0
36
+
37
+ - 后台服务 `cotutor service`:登录后自己起来,崩了自己再起,关了终端照样跑
38
+ - 菜单栏(任务栏)图标 `cotutor tray`:不开终端也能看状态、打开家长端、重启
39
+ - 端口只在 `init` 时选一次,以后不变;地址认电脑名,换网络不用重扫
package/README.md CHANGED
@@ -12,7 +12,21 @@
12
12
  请阅读 https://raw.githubusercontent.com/TarsLab/cotutor/main/INSTALL.md ,照着在这台电脑上安装 cotutor
13
13
  ```
14
14
 
15
- **自己装:** 需要一台电脑(Mac 最省事)、Node ≥ 22.18,和一个百炼账号(北京地域,按量付费)。老师的嘴和脑都走百炼,由应用直接调用,不用另装模型客户端:嘴是 `qwen3.8-omni-flash-realtime`,听孩子说、照着讲稿念;脑是 `qwen3.8-max`,写讲稿和板书。
15
+ **自己装:** 需要一台电脑(Mac 最省事)、[Node.js](https://nodejs.org) ≥ 22.18(装 LTS 版),和一个百炼账号(北京地域,按量付费)。老师的嘴和脑都走百炼,由应用直接调用,不用另装模型客户端:嘴是 `qwen3.8-omni-flash-realtime`,听孩子说、照着讲稿念;脑是 `qwen3.8-max`,写讲稿和板书。
16
+
17
+ 一行装好(会查 Node 的版本、连不上 npm 就用国内镜像;不用 sudo、不改系统设置)。最后那三个是孩子的短名(小写字母,不用真名)、称呼、年级,不写就只装程序:
18
+
19
+ ```sh
20
+ # Mac(终端里)
21
+ curl -fsSL https://raw.githubusercontent.com/TarsLab/cotutor/main/install.sh | sh -s -- ming 小明 2
22
+ ```
23
+
24
+ ```powershell
25
+ # Windows(PowerShell 里)
26
+ & ([scriptblock]::Create((irm https://raw.githubusercontent.com/TarsLab/cotutor/main/install.ps1))) ming 小明 2
27
+ ```
28
+
29
+ 连不上 GitHub 就把地址换成 `https://cdn.jsdelivr.net/npm/cotutor@latest/install.sh`(Windows 是 `install.ps1`)。也可以一条条自己来:
16
30
 
17
31
  ```sh
18
32
  npm i -g cotutor
@@ -162,6 +176,23 @@ cotutor send math-tutor 你好 --sandbox
162
176
  cotutor serve --sandbox --fresh # 丢掉试过的,从真 workspace 重新分(真的那边改了老师文件、vault 也用这个带过来)
163
177
  ```
164
178
 
179
+ ## 卸载
180
+
181
+ 孩子的文件夹(`~/cotutor/<孩子>/`)和备份(`~/cotutor/.backups/`)是家里的记录,删了找不回来,卸载程序不会碰它们;真不要了自己删。Windows 上命令写成 `cotutor.cmd`、`npm.cmd`。
182
+
183
+ ```sh
184
+ cotutor service uninstall --workspace ~/cotutor/<孩子> # 每个孩子一次;最后一个卸掉时,菜单栏(任务栏)图标也一起卸
185
+ npm rm -g cotutor
186
+ ```
187
+
188
+ 还留在电脑上的,不要了可以删:
189
+
190
+ - `~/.config/cotutor/`:百炼的 key、证书、家长端密码、几项设置
191
+ - 日志:Mac 在 `~/Library/Logs/cotutor/`,Windows 在 `%LOCALAPPDATA%\cotutor\`(里面还有图标的文件),Linux 在 `~/.local/state/cotutor/`;Mac 的图标文件在 `~/Library/Application Support/cotutor/`
192
+ - 签证书时装的根证书:跑 `mkcert -uninstall` 从这台电脑的信任里拿掉,再删 `mkcert -CAROOT` 说的那个目录
193
+ - 平板上:设置 › 通用 › VPN 与设备管理里删掉那个描述文件,主屏幕上的图标长按删掉
194
+ - Windows 防火墙里给 Node.js 开的「允许」:Windows 安全中心 › 防火墙和网络保护 › 允许应用通过防火墙,别的程序也用 Node.js 时留着
195
+
165
196
  ## 开发
166
197
 
167
198
  ```sh
@@ -0,0 +1,8 @@
1
+ export declare const ROOT_CA = "rootCA.pem";
2
+ export declare function defaultCaRoot(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, home?: string): string;
3
+ export declare function rootCaFile(o?: {
4
+ env?: NodeJS.ProcessEnv;
5
+ platform?: NodeJS.Platform;
6
+ home?: string;
7
+ askMkcert?: () => Promise<string>;
8
+ }): Promise<string | null>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 根证书(mkcert 的 rootCA.pem,公钥)在哪:平板扫码装证书时从这里给(serve.ts 的 http 那一面,GET /ca.crt)。
3
+ * 只认 rootCA.pem;同目录的 rootCA-key.pem 是私钥,永远不读、不给。
4
+ * 先看 CAROOT 环境变量和 mkcert 的缺省目录(后台服务的 PATH 里不一定有 mkcert),都没有再问 mkcert -CAROOT。
5
+ */
6
+ import { execFile } from 'node:child_process';
7
+ import { existsSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { promisify } from 'node:util';
11
+ const execFileP = promisify(execFile);
12
+ export const ROOT_CA = 'rootCA.pem';
13
+ export function defaultCaRoot(platform = process.platform, env = process.env, home = homedir()) {
14
+ if (platform === 'darwin')
15
+ return join(home, 'Library', 'Application Support', 'mkcert');
16
+ if (platform === 'win32')
17
+ return join(env.LOCALAPPDATA || join(home, 'AppData', 'Local'), 'mkcert');
18
+ return join(env.XDG_DATA_HOME || join(home, '.local', 'share'), 'mkcert');
19
+ }
20
+ export async function rootCaFile(o = {}) {
21
+ const env = o.env ?? process.env;
22
+ for (const dir of [env.CAROOT, defaultCaRoot(o.platform, env, o.home)]) {
23
+ if (dir && existsSync(join(dir, ROOT_CA)))
24
+ return join(dir, ROOT_CA);
25
+ }
26
+ try {
27
+ const dir = await (o.askMkcert ?? (async () => (await execFileP('mkcert', ['-CAROOT'], { timeout: 5000, env })).stdout.trim()))();
28
+ if (dir && existsSync(join(dir, ROOT_CA)))
29
+ return join(dir, ROOT_CA);
30
+ }
31
+ catch { /* 没装 mkcert */ }
32
+ return null;
33
+ }
@@ -1,4 +1,5 @@
1
- import { type Server } from 'node:http';
1
+ import { type RequestListener, type Server } from 'node:http';
2
+ import { type Server as NetServer } from 'node:net';
2
3
  import { siteUrl } from '../../shared/site-url.ts';
3
4
  import type { ListenInfo } from '../../ui/parent/qr-page.ts';
4
5
  import { type AppContext } from '../http/app.ts';
@@ -14,7 +15,8 @@ export interface ServeOptions extends ResolveOptions {
14
15
  awake?: boolean;
15
16
  }
16
17
  export interface ServeResult {
17
- server: Server;
18
+ /** 用 https 时是前面那层(同一个端口分给 https 和 http);不用 https 就是 http 服务本身 */
19
+ server: NetServer;
18
20
  ctx: AppContext;
19
21
  ws: Workspace;
20
22
  port: number;
@@ -42,6 +44,13 @@ export declare function localName(name?: string): string;
42
44
  export declare function certCovers(certFile: string, host: string): boolean;
43
45
  /** 扫码页要的三样;listen 之后才知道真端口 */
44
46
  export declare function listenInfo(https: boolean, port: number): ListenInfo;
47
+ /**
48
+ * 用 https 时 http 那一面只做两件事:给根证书(平板扫码装证书,这时还不信任 https,所以走 http)、别的都跳到 https
49
+ * (切到 https 以前开着的页面、旧书签、手输的 http 地址)。只给 rootCA.pem,私钥永远不给;找不到就说怎么弄。
50
+ */
51
+ export declare function plainHandler(caFile: () => string | null): RequestListener;
52
+ /** 同一个端口既认 https 又认 http:看第一个字节,TLS 握手(0x16)交给 https,别的交给 http 那一面 */
53
+ export declare function httpAndHttps(secure: Server, plain: Server): NetServer;
45
54
  export declare function serveWorkspace(opts?: ServeOptions): Promise<ServeResult>;
46
55
  /**
47
56
  * busy() 为真就起一个拦闲置睡眠的子进程,为假就停;serve 没了它自己退:macOS 是 caffeinate -i -w <pid>,Windows 是一段 PowerShell(service-windows.ts 的 awakeScript);Linux 什么都不做。
@@ -7,9 +7,11 @@
7
7
  */
8
8
  import { execFileSync, spawn } from 'node:child_process';
9
9
  import { X509Certificate } from 'node:crypto';
10
- import { readFileSync } from 'node:fs';
10
+ import { createReadStream, existsSync, readFileSync } from 'node:fs';
11
11
  import { createServer as createHttp } from 'node:http';
12
12
  import { createServer as createHttps } from 'node:https';
13
+ import { createServer as createNetServer } from 'node:net';
14
+ import { rootCaFile } from "./root-ca.js";
13
15
  import { hostname, networkInterfaces } from 'node:os';
14
16
  import { join } from 'node:path';
15
17
  import { siteUrl } from "../../shared/site-url.js";
@@ -91,6 +93,47 @@ export function listenInfo(https, port) {
91
93
  const ip = lanAddresses()[0];
92
94
  return { name: siteUrl(scheme, localName(), port), ip: ip ? siteUrl(scheme, ip, port) : null, https };
93
95
  }
96
+ /**
97
+ * 用 https 时 http 那一面只做两件事:给根证书(平板扫码装证书,这时还不信任 https,所以走 http)、别的都跳到 https
98
+ * (切到 https 以前开着的页面、旧书签、手输的 http 地址)。只给 rootCA.pem,私钥永远不给;找不到就说怎么弄。
99
+ */
100
+ export function plainHandler(caFile) {
101
+ return (req, res) => {
102
+ const path = (req.url ?? '/').split('?')[0];
103
+ if (req.method === 'GET' && (path === '/ca.crt' || path === '/ca')) {
104
+ const file = caFile();
105
+ if (file && existsSync(file)) {
106
+ res.writeHead(200, { 'content-type': 'application/x-x509-ca-cert', 'content-disposition': 'attachment; filename="cotutor-ca.crt"', 'cache-control': 'no-store' });
107
+ createReadStream(file).pipe(res);
108
+ return;
109
+ }
110
+ res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
111
+ res.end('这台电脑上没找到根证书:先在电脑上跑 mkcert -install 和 cotutor cert');
112
+ return;
113
+ }
114
+ res.writeHead(301, { location: `https://${req.headers.host ?? 'localhost'}${req.url ?? '/'}`, 'cache-control': 'no-store' });
115
+ res.end();
116
+ };
117
+ }
118
+ /** 同一个端口既认 https 又认 http:看第一个字节,TLS 握手(0x16)交给 https,别的交给 http 那一面 */
119
+ export function httpAndHttps(secure, plain) {
120
+ const front = createNetServer((sock) => {
121
+ sock.on('error', () => undefined);
122
+ sock.once('data', (first) => {
123
+ sock.pause();
124
+ sock.unshift(first);
125
+ (first[0] === 0x16 ? secure : plain).emit('connection', sock);
126
+ process.nextTick(() => sock.resume());
127
+ });
128
+ });
129
+ front.once('close', () => {
130
+ for (const s of [secure, plain]) {
131
+ s.close();
132
+ s.closeAllConnections();
133
+ }
134
+ });
135
+ return front;
136
+ }
94
137
  export async function serveWorkspace(opts = {}) {
95
138
  const ws = loadWorkspace(opts.workspace, opts);
96
139
  // 不探 CLI(起服务不用等它);防火墙要查:平板连不上时这里是家长唯一看得到提醒的地方
@@ -101,10 +144,13 @@ export async function serveWorkspace(opts = {}) {
101
144
  ctx.openCalls = () => liveUpgrade.openCalls();
102
145
  const handler = createHandler(ctx);
103
146
  const tls = opts.http ? null : httpsFiles(ws);
104
- const server = tls ? createHttps({ cert: readFileSync(tls.cert), key: readFileSync(tls.key) }, handler) : createHttp(handler);
147
+ const app = tls ? createHttps({ cert: readFileSync(tls.cert), key: readFileSync(tls.key) }, handler) : createHttp(handler);
105
148
  // WebSocket:实时老师的那条线(src/app/ws/);不认就关掉
106
- server.on('upgrade', (req, socket, head) => { void liveUpgrade(req, socket, head, ctx).then((took) => { if (!took)
149
+ app.on('upgrade', (req, socket, head) => { void liveUpgrade(req, socket, head, ctx).then((took) => { if (!took)
107
150
  socket.destroy(); }, () => socket.destroy()); });
151
+ // 用 https 时同一个端口也认 http(给平板装根证书、把 http 的跳过去)
152
+ const caFile = tls ? await rootCaFile() : null;
153
+ const server = tls ? httpAndHttps(app, createHttp(plainHandler(() => caFile))) : app;
108
154
  // 别的程序只占着 127.0.0.1 时,绑通配地址照样成功:平板连到这边,这台电脑上开 localhost 却到了别的程序。先问一声,当被占处理(main.ts 接)
109
155
  if (port !== 0 && (await answersLocally(port)))
110
156
  throw Object.assign(new Error(`port ${port} answers on 127.0.0.1`), { code: 'EADDRINUSE', port });
@@ -23,7 +23,7 @@ export const PIN_RE = /^\d{4,8}$/;
23
23
  const MAX_FAILS = 5;
24
24
  const LOCK_MS = 60_000;
25
25
  /** 孩子端的路径:设了 PIN 也开;别的都要凭证。改孩子端加了新接口要来这里登记(tests/access.test.ts 盯着孩子端页面源码里用到的每条) */
26
- const KID_EXACT = new Set(['/', '/manifest.webmanifest', '/favicon.svg', '/favicon-32.png', '/favicon.ico', '/api/health', '/api/auth', '/dictation', '/dictation/', '/talk', '/talk/', '/api/dictation', '/api/talk']);
26
+ const KID_EXACT = new Set(['/', '/sw.js', '/manifest.webmanifest', '/favicon.svg', '/favicon-32.png', '/favicon.ico', '/api/health', '/api/auth', '/dictation', '/dictation/', '/talk', '/talk/', '/api/dictation', '/api/talk']);
27
27
  const KID_PREFIX = ['/kid/', '/stage/', '/api/kid/', '/api/audio/', '/api/bundles/', '/api/dictation/', '/api/talk/', '/api/live/'];
28
28
  export function isKidPath(p) {
29
29
  return KID_EXACT.has(p) || KID_PREFIX.some((x) => p.startsWith(x)) || /^\/icon-\d{2,4}\.png$/.test(p);
@@ -37,6 +37,7 @@ import { knownLatest, knownNotes } from "../machine/update.js";
37
37
  import { isNewer } from "../../shared/upgrade.js";
38
38
  import { qrPage } from "../../ui/parent/qr-page.js";
39
39
  import { kidPage } from "../../ui/kid/kid-page.js";
40
+ import { KID_SW_JS } from "../../ui/kid/offline-sw.js";
40
41
  import { parentPage } from "../../ui/parent/parent-page.js";
41
42
  import { kidHomeView } from "../../runtime/home/store.js";
42
43
  import { photosOf } from "../../runtime/kid-input.js";
@@ -1070,6 +1071,9 @@ export async function route(method, path, ctx, body, who = 'local', client = {})
1070
1071
  // 主屏幕(iPad「添加到主屏幕」):清单与图标都按标题现生成,没有静态资源
1071
1072
  if (p === '/manifest.webmanifest')
1072
1073
  return { status: 200, json: webManifest(ws.config.title), contentType: 'application/manifest+json; charset=utf-8' };
1074
+ // 孩子端的离线页(offline-sw.ts):平板打开时电脑关了、睡着了,给「老师们还没到」,不给浏览器的报错
1075
+ if (p === '/sw.js')
1076
+ return { status: 200, body: Buffer.from(KID_SW_JS), contentType: 'text/javascript; charset=utf-8', cacheControl: 'no-cache' };
1073
1077
  const icon = /^\/icon-(\d{2,4})\.png$/.exec(p);
1074
1078
  if (icon) {
1075
1079
  const n = Number(icon[1]);
@@ -26,6 +26,7 @@ import { shownAvatar } from "./avatars.js";
26
26
  import { qrNotes, qrSvg } from "../../ui/shared/qr-svg.js";
27
27
  import { readDevices } from "../../runtime/journal/kid-log.js";
28
28
  import { parentSetupRoute } from "./parent-setup.js";
29
+ import { rootCaFile } from "../cli/root-ca.js";
29
30
  const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
30
31
  const bad = (message) => ({ status: 400, json: { error: 'bad_request', message } });
31
32
  /** 家长端列的老师:有脸的(-tutor 结尾)、开着的;藏着的也列(家长要能把他放回首页) */
@@ -154,9 +155,13 @@ async function kidQr(ctx) {
154
155
  const devices = Object.values(await readDevices(ctx.ws));
155
156
  const last = devices.map((d) => d.last).sort().at(-1) ?? null;
156
157
  const parent = { url: `${info.name}parent`, ip: info.ip ? `${info.ip}parent` : null };
158
+ // 装根证书:走同一个端口的 http 那一面(serve.ts 的 plainHandler),平板这时还不信任 https;端口照写,http 缺省是 80
159
+ const caUrl = (u) => { const x = new URL(u); return `http://${x.hostname}:${x.port || '443'}/ca.crt`; };
160
+ const ca = info.https && (await rootCaFile()) ? { url: caUrl(info.name), ip: info.ip ? caUrl(info.ip) : null } : null;
157
161
  return {
158
162
  ready: true, https: info.https, url: info.name, svg: qrSvg(info.name), ip: info.ip, svgIp: info.ip ? qrSvg(info.ip) : null,
159
163
  parent: { ...parent, svg: qrSvg(parent.url), svgIp: parent.ip ? qrSvg(parent.ip) : null },
164
+ ca: ca ? { ...ca, svg: qrSvg(ca.url), svgIp: ca.ip ? qrSvg(ca.ip) : null } : null,
160
165
  notes: qrNotes(info.https).slice(1), devices: devices.length, last,
161
166
  };
162
167
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 舞台包的静态文件:dist/stage/(esbuild 打的 stage.js / stage.css / index.html)+ /stage/fonts/(excalidraw 的字体:dist/stage/fonts/ 在就给它,
3
- * 不在从 node_modules 现取;部署包把字体拷进 dist/stage/fonts/、去掉 excalidraw 依赖,见 docs/wip/deploy/)。
2
+ * 舞台包的静态文件:dist/stage/(esbuild 打的 stage.js / stage.css / index.html)+ /stage/fonts/(excalidraw 的字体)。
3
+ * 字体由 build:stage 拷进 dist/stage/fonts/,npm 装的包里没有 excalidraw(它只在打包时用);开发时没打包就从 node_modules 现取。
4
4
  * serve 与 mock 共用;没打包(开发时没跑 pnpm run build:stage)→ null,页面开重卡舞台时什么都不出现,doctor 点名。
5
5
  */
6
6
  import { existsSync } from 'node:fs';
@@ -74,6 +74,7 @@ import { kidThreads } from "../../runtime/kid-view.js";
74
74
  import { ICON_SIZES, appIconPng, webManifest } from "../../ui/shared/icon.js";
75
75
  import { faviconResponse } from "../../ui/shared/favicon.js";
76
76
  import { kidPage } from "../../ui/kid/kid-page.js";
77
+ import { KID_SW_JS } from "../../ui/kid/offline-sw.js";
77
78
  import { parentPage } from "../../ui/parent/parent-page.js";
78
79
  import { arrangeHome, homeOwners, kidButtons, kidTiles, parseHome } from "../../runtime/home/home.js";
79
80
  import { packageTheme } from "../cli/themes.js";
@@ -644,6 +645,8 @@ export function createMock(opts = {}) {
644
645
  return { status: 200, html: VOICE_GALLERY_PAGE };
645
646
  if (p === '/manifest.webmanifest')
646
647
  return { status: 200, json: webManifest(title), contentType: 'application/manifest+json; charset=utf-8' };
648
+ if (p === '/sw.js')
649
+ return { status: 200, body: Buffer.from(KID_SW_JS), contentType: 'text/javascript; charset=utf-8', cacheControl: 'no-cache' };
647
650
  // 家长端「记录」:自己的一页(parent-page.ts);清单接口照真服务(答案不剥,第一节带一条给家长的尾巴与记忆)。mock 没有工作台 /dev
648
651
  if (p === '/parent')
649
652
  return { status: 200, html: parentPage(title) };