or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Syncing
xxxxxxxxxx
HackMD 的前世與今生,以及未來
Build a community with open collaboration
Max Wu @ WebConf Taiwan 2023
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →Max Wu
是個喜歡技術的人,也喜歡玩遊戲!
HackMD
Build a community with open collaboration
開放協作!建立社群!
出沒於各式 Conf
PyCon TW COSCUP MOPCON SITCON HITCON LaravelConf Modern Web DevOpsDay Taipei Agile Summit
WebConf
上面有哪些社群?
Ethereum g0v Rust Lang nf-core ETHTaipei GovComms Wikidata Taiwan OpenStreetMap HackingThursday matplotlib Fedora OpenStack Kubernetes Node.js OCaml OpenStack Nordic RSE Astro
歡迎大家來蓋社群!
一句話說明 HackMD 用來做什麼?
用純文字與他人協作知識的平台
黑客松、社群小聚、遠端會議、實驗研究、開發文件等…
網頁上的即時通訊
HackMD 如何讓協作能很即時的收到更新?
方案
Note:
polling:前端定時向伺服器發出請求
long-polling:前端發出一個長等待伺服器回應的請求
Server-Sent Events:由伺服器對瀏覽器發送資料
WebSocket:建立即時雙向溝通,基於 TCP
WebRTC:建立即時影像與音訊溝通,大多基於 UDP
WebSocket
最開始的時候就採用
socket.io
Note:
socket.io 的編碼,有考慮瀏覽器支援度:
https://socket.io/docs/v4/custom-parser/
如果要傳遞 binary 的資料,可以考慮用 msgpack
transport 可以改成只用 websocket 連線,避免需要設定複雜的 sticky session
https://socket.io/docs/v4/using-multiple-nodes/
要注意 socket.io 的功能版本差異,但是官網文件寫得相當完整,建議閱讀
https://socket.io/docs/v4/how-it-works/#socketio
我們都稱作 MMORPG
筆記 = 房間
筆記內文 = 地圖
使用者 = 玩家
游標位置 = 玩家座標
Note:
立即下載,送火槍兵!
如何讓協作體驗更好?
同時有多人要加入房間
Image Not Showing
Possible Reasons
需要排隊
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →一台伺服器能乘載的人數有限
Image Not Showing
Possible Reasons
分多台協作伺服器
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →內文或是資料太大包
Image Not Showing
Possible Reasons
只回傳差異範圍
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →如何知道使用者是否在線上?
heartbeat
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →每隔幾秒跟伺服器問候一下 (ping/pong)
這樣才能確認正在線上的所有 clients 名單
如果 heartbeat 沒有回應,會視同斷線
因此如果前端太忙無法回應,就會斷線了喔!
架設多台協作節點
當有越多 clients,會消耗越多的記憶體 (線性成長)
因此有大量的使用者時,會需要多台的節點
可以用 Cluster 模式,這樣也能利用到多核心 CPU 的優勢
Note:
Node.js 預設是 single thread
如何架設多台請見:
https://socket.io/docs/v4/using-multiple-nodes/
Sticky Session
每台節點的 context 不同 (例如暫存的筆記內容不同)
當 client 傳遞事件時,需要保持跟其中一台對談
如果每次都送到不同節點,那節點之間就要付出事件傳遞的成本
Note:
除非你可以讓節點的 context 是無狀態 (通常蠻困難的)
如何啟用 Sticky Session 請見:
https://socket.io/docs/v4/using-multiple-nodes/
跨節點傳遞事件
有些狀況需要從讓協作節點間互相傳遞事件
或是讓外部傳遞事件進入
例如:
打 web API,需要讓協作節點同步資料,也向 client 發出事件
這時候可以用
socket.io
Adapter,有多種實作方式:redis, mongodb, postgresql
Note:
socket.io
adapterhttps://socket.io/docs/v4/adapter/
如何調整效能?
後端的 websocket 有多種實作,可以替換成
eiows, uws, uWebSockets.js, ws
也可以在使用者離線事件中,手動觸發
gc()
清出記憶體Note:
效能調整請見:
https://socket.io/docs/v4/performance-tuning/
https://socket.io/docs/v4/memory-usage/
怎麼變成
Image Not Showing
Possible Reasons
socket.io
推坑大會- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →即時連線效能調好了,那前端呢?
這個水很深
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →欸?我的 websocket 怎麼斷線了!
剛說過,因為有 heartbeat,如果:
以上都會導致 websocket 暫時斷線
自動重新連線的機制,
socket.io
有實作那怎麼辦?
需要減少同時傳入的事件
可以將多個事件累計後變成一個事件發出
需要認知到 websocket 並不能保證使用者隨時都在線上
因此要做好斷線與重連的恢復機制
Note:
詳細請讀:Page Lifecycle API
https://developer.chrome.com/blog/page-lifecycle-api/#developer-recommendations-for-each-state
長文件效能:分區段顯示
筆記內文 = 地圖
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →如果內文很長需要捲動,顯示的效能會嚴重影響閱讀體驗
如果偵測哪些元素是在可見範圍,只針對那塊顯示與渲染?
但檢查哪些元素是可見的,就會需要評估元素的大小與位置
Element.getBoundingClientRect()
這會觸發瀏覽器 layout
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →有方法可以讓元素自己告訴我它現在是否可以被看到嗎?
Note:
建議閱讀
https://developer.chrome.com/blog/inside-browser-part3
Intersection Observer API
建立一個 IntersectionObserver 給予可捲動的元素 root
當捲動元素,使內部的元素進入或是離開可見範圍時
會呼叫 callback 事件,參數會提供元素的資訊與交集比例
boundingClientRect, intersectionRatio, intersectionRect, isIntersecting, rootBounds, target, time
Note:
Intersection Observer API
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
這樣一來,我們可以完全不用去記錄與計算每個元素的位置
處於觀察者的角色,等待元素交集時的事件發生
當
isIntersecting
為true
時,就標示這個元素可見或是設定將不可見的元素給予
visibility: hidden;
渲染時可以只針對可見元素處理做後處理
(例如:顯示數學公式)
效能好到不行
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →但別忘記 XSS!顯示前要先過濾 (會影響效能)
Note:
延伸閱讀 MutationObserver
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
如果有 DOM 要做部分更新,可以考慮用
https://github.com/patrick-steele-idem/morphdom
那些年遇過的 XSS
就像抓漏,怎樣都有洞…
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →什麼是 XSS?
簡單來說,在使用者可以輸入的地方
寫入
<script>
或是各種奇形怪招讓網頁執行的時候,組成非預期的程式碼
把使用者的資料傳出去,或是做不該做的事
(例如把 Cookies 偷走、偷偷跑挖礦程式)
Note:
https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting
XSS 環境清潔方式
幫 HTML 消毒 Sanitizer
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →Before:
abc <script>alert(1)</script> def
After:
abc def
你以為這樣就乾淨了嗎?
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →Note:
Chrome 有新實作 HTML Sanitizer API
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API
我們採用了這套:
https://github.com/leizongmin/js-xss
orange 的 XSS
直接對過濾 XSS 的 function
preventXSS
下手利用 CSP 允許的
https://cdnjs.cloudflare.com/
載入 AngularJS再用 template injection 執行不安全的程式碼
{{constructor.constructor('alert(1)')()}}
結論:HTML 註解語法也要處理!
Note:
CSP
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
完整 writeup 與 payload 請見
https://blog.orange.tw/2019/03/a-wormable-xss-on-hackmd.html
k1tten 的 XSS
$value.parent().append('<div class="alert alert-warning">' + err + '</div>')
err
會直接顯示在網頁上,沒做過濾但是有字數限制結論:錯誤訊息也要過濾 XSS!
Note:
完整 writeup
https://github.com/k1tten/writeups/blob/master/bugbounty_writeup/HackMD_XSS_%26_Bypass_CSP.md
maple3142 的 XSS
HackMD 新增了顯示 figma 的功能,然後就出漏洞了…
顯示時會找尋網頁
data-figma-src
的值來組合成 figma 網址而這段沒過濾!
這次被繞過 CSP 用的是
www.google.com
的 JSONP callbackhttps://www.google.com/complete/search?client=chrome&q=123&jsonp=alert(1)//
怎麼被回報漏洞的速度比出功能的速度還快?!結論:code review 時也要檢查 XSS!
Note:
JSONP
https://blog.logrocket.com/jsonp-demystified-what-it-is-and-why-it-exists/
完整 writeup
https://blog.maple3142.net/2022/08/03/hackmd-xss
splitline 的 XSS
利用嵌入 vimeo 影片的語法,顯示時會呼叫 vimeo 的 JSONP
//vimeo.com/api/v2/video/346762373.json?callback=alert#.json?callback=random
幸好 vimeo 會過濾 callback 的參數
callback 時會把不安全的指令過濾…
以為就這樣嗎?但漏洞總是挖呀挖挖呀挖
splitline 的 XSS cont.
發現
https://vimeo.com/blog
是用 WordPress 架設的整個網站找啊找找到一程式碼,可以非同步載入 script
s.src = _zxcvbnSettings.src;
這時候,使出 DOM clobbering,控制 src 的值放入 payload:
<img src="https://host/xss.js" id="_zxcvbnSettings">
結論:嚴格過濾影片網址 videoid
Note:
完整 writeup & payload
https://blog.splitline.tw/hackmd-xss
CSP 又被繞過了…外站的程式被利用成本站的漏洞…
就算有 CSP, X-XSS-Protection, Sanitizer 等
也不代表就沒有 XSS 漏洞,總是有繞過的招數
做新功能時要檢查是否有正確的過濾字串再顯示
HackMD 需要非常注意過濾處理的效能
人生真難
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →Note:
X-XSS-Protection
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
Imgur 圖片大搬家
免費的最貴…
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →有一天,收到了這樣的通知
Note:
https://help.imgur.com/hc/en-us/articles/14415587638029/
HackMD 從很早的時候就有整合 Imgur
方便使用者在寫筆記時能直接上傳圖片
這下…麻煩了…
要寫程式處理所有筆記嗎?
有多少篇筆記裡面有多少個 imgur 圖片網址呢?
127 萬篇筆記中有 900 萬張圖片!
這…也太多圖片要處理了!
要如何快還要更快呢?
SQL comes to Rescue!
直接用 SQL 跑 regex 將筆記中的網址插入 table
regexp_matches(content, E'(https://i\\.imgur\\.com/([^\\s)\\n"\\/\\]\\?][a-zA-Z0-9]+\\.[a-zA-Z0-9]+))', 'g') AS urls
轉移圖片到 S3
有這麼多圖片網址,要怎麼樣在一週內全部轉移?
AWS Lambda 平行跑起來!
最多時有 15 個工作平行在跑,每秒可以轉移 50~100 張圖片

