0

Ba cách làm app chặn Reels trên iOS: content blocker, tiện ích Safari hay WKWebView

Mình là Ayumu Kitabayashi, đang tự làm một mình app iPhone minulo: ẩn Reels của Instagram và Shorts của YouTube nhưng vẫn giữ tin nhắn (DM). Trước khi viết dòng code nào, mình phải chọn cách "ẩn", và trên iOS thực ra chỉ có ba hướng. Khác biệt giữa chúng không nằm nhiều ở chỗ ẩn được gì, mà ở ai được đọc trang web và người dùng có thật sự đi vào code của mình hay không.

Bài này so sánh ba hướng đó và lý do mình chọn hướng thứ ba.

Trước tiên: Screen Time không làm được gì

Ý tưởng đầu tiên thường là Screen Time API (FamilyControls + ManagedSettings). Nó chặn được cả một app, hoặc cả một tên miền trong Safari. Nhưng nó không đi vào bên trong app được: không có cách nào nói "Instagram thì được, riêng tab Reels thì không". App Instagram và YouTube gốc đóng với bên thứ ba, và iOS cũng không có thứ gì giống accessibility service của Android để đọc màn hình của app khác.

Vì vậy, mọi app chặn Reels trên iOS đều làm việc với bản web của mạng xã hội. Câu hỏi chỉ là trang web đó được hiển thị ở đâu.

Cách 1: Content blocker của Safari

Content blocker là một danh sách quy tắc dạng JSON. Safari tự biên dịch và tự áp dụng. Code của bạn không bao giờ chạy trên trang.

[{
  "trigger": { "url-filter": "^https://www\\.instagram\\.com/" },
  "action": { "type": "css-display-none", "selector": "a[href='/reels/']" }
}]

Điểm mạnh:

  • Riêng tư từ thiết kế: tiện ích không đọc được trang, nên không có gì để rò rỉ.
  • Nhanh, vì Safari biên dịch quy tắc trước.
  • Ít tốn công bảo trì.

Giới hạn mình gặp khi chuẩn bị quy tắc:

  • CSS duy nhất bạn có là display: none trên một selector. Không đổi được thuộc tính khác, nên không thể "vô hiệu hoá" mà không xoá hẳn. Ở bản web di động của một mạng xã hội, thanh tab dưới cùng được xếp theo vị trí: xoá một tab bằng display: none thì các tab bên cạnh không dồn lại, còn ẩn cả khung tab thì để lại một ô xám trống. Muốn sửa phải dùng JavaScript.
  • url-filter là một dạng regex bị giới hạn. Ví dụ không có |, nên "hai đường dẫn này" nghĩa là hai quy tắc.
  • Hãy chặt chẽ với định dạng. Theo mình hiểu về trình phân tích của Safari, một khoá lạ có thể khiến cả danh sách bị từ chối mà trang không báo gì. Mình để metadata (quy tắc nào của mạng nào) ở một file riêng, theo đúng thứ tự của danh sách.

Cách 2: Tiện ích web của Safari (content script)

Tiện ích web có thể chèn CSS và JavaScript, nên xử lý được những gì content blocker không làm được: theo dõi DOM, vô hiệu hoá nút, chuyển /reels/ sang hộp thư.

Cái giá phải trả:

  • Script đọc được trang, kể cả DM của người dùng trên trang đó. Safari hỏi quyền theo từng trang và cảnh báo rằng tiện ích có thể đọc trang. Với một app lẽ ra giúp điện thoại "yên" hơn, đó là màn hình đầu tiên khó chấp nhận.
  • Bạn phải bảo trì JavaScript dựa trên DOM của người khác, thứ có thể đổi bất cứ lúc nào.

Quy tắc của mình: cái gì biểu diễn được bằng selector thì để ở content blocker, chỉ cái nào thật sự cần script mới đưa vào script.

Vấn đề chung của cả hai cách dùng Safari

Cả hai chỉ chạy trong Safari. Thói quen ta muốn bỏ là bấm vào biểu tượng Instagram, mà biểu tượng đó mở app gốc, nơi tiện ích Safari không tồn tại. Người dùng phải nhớ vào instagram.com bằng Safari mỗi lần, đúng vào lúc họ ít có khả năng nhớ nhất.

Trên máy tính thì không sao, vì người ta vốn sống trong trình duyệt. Trên điện thoại, đó là điểm yếu.