最後統計一共轉移了 1.9TB 的資料
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →未來所有使用者上傳圖片都使用 HackMD 的專屬圖床
避免這種事情再次發生,一勞永逸!
Note:
雖然外部服務很方便,但是當服務用量成長之後,很容易成為絆腳石
付錢或是自行架設都是解方
比較字串中遇到的 Emoji 問題
裂開了就沒辦法顯示!
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →各位知道 emoji 可能是用多個 Unicode 表示嗎?
👋➕🏻🟰👋🏻
Emoji Modifier Sequences
Note:
https://css-tricks.com/changing-emoji-skin-tones-programmatically/
更神奇的是!
怎麼會有兩個 Emoji 的 UTF-16 編碼相同?
Note:
試試看:
同一個 Emoji 編碼怎麼又不同?

🤯🤯🤯
Note:
試試看:
Unicode 是個 21 位元的標準
JavaScript 採用的是 UTF-16,最多可以表示 65,535 個字元
有些 Emoji 編碼索引超過 65,535,就會用兩個 Unicode 組成
一個高位字元 (high surrogate) 與一個低位字元 (low surrogate) 組成一對 (surrogate pair) 來表示
Note:
參考資料:
http://russellcottrell.com/greek/utilities/SurrogatePairCalculator.htm
https://evanhahn.com/utf-21/
https://docs.oracle.com/cd/E19683-01/816-3982/6ma7og00a/index.html
相信大家用過 git 可能看過這個:
GNU diff Unified Format
HackMD 會自動幫筆記產生版本紀錄
每個版本紀錄會計算出差異範圍
diff-match-patch
diff-match-patch 是什麼?
Diff: 比較字串差異
Match: 找出相近字串
Patch: 套用字串差異
HackMD 採用這套比對文字差異
Note:
diff-match-patch
https://github.com/google/diff-match-patch
如果 diff 兩個 emoji
有可能把 emoji 切成兩個 Unicode 來比較
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →前方有坑!!!
diff 可能會產出 lone surrogates 導致無法顯示
Note:
https://github.com/google/diff-match-patch/issues/59
坑點 1:
patch_toText
坑點 2:
diff_main
官方 demo 可以重現
https://neil.fraser.name/software/diff_match_patch/demos/diff.html
補坑選項
Google 萬年不 merge PRNote:
https://github.com/google/diff-match-patch/pull/69
https://github.com/google/diff-match-patch/pull/13
https://github.com/google/diff-match-patch/pull/80
After 補坑
可以正常的 diff 出 emoji 的變更了!!
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →Note:
我們採用了這個 PR
https://github.com/google/diff-match-patch/pull/80
似乎 Simplenote 也用這個修正,沒出過什麼問題
HackMD 2.0
知識的三個層次
自己
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →自己與未來的他人
Image Not Showing
Possible Reasons
有限的團體
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →與不特定對象連結
Image Not Showing
Possible Reasons
公眾
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →Note:
各位做筆記時,是為了自己還是誰呢?
全新設計的編輯器
Light/Dark mode
支援 Grammarly、手機版體驗提升
編輯與顯示效能大提升
用協作打造新型態知識網路
社群、留言討論、探索知識
串連世界上的節點!
現在立即體驗
https://hackmd.io
設定
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →Thanks for listening!
https://hackmd.io/@MaxWu/hackmd-webconf-2023
本簡報使用 HackMD 製作與發佈