Cách 3: App của riêng mình với WKWebView

Đây là cách minulo làm. App mở bản web chính thức của mạng xã hội trong một WKWebView và chèn CSS, JS của mình. Đăng nhập diễn ra trên trang đăng nhập của chính mạng xã hội, bên trong web view đó.

Điều làm cách này hiệu quả trên iPhone là kết hợp với Screen Time: minulo có thể chặn app Instagram gốc, và màn hình chặn có nút đưa bạn sang minulo. Thói quen bấm biểu tượng sẽ dẫn tới một nơi không có Reels. (Mình đã viết về cách mở app của mình từ màn hình chặn, khó hơn tưởng tượng.)

Những việc mình phải xử lý:

Chèn hai lần. CSS đi vào qua injectedJavaScriptBeforeContentLoaded (react-native-webview), để Reels không loé lên trước khi quy tắc có hiệu lực. Phần hành vi chèn sau khi tải, cùng MutationObserver, vì các trang này liên tục thêm DOM rất lâu sau lần vẽ đầu.

Quy tắc theo URL phải áp lại. Việc chèn chạy một lần cho mỗi document, nhưng single-page app đổi URL mà không tải lại. Một quy tắc chỉ dành cho /explore/ sẽ hoặc áp dụng khắp nơi, hoặc không ở đâu cả, trừ khi script tự theo dõi URL và tự thay một thẻ <style> theo phạm vi.

Selector hỏng mà không báo. Khi trang đổi tên class, CSS không ném lỗi, nó chỉ ngừng khớp. Mình đếm số lần khớp của từng quy tắc và coi quy tắc cứ khớp 0 lần là đã hỏng.

Code được chèn chạy trong "thế giới" của trang. JavaScript của chính trang web cũng gọi được window.ReactNativeWebView.postMessage dễ như code của bạn. Tin nhắn từ trang gửi về app là không đáng tin, nên app kiểm tra chúng bằng một token theo từng màn hình và danh sách loại tin được phép, và chỉ coi đó là lớp phòng thủ đầu tiên.

Gửi user agent mới. Trước đây mình dùng một user agent Safari cố định, cũ hơn hai đời iOS. Khi đọc review trên App Store của một app web view tương tự, có 18 review nhắc đến cảnh báo đăng nhập đáng ngờ, kiểm tra bot hoặc bị khoá tài khoản. User agent cũ là thứ dễ khiến bạn nổi bật, nên giờ app báo cùng phiên bản Safari với thiết bị đang chạy.

Một nguồn quy tắc, ba đầu ra

Mình không muốn phải chọn mãi mãi, nên các quy tắc nằm trong một file TypeScript và được xuất ra:

  • quy tắc chỉ cần selector thành JSON cho content blocker
  • vài quy tắc cần code thành JS cho content script
  • tất cả được chèn vào WKWebView trong app

Cùng một khai báo (urlFilter, css, redirect) được cả ba đọc. Trước đây app bỏ qua urlFilter trong khi hai đầu ra kia tôn trọng nó, và trên iOS có một quy tắc ẩn đồ ở mọi trang. Khi cả ba đọc chung một khai báo, kiểu lệch đó lộ ra ở một chỗ thay vì ba.

Tóm tắt

Content blocker Tiện ích Safari App WKWebView
Đọc được trang Không Có Có
Vô hiệu hoá, chuyển hướng (không chỉ ẩn) Không Có Có
Có tác dụng khi bấm biểu tượng Instagram Không Không Có, nhờ màn hình chặn của Screen Time
Bảo trì Thấp Trung bình Cao
Phù hợp nhất với Máy tính, ưu tiên riêng tư Người dùng máy tính thích tuỳ chỉnh Điện thoại

Nếu bạn làm cho máy tính, hãy bắt đầu bằng content blocker và chỉ thêm content script khi buộc phải. Trên iPhone, yếu tố quyết định là biểu tượng app: thứ bạn làm ra phải nằm đúng chỗ ngón tay người dùng chạm vào.


Mình làm minulo, app iPhone ẩn Reels và Shorts nhưng vẫn giữ DM. Việc ẩn là miễn phí. Trong lúc app Instagram gốc đang bị chặn, thông báo của nó sẽ không đến: minulo được làm như vậy. Bạn vẫn đọc và trả lời DM trong minulo.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí