วิธีทำให้การติดตามคำเชิญใช้งานแอปมือถือจากการแชร์ผ่านเว็บเป็นแบบอัตโนมัติ

opoinstall
2026-10-28
5 min read

จะตั้งค่าการติดตามคำเชิญใช้งานสำหรับมือถือแบบอัตโนมัติได้อย่างไร? การตั้งค่าการติดตามคำเชิญแบบอัตโนมัติต้องอาศัยการรวม SDK การระบุที่มา การแนบโทเค็นคำเชิญแบบทึบแสง (shareCode) ไปกับลิงก์แชร์บนเว็บ การดึงข้อมูลดังกล่าวบนหน้าแลนดิ้งเพจ และการใช้ความสามารถการลิงก์ลึกแบบหน่วงเวลาที่รองรับโดยแพลตฟอร์ม เพื่อพยายามกู้คืนพารามิเตอร์ในการเปิดแอปพลิเคชันครั้งแรก

การติดตามคำเชิญใช้งานแบบอัตโนมัติคือโครงสร้างพื้นฐานทางวิศวกรรมที่จับคู่การติดตั้งแอปพลิเคชันมือถือเข้ากับการกระทำในการแชร์ของผู้ใช้เดิมผ่านโปรแกรม ด้วยการส่งตัวระบุผู้เชิญแบบทึบแสง (shareCode) ผ่านหน้าแลนดิ้งเพจบนเว็บ และพยายามแก้ไขบริบทนี้เมื่อเปิดแอปพลิเคชันครั้งแรก การติดตามคำเชิญแบบอัตโนมัติจะช่วยลดการป้อนรหัสโปรโมชันด้วยตนเอง และรองรับการระบุที่มาผ่านคอนเทนต์เว็บเบราว์เซอร์ในแอป

คำศัพท์ คำนิยาม เอนทิตีที่เกี่ยวข้อง บทบาทความตั้งใจในการค้นหา
การติดตามคำเชิญ (Referral Tracking) การระบุที่มาของการได้มาซึ่งผู้ใช้ปลายทางไปยังเซสชันการแชร์ของผู้ใช้เดิมเฉพาะรายผ่านโปรแกรม การติดตั้งพารามิเตอร์แอป เชิงข้อมูล / เชิงพาณิชย์
ซอฟต์แวร์ติดตามคำเชิญ โครงสร้างพื้นฐาน SDK และเซิร์ฟเวอร์ที่ออกแบบมาเพื่อทำให้การผูกมัดระหว่างผู้เชิญและผู้ถูกเชิญเป็นแบบอัตโนมัติ การทำลิงก์ลึกแบบหน่วงเวลา (Deferred Deep Linking) เชิงพาณิชย์
โปรแกรมคำเชิญใช้งานแอป โครงการส่งเสริมการเติบโตที่จูงใจให้ผู้ใช้ปัจจุบันเชิญเพื่อนใหม่มาติดตั้งแอปพลิเคชัน สถิติและติดตามช่องทาง เชิงพาณิชย์
ระบบคำเชิญใช้งาน สถาปัตยกรรมทางเทคนิคแบบครบวงจรที่จัดการการสร้างลิงก์ การระบุที่มา และการออกรางวัล เว็บ JS SDK เชิงเทคนิค

ภาพรวมสถาปัตยกรรม: การติดตามคำเชิญอัตโนมัติเชื่อมโยงการแชร์โซเชียลกับการติดตั้งแอปอย่างไร

การวิเคราะห์เชิงกลไกของระบบคำเชิญรุ่นเก่า: เหตุใดรหัสโปรโมชันแบบเดิมจึงลดความเร็วของฟวยล์การแปลง

โปรแกรมคำเชิญใช้งานบนมือถือแบบดั้งเดิมอาศัยรหัสโปรโมชันตัวอักษรและตัวเลขแบบใช้มือกรอก ภายใต้เวิร์กฟิลด์รุ่นเก่านี้ ผู้ใช้ปัจจุบันจะสร้างรหัสคำเชิญ (เช่น INVITE2026) คัดลอกข้อความด้วยตนเอง และส่งให้เพื่อนผ่าน SMS หรือแอปข้อความโซเชียล จากนั้นผู้ใช้ที่ได้รับเชิญจะต้องดาวน์โหลดแอปพลิเคชันจากร้านค้าแอป กรอกข้อมูลการลงทะเบียนให้เสร็จสิ้น ค้นหาช่องป้อนรหัสโปรโมชันเฉพาะ และพิมพ์ชุดอักขระนั้นให้ถูกต้อง

กระบวนการแบบ manual นี้สร้างอุปสรรคสำคัญตลอดเส้นทางการได้มาซึ่งผู้ใช้:

  • การละทิ้งกระบวนการด้านความคิดและเครื่องกล: ผู้ใช้มักลืมคัดลอกรหัสบัตรกำนัล หาช่องป้อนรหัสไม่พบในเมนูการเริ่มต้นใช้งานเชิงลึก หรือละทิ้งกระบวนการเมื่อรหัสใช้งานไม่ได้เนื่องจากข้อผิดพลาดในการใช้ตัวพิมพ์ใหญ่-เล็กหรือการพิมพ์
  • การรั่วไหลของช่องทางและการดึงข้อมูลรหัส: รหัสตัวอักษรและตัวเลขสามารถคัดลอกและเผยแพร่ไปยังเว็บไซต์รวบรวมคูปองสาธารณะหรือกระดานสนทนาได้อย่างง่ายดาย สิ่งนี้ตัดความสัมพันธ์แบบเพียร์ทูเพียร์ที่แท้จริง อนุญาตให้ผู้ใช้แบบออร์แกนิกที่ไม่ได้รับคำเชิญมาเคลมรางวัลได้ ในขณะที่ทำให้หน่วยเศรษฐศาสตร์ของแคมเปญลดลง
  • จุดบอดในการวัดผล: การพึ่งพารหัสโปรโมชันแบบแมนนวลเพียงอย่างเดียวทำให้เหตุการณ์สำคัญในฟวยล์ระดับกลาง (เช่น การแสดงผลลิงก์ การคลิกเว็บวิวโซเชียล และอัตราการส่งต่อร้านค้า) ไม่ได้รับการวัดผล เว้นแต่จะมีการติดตั้งเครื่องมือวิเคราะห์แยกต่างหาก

ท่อส่งการแชร์แบบโปรแกรม: การกระทำของผู้เชิญ การจัดเก็บเว็บ การส่งต่อไปยังสโตร์ และการแก้ไขปัญหาในวันที่ 0

บริบทการเชิญเดินทางจากลิงก์แชร์มือถือไปยังหน้าแลนดิ้งเพจบนเว็บ

การติดตามคำเชิญแบบอัตโนมัติจะแทนที่การพิมพ์ด้วยมือด้วยท่อส่งพารามิเตอร์แบบโปรแกรมอย่างต่อเนื่อง แทนที่จะแจกจ่ายลิงก์สโตร์ที่ไม่มีพารามิเตอร์พร้อมกับบัตรกำนัลข้อความที่ตัดขาดจากกัน แอปพลิเคชันจะรวมตัวตนของผู้เชิญไว้ใน URL การแชร์ผ่านเว็บแบบโต้ตอบโดยตรง

วงจรชีวิตทางเทคนิคทำงานผ่าน 4 ขั้นตอนที่แยกจากกัน:

[ผู้ใช้เดิมเชิญเพื่อน] ──> เรียก shareTelemetry.reportIntent(shareCode, Platform)
              │
              ▼
[สร้างลิงก์เว็บแบบไดนามิก] ───> เพิ่มพารามิเตอร์ ?shareCode=usr_9f82a1b4 ไปยัง URL ปลายทาง
              │
              ▼
[แชร์ภายใน WeChat/Line] ────> เพื่อนแตะลิงก์ภายในเว็บวิวแบบแซนด์บ็อกซ์
              │
              ▼
[การจัดเตรียมและการส่งต่อเว็บ JS SDK] ─>แคชบริบทและดำเนินการเปิดแอป / ดาวน์โหลดจากสโตร์
              │
              ▼
[เปิดแอปครั้งแรกบนอุปกรณ์] ───> SDK เนทีฟสอบถามการกู้คืนพารามิเตอร์
              │
              ▼
[การผูกมัดแบ็กเอนด์ของแอปพลิเคชัน] ──> แบ็กเอนด์ตรวจสอบโทเค็นและสร้างการผูกมัดที่รอดำเนินการ
              │
              ▼
[เหตุการณ์สำคัญในการเปิดใช้งานที่ตรวจสอบแล้ว] ─> แบ็กเอนด์จ่ายรางวัลเมื่อทำภารกิจสำเร็จ

เมื่อผู้ใช้ที่ได้รับเชิญเปิดลิงก์ หน้าแลนดิ้งเพจบนเว็บจะรัน Web JS SDK ที่ดึงโทเค็นของผู้เชิญ (shareCode) และจัดเก็บ บริบทคำเชิญ เมื่อผู้ใช้ดำเนินการติดตั้งและเปิดแอปพลิเคชันเนทีฟ SDK ฝั่งไคลเอนต์จะดึงเพย์โหลดที่จัดเก็บไว้ระหว่างการเปิดใช้งานครั้งแรก ช่วยให้แบ็กเอนด์ของแอปพลิเคชันสามารถผูกความสัมพันธ์คำเชิญได้โดยอัตโนมัติโดยไม่ต้องให้ผู้เข้าร่วมป้อนข้อมูลด้วยตนเอง

การกู้คืนข้อมูลหลังการเปิดแอปครั้งแรกจากสโตร์: ข้อจำกัดเฉพาะของแต่ละแพลตฟอร์ม

หลังจากที่ผู้ใช้ติดตั้งแอปพลิเคชันจากสโตร์แอป การกู้คืนพารามิเตอร์คำเชิญจะขึ้นอยู่กับแพลตฟอร์มและกลไกที่แอปพลิเคชันหรือ SDK การระบุที่มานำไปใช้ การส่ง shareCode ผ่าน URL ของเว็บไม่ได้หมายความว่าพารามิเตอร์นั้นจะใช้งานได้กับแอปพลิเคชันที่เพิ่งติดตั้งใหม่โดยอัตโนมัติ

  • Android / Google Play: Google Play Install Referrer สามารถให้ข้อมูลเมตาการเชิญที่รองรับซึ่งเกี่ยวข้องกับการติดตั้งจาก Play Store แอปพลิเคชันต้องกำหนดค่าและทดสอบเพย์โหลดรีเฟอร์เรอร์จริง และตรวจสอบว่าพารามิเตอร์ที่คาดหวังรอดพ้นจากการเปลี่ยนเส้นทางและขั้นตอนการติดตั้ง SDK การระบุที่มาของบุคคลที่สามอาจใช้กลไกเพิ่มเติม ซึ่งต้องมีการจัดทำเอกสารแยกต่างหาก
  • iOS / App Store: App Store ไม่ได้ถ่ายโอนพารามิเตอร์คิวรีของหน้าแลนดิ้งเพจตามอำเภอใจไปยังแอปพลิเคชันที่ติดตั้งโดยอัตโนมัติ Universal Links รองรับการกำหนดเส้นทางโดยตรงเมื่อติดตั้งแอปพลิเคชันแล้วและการกำหนดค่าโดเมนที่เกี่ยวข้องถูกต้อง แต่ไม่ได้รับประกันการกู้คืนพารามิเตอร์แบบหน่วงเวลาหลังจากการติดตั้งใหม่ด้วยตัวเอง
  • SDK การระบุที่มา: ใช้เฉพาะกลไกการกู้คืนที่ระบุไว้สำหรับเวอร์ชัน SDK และแพลตฟอร์มที่กำลังใช้งานจริง ตรวจสอบการใช้งาน การรวบรวมข้อมูล และผลกระทบด้านความเป็นส่วนตัว แทนที่จะสมมติว่า SDK สามารถกู้คืนพารามิเตอร์คำเชิญทุกรายการในทุกเบราว์เซอร์และเส้นทางการติดตั้ง

หากไม่สามารถกู้คืนบริบทคำเชิญผ่านกลไกที่รองรับได้ ให้ผู้ใช้ดำเนินการเริ่มต้นใช้งานให้เสร็จสิ้นและบันทึกการติดตั้งว่าไม่มีการระบุที่มา ห้ามอนุมานหรือสร้างความสัมพันธ์การเชิญจากแมตช์ที่ไม่ได้รับการยืนยัน

เส้นทางการกู้คืนคำเชิญของ Android และ iOS บรรจบกันที่การตรวจสอบความถูกต้องของแบ็กเอนด์

ขอบเขตการระบุที่มาและความเป็นส่วนตัวของ iOS

การใช้งานคำเชิญบน iOS ต้องไม่พึ่งพาการทำลายนิ้วมืออุปกรณ์ (device fingerprinting) ที่ห้ามใช้ เพื่อระบุตัวตนของผู้ใช้หรืออุปกรณ์ในแอปพลิเคชันหรือเว็บไซต์ ข้อกำหนดด้านความเป็นส่วนตัวและการใช้ข้อมูลของ Apple ห้ามมิให้มีการสร้างตัวระบุที่ไม่ซ้ำกันจากสัญญาณอุปกรณ์ เบราว์เซอร์ การกำหนดค่า ตำแหน่ง หรือเครือข่ายเพื่อจุดประสงค์นี้ การขออนุญาต App Tracking Transparency ไม่ได้ทำให้การทำลายนิ้วมือที่ถูกห้ามกลายเป็นสิ่งที่อนุญาต

ใช้ Universal Links สำหรับการกำหนดเส้นทางแอปโดยตรงที่ได้รับการตรวจสอบเมื่อติดตั้งแอปพลิเคชันแล้ว สำหรับการกู้คืนคำเชิญหลังการติดตั้ง ให้ใช้กลไกที่มีเอกสารประกอบซึ่งพฤติกรรมได้รับการตรวจสอบสำหรับเวอร์ชัน SDK ที่ใช้งานและข้อกำหนดของ Apple ที่เกี่ยวข้อง ตรวจสอบการเปิดเผยข้อมูลส่วนบุคคลของ SDK และการประกาศ API ตามเหตุผลที่จำเป็นตามความเหมาะสม แต่ห้ามถือว่าการมีอยู่ของ Privacy Manifest เป็นหลักฐานว่าการใช้งานการระบุที่มาเป็นไปตามข้อกำหนด

หาก SDK ที่เลือกอาศัยวิธีการจับคู่ที่ไม่สามารถยืนยันได้ว่าได้รับอนุญาต อย่าแนะนำวิธีนั้นสำหรับการใช้งานบน iOS ขอคำชี้แจงจากผู้ให้บริการ SDK และเลือกทางเลือกที่เป็นไปตามข้อกำหนด ในกรณีที่ไม่มีกลไกที่ได้รับอนุญาตใดสามารถสร้างความสัมพันธ์การเชิญได้อย่างน่าเชื่อถือ ให้คงขั้นตอนการเริ่มต้นใช้งานตามปกติไว้และทำเครื่องหมายการติดตั้งว่าไม่มีการระบุที่มา

ข้อมูลอ้างอิง: ความเป็นส่วนตัวของผู้ใช้และการใช้ข้อมูลของ Apple

ดูเพิ่มเติม: การติดตามคำเชิญ ──> การติดตั้งพารามิเตอร์แอป

ขั้นตอนที่ 1: การติดตั้งเครื่องมือรายงานการแชร์ฝั่งไคลเอนต์ API

การเริ่มต้นใช้งาน SDK การระบุที่มาเนทีฟบนจุดเข้าใช้งาน Android และ iOS

ก่อนที่แอปพลิเคชันจะสามารถรายงานกิจกรรมการแชร์หรือเรียกคืนบริบทคำเชิญที่เข้ามาได้ SDK มือถือเนทีฟจะต้องเริ่มต้นทำงานในช่วงต้นของวงจรชีวิตแอปพลิเคชัน

บน Android การเริ่มต้นใช้งานจะอยู่ในเมธอด onCreate() ของคลาสย่อย Application แบบกำหนดเอง เพื่อให้มั่นใจว่ามีการสร้างตัวรับฟังการระบุที่มาและโครงสร้างหน่วยความจำหลักก่อนที่คอมโพเนนต์ UI ในเบื้องหน้าจะถูกเมาท์ บน iOS การเริ่มต้นใช้งานจะเกิดขึ้นภายในเดลิเกตการดำเนินการ application(_:didFinishLaunchingWithOptions:)

นักพัฒนาสามารถรับแพ็กเกจ SDK มือถืออย่างเป็นทางการได้โดยตรงจาก ศูนย์ดาวน์โหลด SDK ของ OpoInstall

การเรียกใช้เมธอดการรายงานการแชร์ก่อนทริกเกอร์ชีทการแชร์ของระบบปฏิบัติการเนทีฟ

แอปพลิเคชันควรบันทึกความตั้งใจในการแชร์ก่อนที่จะเปิดอินเทอร์เฟซการแชร์ของแพลตฟอร์ม ดำเนินการนี้ผ่านบริการเทเลเมทรีที่เป็นของแอปพลิเคชันหรือเมธอด SDK การระบุที่มาที่ได้รับการตรวจสอบเทียบกับเวอร์ชัน SDK ที่ใช้งานจริง ตัวอย่างด้านล่างใช้การละเว้น shareTelemetry.reportIntent(...) ที่เป็นของแอปพลิเคชัน ซึ่งไม่ใช่ API อย่างเป็นทางการของ OpoInstall อะแดปเตอร์ต้องใช้งานคำขอเครือข่ายจริง การจัดการข้อผิดพลาด และพฤติกรรมคอลแบ็กสำหรับแอปพลิเคชันที่ใช้งานจริง

เพื่อรักษาข้อมูลเทเลเมทรีให้สะอาด ทีมวิศวกรรมต้องแยกแยะระหว่างเหตุการณ์ที่แตกต่างกัน 5 เหตุการณ์ในวงจรชีวิตการแชร์:

  • เริ่มต้นความตั้งใจแชร์ (share_intent_initiated): UI ของแอปพลิเคชันไคลเอนต์บันทึกว่าผู้ใช้คลิกการกระทำเชิญ
  • ส่งการแชร์แล้ว (share_dispatched): แอปพลิเคชันไคลเอนต์ส่งเพย์โหลดไปยังแพลตฟอร์มปลายทางที่ระบุ (เช่น การเปิดความตั้งใจทางสังคมที่ชัดเจน) หรือเปิดตัวเลือกการแชร์ของระบบ
  • แชร์เสร็จสมบูรณ์ (share_completed): SDK โซเชียลของบุคคลที่สามส่งคืนคอลแบ็กการเสร็จสิ้นที่ได้รับการยืนยันว่าได้โพสต์หรือส่งข้อความแล้ว (ใช้ได้เฉพาะเมื่อใช้ SDK แพลตฟอร์มบุคคลที่สามเฉพาะที่มีการสนับสนุนคอลแบ็ก)
  • คลิกคำเชิญแล้ว (referral_click): ผู้รับเปิดหน้าแลนดิ้งเพจบนเว็บ ซึ่งทริกเกอร์การบันทึกการเข้าสู่ระบบฝั่งเว็บ
  • ผูกความสัมพันธ์คำเชิญแล้ว (referral_bound): ผู้รับทำการลงทะเบียนบัญชีเสร็จสิ้น และแบ็กเอนด์ตรวจสอบผู้สมัคร shareCode และเขียนความสัมพันธ์คำเชิญลงในฐานข้อมูล

การบันทึกเทเลเมทรีการแชร์จะจับความตั้งใจของผู้ใช้ในการแชร์ สิ่งนี้ช่วยให้ไปพป์ไลน์การวิเคราะห์สามารถจัดทำดัชนี shareCode ของผู้เชิญเทียบกับช่องทางแพลตฟอร์มที่กำหนด เพื่อเตรียมระบบให้พร้อมสำหรับการเชื่อมโยงกับการเข้าชมหน้าแลนดิ้งเพจบนเว็บในภายหลัง

การระบุตัวระบุแพลตฟอร์ม: การแมปช่องทางโซเชียล

ตัวระบุต่อไปนี้เป็นตัวอย่างประกอบเท่านั้น ใช้การแจงนับช่องทางที่แน่นอนซึ่งรองรับโดยเวอร์ชัน SDK ที่ปรับใช้ และตรวจสอบตัวระบุแต่ละรายการเทียบกับข้อมูลอ้างอิง API อย่างเป็นทางการ:

  • เครือข่ายข้อความปิด: WechatSession (แชทตรง WeChat), WechatTimeline (WeChat Moments), Line, WhatsApp, FacebookMessenger
  • ฟีดโซเชียลและไมโครบล็อกสาธารณะ: Twitter, Facebook, Instagram, Weibo, Douyin, TikTok
  • การขนส่งระบบปฏิบัติการโดยตรง: Sms, Email, Copy (คัดลอกลิงก์คลิปบอร์ด) และ Other (ชีทแชร์ของระบบ)

เมื่อแอปพลิเคชันเปิดชีทแชร์ของระบบทั่วไป ซึ่งไม่สามารถระบุการเลือกปลายทางสุดท้ายของผู้ใช้ล่วงหน้าได้ ควรจัดประเภทเหตุการณ์นั้นเป็น Other แทนที่จะฮาร์ดโค้ดเครือข่ายโซเชียลเฉพาะ

การจัดโครงสร้างโทเค็นตัวตนผู้เชิญ: แนวปฏิบัติที่ดีที่สุดสำหรับการจัดรูปแบบ shareCode

shareCode ทำหน้าที่เป็นโทเค็นทางธุรกิจระดับแอปพลิเคชันที่เชื่อมโยงการติดตั้งใหม่กลับไปยังบัญชีผู้เชิญ แม้ว่านักพัฒนาจะสามารถใช้สตริงตัวอักษรและตัวเลขตามอำเภอใจได้ แต่การปฏิบัติตามแนวปฏิบัติที่ดีที่สุดด้านโครงสร้างจะช่วยป้องกันความเสียหายของพารามิเตอร์:

  • โทเค็นแบบทึบแสงที่ออกโดยเซิร์ฟเวอร์: shareCode ควรเป็นโทเค็นคำเชิญแบบทึบแสงที่ออกโดยเซิร์ฟเวอร์ (เช่น โทเค็นที่ปลอดภัยสำหรับ URL หรือ UUID เช่น usr_9f82a1b4) แทนที่จะเป็น ID การเพิ่มอัตโนมัติของฐานข้อมูลดิบที่นับได้ (1024) แบ็กเอนด์ของแอปพลิเคชันจะแก้ไขโทเค็นนี้ไปยังบัญชีผู้เชิญ โดยบังคับใช้การหมดอายุและขีดจำกัดอัตรา
  • ข้อจำกัดความยาว: ทำให้โทเค็นมีขนาดกะทัดรัด (แนะนำให้อยู่ใน 64 ตัวอักษรอย่างเคร่งครัด \le 128 ตัวอักษร) เพื่อหลีกเลี่ยงการเพิ่มความยาวของ URL หรือทำให้เกิดการตัดทอนสตริงในแอปข้อความ
  • ชุดอักขระที่ปลอดภัย: จำกัดอักขระให้อยู่ในค่าตัวอักษรและตัวเลขที่ปลอดภัยสำหรับ URL ขีดกลาง และขีดล่าง ([A-Za-z0-9_-]) หลีกเลี่ยงตัวคั่นดิบ เช่น &, =, ? หรือช่องว่างที่เปลี่ยนการแยกวิเคราะห์ URI

บน Android 11 (ระดับ API 30) และใหม่กว่า การสอบถามว่ามีการติดตั้งแพ็กเกจโซเชียลของบุคคลที่สามหรือไม่ จำเป็นต้องประกาศองค์ประกอบ <queries> ที่สอดคล้องกันภายใน AndroidManifest.xml ตาม คู่มือสำหรับนักพัฒนา Android ของ Google เกี่ยวกับการมองเห็นแพ็กเกจใน Android 11 รูปแบบการใช้งานด้านล่างแสดงวิธีประกาศการมองเห็นแพ็กเกจ จัดการการหมดเวลาการรายงานการแชร์แบบไม่บล็อก จัดการการรีเซ็ตสถานะที่ปลอดภัยต่อวงจรชีวิต และส่งความตั้งใจทางโซเชียลที่ชัดเจนพร้อมระบบสำรองคลิปบอร์ด:

<!-- การกำหนดค่า Android และการใช้งานการแชร์ฝั่งไคลเอนต์ -->
<!-- ส่วนที่ 1: การมองเห็นแพ็กเกจ AndroidManifest.xml และการประกาศแอปพลิเคชัน -->
<!-- ไฟล์: app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">

    <!-- การประกาศการมองเห็นแพ็กเกจอย่างชัดเจนที่จำเป็นสำหรับ Android 11+ (API 30+) -->
    <queries>
        <package android:name="com.tencent.mm" />
        <package android:name="jp.naver.line.android" />
    </queries>

    <application
        android:name=".CustomApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
        
        <activity
            android:name=".InviteActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
// จุดเริ่มต้นวงจรชีวิตแอปพลิเคชัน Android
// ส่วนที่ 2: CustomApplication.kt (การเริ่มต้นวงจรชีวิต SDK)
// ไฟล์: app/src/main/java/com/example/app/CustomApplication.kt

package com.example.app

import android.app.Application
import com.opoinstall.api.OpoInstall

class CustomApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // เริ่มต้นไคลเอนต์หลักของ OpoInstall บนเทรดหลักก่อนเมาท์กิจกรรม
        OpoInstall.initialize(this)
    }
}
// การใช้งานกิจกรรมการแชร์บน Android
// ส่วนที่ 3: InviteActivity.kt (เทเลเมทรีแอปพลิเคชันและการส่งแบบเจาะจงพร้อมระบบสำรองคลิปบอร์ด)
// ไฟล์: app/src/main/java/com/example/app/InviteActivity.kt

package com.example.app

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.util.concurrent.atomic.AtomicBoolean

class InviteActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "ReferralShareTracking"
        // ตัวอย่างปลายทางเปลี่ยนเส้นทางที่เป็นของแอปพลิเคชัน แทนที่ด้วยเส้นทาง HTTPS ที่ใช้งานจริง
        private const val BASE_INVITE_HOST = "promo.example.com"
        private const val BASE_INVITE_PATH = "invite.html"
        
        // แพ็กเกจแพลตฟอร์มเป้าหมายสำหรับการกำหนดเส้นทางเจตนาที่ชัดเจน
        private const val PACKAGE_WECHAT = "com.tencent.mm"
        private const val PACKAGE_LINE = "jp.naver.line.android"
        
        // ตัวอย่างเวลาหมดเวลาที่กำหนดโดยแอปพลิเคชัน ปรับแต่งและทดสอบสำหรับบริการเทเลเมทรีจริง
        private const val SHARE_TIMEOUT_MS = 800L
    }

    // ตัวป้องกันการกดซ้ำเพื่อป้องกันการเรียกใช้เจตนาแชร์พร้อมกันอย่างรวดเร็ว
    private val isSharingInProgress = AtomicBoolean(false)

    // การละเว้นอะแดปเตอร์เทเลเมทรีที่เป็นของแอปพลิเคชัน
    private val shareTelemetry = ShareTelemetryService()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_invite)

        val btnShareWeChat = findViewById<Button>(R.id.btn_share_wechat)
        val btnShareLine = findViewById<Button>(R.id.btn_share_line)
        val btnShareGeneric = findViewById<Button>(R.id.btn_share_generic)

        // โทเค็นจำลองประกอบ: ในการผลิต ให้ดึงโทเค็นที่ออกโดยเซิร์ฟเวอร์จากเซสชันที่ตรวจสอบสิทธิ์แล้ว
        val currentInviterToken = "usr_9f82a1b4"

        btnShareWeChat.setOnClickListener {
            initiateShareFlow(currentInviterToken, "WechatSession", targetPackage = PACKAGE_WECHAT)
        }

        btnShareLine.setOnClickListener {
            initiateShareFlow(currentInviterToken, "Line", targetPackage = PACKAGE_LINE)
        }

        btnShareGeneric.setOnClickListener {
            // ชีทแชร์ของระบบที่ไม่ได้แบ่งส่วน แพลตฟอร์มจัดประเภทเป็น Other
            initiateShareFlow(currentInviterToken, "Other", targetPackage = null)
        }
    }

    /**
     * ตัวช่วยในการตรวจสอบว่ามีการติดตั้งแอปโซเชียลเป้าหมายหรือไม่ก่อนรายงานเทเลเมทรีช่องทางเฉพาะ
     * ต้องมีการประกาศ <queries> ใน AndroidManifest.xml บน Android 11+ (API 30+)
     */
    private fun isAppInstalled(packageName: String?): Boolean {
        if (packageName.isNullOrEmpty()) return false
        return try {
            packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
            true
        } catch (e: PackageManager.NameNotFoundException) {
            false
        } catch (e: Exception) {
            Log.w(TAG, "ข้อยกเว้นการตรวจสอบแพ็กเกจสำหรับ $packageName: ${e.message}")
            false
        }
    }

    /**
     * คัดลอก URL คำเชิญแบบเต็มไปยังคลิปบอร์ดของระบบเป็นทางเลือกสุดท้าย
     */
    private fun copyInviteLinkToClipboard(shareUri: String): Boolean {
        return try {
            val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
            clipboard.setPrimaryClip(ClipData.newPlainText("ลิงก์คำเชิญการแนะนำ", shareUri))
            true
        } catch (e: Exception) {
            Log.e(TAG, "ไม่สามารถคัดลอกลิงก์คำเชิญ: ${e.message}", e)
            false
        }
    }

    private fun showShareFailureFallback(shareUri: String) {
        val copied = copyInviteLinkToClipboard(shareUri)
        val message = if (copied) {
            "ไม่สามารถเปิดแอปแชร์ได้ คัดลอกลิงก์คำเชิญไปยังคลิปบอร์ดแล้ว"
        } else {
            "ไม่สามารถเปิดแอปแชร์หรือคัดลอกลิงก์คำเชิญได้"
        }
        Toast.makeText(this, message, Toast.LENGTH_LONG).show()
    }

    /**
     * บันทึกความตั้งใจในการแชร์ผ่านเทเลเมทรีที่เป็นของแอปพลิเคชันพร้อมระบบสำรองการหมดเวลาแบบไม่บล็อก
     *
     * @param shareCode โทเค็นผู้เชิญแบบทึบแสง (เข้มงวด <= 128 ตัวอักษร)
     * @param platformIdentifier ช่องทางโซเชียลเป้าหมายที่ตรงกับอนุกรมวิธานการรายงาน
     * @param targetPackage ชื่อแพ็กเกจแอปพลิเคชันเฉพาะ หรือ null สำหรับตัวเลือกของระบบทั่วไป
     */
    private fun initiateShareFlow(shareCode: String, platformIdentifier: String, targetPackage: String?) {
        // บังคับใช้การตรวจสอบการกดซ้ำ
        if (!isSharingInProgress.compareAndSet(false, true)) {
            Log.d(TAG, "มีการเรียกใช้การแชร์อยู่แล้ว กำลังเพิกเฉยต่อการคลิกซ้ำ")
            return
        }

        val isTargetInstalled = isAppInstalled(targetPackage)
        val effectivePlatform = if (isTargetInstalled) platformIdentifier else "Other"
        val effectivePackage = if (isTargetInstalled) targetPackage else null

        val isDispatched = AtomicBoolean(false)
        val mainHandler = Handler(Looper.getMainLooper())

        // รันนิ่งการหมดเวลาแบบไม่บล็อก: ช่วยให้มั่นใจว่าชีทแชร์ UI จะเปิดขึ้นแม้ว่าการจับมือของเครือข่ายจะล่าช้า
        val timeoutRunnable = Runnable {
            if (isDispatched.compareAndSet(false, true)) {
                Log.w(TAG, "การรายงานก่อนแชร์หมดเวลาหลังจาก ${SHARE_TIMEOUT_MS}มิลลิวินาที ดำเนินการจัดส่งต่อ")
                dispatchShareSafely(shareCode, effectivePlatform, effectivePackage)
            }
        }
        mainHandler.postDelayed(timeoutRunnable, SHARE_TIMEOUT_MS)

        // รายงานความตั้งใจในการแชร์แบบอะิงโครนัสผ่านอะแดปเตอร์เทเลเมทรีที่เป็นของแอปพลิเคชัน
        shareTelemetry.reportIntent(shareCode, effectivePlatform) { result ->
            mainHandler.removeCallbacks(timeoutRunnable)
            if (isDispatched.compareAndSet(false, true)) {
                if (result.isFailure) {
                    Log.w(TAG, "เทเลเมทรีการแชร์ล้มเหลว: ${result.exceptionOrNull()?.message}")
                } else {
                    Log.d(TAG, "บันทึกความตั้งใจในการแชร์สำหรับ $effectivePlatform แล้ว")
                }
                mainHandler.post {
                    dispatchShareSafely(shareCode, effectivePlatform, effectivePackage)
                }
            }
        }
    }

    /**
     * ดำเนินการส่งความตั้งใจพร้อมตรวจสอบวงจรชีวิตและรีเซ็ตสถานะที่รับประกันในบล็อก finally
     */
    private fun dispatchShareSafely(shareCode: String, platform: String, targetPackage: String?) {
        try {
            if (isFinishing || isDestroyed) {
                Log.d(TAG, "กิจกรรมเสร็จสิ้นหรือถูกทำลาย ข้ามการส่งการแชร์")
                return
            }
            dispatchShareAction(shareCode, platform, targetPackage)
        } catch (e: Exception) {
            Log.e(TAG, "การส่งการแชร์พบข้อยกเว้นที่ไม่ได้จัดการ: ${e.message}", e)
        } finally {
            isSharingInProgress.set(false)
        }
    }

    private fun dispatchShareAction(shareCode: String, platform: String, targetPackage: String?) {
        // สร้าง URL ปลายทางอย่างปลอดภัยโดยใช้ Uri.Builder เพื่อบังคับใช้การเข้ารหัส RFC 3986
        val shareUri = Uri.Builder()
            .scheme("https")
            .authority(BASE_INVITE_HOST)
            .appendEncodedPath(BASE_INVITE_PATH)
            .appendQueryParameter("shareCode", shareCode)
            .appendQueryParameter("platform", platform)
            .build()
            .toString()

        val shareMessage = "มาร่วมกับฉันบนแอปและรับบัตรกำนัลต้อนรับ! ดาวน์โหลดที่นี่: $shareUri"

        val sendIntent = Intent(Intent.ACTION_SEND).apply {
            putExtra(Intent.EXTRA_TEXT, shareMessage)
            type = "text/plain"
        }

        if (targetPackage != null) {
            sendIntent.setPackage(targetPackage)
            try {
                startActivity(sendIntent)
                return
            } catch (e: Exception) {
                Log.w(TAG, "การเปิดแอปเป้าหมายล้มเหลว: ${e.message}", e)
            }
        }

        // ใช้ตัวเลือกการแชร์ของระบบเมื่อแอปเป้าหมายไม่พร้อมใช้งานหรือการเปิดการแชร์แบบเจาะจงล้มเหลว
        try {
            sendIntent.setPackage(null)
            val fallbackChooser = Intent.createChooser(sendIntent, "เชิญเพื่อนผ่าน:")
            startActivity(fallbackChooser)
        } catch (e: Exception) {
            Log.e(TAG, "ตัวเลือกการแชร์ของระบบล้มเหลว: ${e.message}", e)
            showShareFailureFallback(shareUri)
        }
    }
}

// อินเทอร์เฟซบริการเทเลเมทรีที่เป็นของแอปพลิเคชันประกอบ
class ShareTelemetryService {
    fun reportIntent(shareCode: String, platform: String, callback: (Result<Unit>) -> Unit) {
        // การใช้งาน: ส่งคำขอเครือข่ายไปยังแบ็กเอนด์เทเลเมทรีของแอปพลิเคชัน
        callback(Result.success(Unit))
    }
}

หากต้องการลงทะเบียนโปรไฟล์แอปพลิเคชันและดึงคีย์การกำหนดค่าที่จำเป็นสำหรับการติดตามการแชร์ ทีมงานสามารถเข้าถึง คอนโซลนักพัฒนา OpoInstall ได้

ขั้นตอนที่ 2: การกำหนดค่า Web JS SDK เพื่อจับพารามิเตอร์แชร์แบบไดนามิก

การรวมเว็บ SDK ผ่านอะแดปเตอร์ที่ตรวจสอบเวอร์ชันแล้ว

การใช้งานหน้าแลนดิ้งเพจด้านล่างเป็นรูปแบบการรวมที่เป็นตัวอย่าง ไม่ใช่การรวม OpoInstall SDK แบบดรอปอินที่ได้รับการตรวจสอบแล้ว ก่อนปรับใช้งาน ให้ยืนยัน URL ชุดคำสั่ง JavaScript ที่แน่นอน ลายเซ็นการเริ่มต้นใช้งาน คอลแบ็กความพร้อม วิธีการปลุกหรือติดตั้ง และสเกมาพารามิเตอร์เทียบกับเอกสารอย่างเป็นทางการสำหรับเวอร์ชัน SDK ที่กำลังปรับใช้งาน

เก็บพฤติกรรมเฉพาะของ SDK ไว้เบื้องหลังอะแดปเตอร์แอปพลิเคชันขนาดเล็ก อะแดปเตอร์ควรเปิดเผยผลลัพธ์การเริ่มต้นใช้งานที่มีเอกสารประกอบและการดำเนินการปลุกหรือติดตั้งเดียวไปยังหน้าแลนดิ้งเพจ แมปการดำเนินการเหล่านี้กับ API ของผู้ขายจริงเฉพาะหลังจากตรวจสอบชื่อเมธอด พฤติกรรมคอลแบ็ก และข้อกำหนดการกำหนดค่าที่สอดคล้องกันแล้วเท่านั้น

หน้าแลนดิ้งเพจควรยังคงตรวจสอบความถูกต้องของ shareCode ที่เข้ามา รักษาพารามิเตอร์คำเชิญบนเส้นทางสำรองที่ได้รับอนุมัติ และป้องกันการนำทางซ้ำ หาก SDK ไม่สามารถเริ่มต้นใช้งานได้หรือจุดเข้าใช้งานที่มีเอกสารประกอบไม่สามารถใช้งานได้ ให้ใช้เส้นทางสำรองที่กำหนดค่าไว้ อย่าอ้างว่าระบบสำรองสามารถกู้คืนการระบุที่มาแบบหน่วงเวลาได้ เว้นแต่จะมีการทดสอบแบ็กเอนด์และการบูรณาการแพ็กเกจเพื่อรองรับ

Web JS SDK จัดเตรียมพารามิเตอร์คำเชิญก่อนที่ผู้ใช้จะเริ่มนำทางไปยังแอปหรือสโตร์

การดึงสตริงคิวรี shareCode อัตโนมัติผ่าน Browser URLSearchParams

เมื่อสร้าง URL การแชร์ แอปพลิเคชันจะผนวกโทเค็นของผู้เชิญเป็นพารามิเตอร์คิวรี (เช่น https://promo.example.com/invite.html?shareCode=usr_9f82a1b4&platform=WechatSession)

บนหน้าแลนดิ้งเพจ สคริปต์ฝั่งไคลเอนต์จะดึงค่านี้โดยใช้อินเทอร์เฟซเบราว์เซอร์มาตรฐาน เช่น URLSearchParams เพื่อป้องกันอินพุตที่เป็นอันตรายหรือการสคริปต์ข้ามเว็บไซต์ (XSS) สคริปต์ควรตรวจสอบความถูกต้องของโทเค็นเทียบกับรายการอนุญาตและรูปแบบรูปแบบก่อนเริ่มต้นใช้งาน SDK:

const query = new URLSearchParams(window.location.search);
const rawShareCode = query.get("shareCode");
const safePattern = /^[A-Za-z0-9_-]{1,128}$/;
const sanitizedShareCode = (rawShareCode && safePattern.test(rawShareCode)) ? rawShareCode : "";

การจัดเตรียมบริบทคำเชิญลงในหน่วยความจำชั่วคราวและบัฟเฟอร์ของระบบ

เมื่อ Web JS SDK เริ่มต้นทำงาน ระบบจะรับ shareCode ที่ผ่านการทำความสะอาดแล้วพร้อมกับตัวระบุแคมเปญเพิ่มเติม (channelCode) เมื่อผู้ใช้แตะปุ่มเรียกร้องการดำเนินการหลัก SDK จะประสานงานการจัดเตรียมบริบท:

  • การลงทะเบียนเซิร์ฟเวอร์การระบุที่มา: สคริปต์ส่งคำขอเครือข่ายแบบอะซิงโครนัสที่เชื่อมโยงเซสชันเว็บปัจจุบันและบริบทไคลเอนต์เข้ากับ shareCode
  • การจัดเตรียมกระดานชนวนของระบบ: การเข้าถึงคลิปบอร์ดอยู่ภายใต้การอนุญาตของเบราว์เซอร์ ข้อกำหนดการเปิดใช้งานของผู้ใช้ และนโยบายความปลอดภัยเฉพาะการใช้งาน การใช้งานบนเว็บควรปฏิบัติต่อการจัดเตรียมคลิปบอร์ดเป็นการปรับปรุงเสริม และมีระบบสำรองเมื่อการเข้าถึงถูกปฏิเสธหรือไม่สามารถใช้งานได้
  • การประสานงานสำรอง: หากการเข้าถึงกระดานชนวนถูกจำกัด ถูกปฏิเสธโดยพรอมต์ของผู้ใช้ หรือถูกล้างด้วยการกระทำของผู้ใช้ในภายหลัง สถาปัตยกรรมตัวระบุที่มาจะสำรองไปยังการจับคู่เซสชันฝั่งเซิร์ฟเวอร์หรือช่องทางผู้อ้างอิงการติดตั้งที่รองรับโดยแพลตฟอร์ม

การผูกปุ่ม CTA การดาวน์โหลดเพื่อดำเนินการลงทะเบียนพารามิเตอร์แบบหน่วงเวลา

หน้าแลนดิ้งเพจผูกปุ่มดาวน์โหลดหลักเข้ากับเมธอดการดำเนินการของ SDK โดยตรง (wakeupOrInstall())

Web JS SDK จัดการวงจรชีวิตการเปลี่ยนเส้นทางภายใน: SDK จะเริ่มต้นความพยายามปลุกแอปผ่านสกีมาที่กำหนดค่าไว้หรือ Universal Links และหากไม่ได้ติดตั้งแอป SDK จะดำเนินการเปลี่ยนเส้นทางแบบหน่วงไปยังสแอปพลิเคชันสโตร์ที่กำหนดค่าไว้ โค้ดฝั่งผู้ใช้ควรพึ่งพาการจัดการการนำทางภายในของ SDK ระหว่างการดำเนินการปกติ โดยสงวนการนำทางสำรองโดยตรงไว้สำหรับสถานการณ์ที่สคริปต์ SDK โหลดไม่สำเร็จ โยนข้อยกเว้นแบบซิงโครนัส หรือถูกบล็อกโดยการควบคุมความปลอดภัยของเบราว์เซอร์

รูปแบบการใช้งานด้านล่างแสดงวิธีผูก Web JS SDK เพื่อจับพารามิเตอร์คิวรี shareCode ที่เข้ามา ตรวจจับคอนเทนต์โซเชียลในแอป จัดการสถานะคิวการคลิก และรักษาพารามิเตอร์ข้ามเส้นทางสำรองโดยไม่มีคีย์ซ้ำหรือเศษ URI ที่จัดรูปแบบไม่ถูกต้อง:

<!-- การนำเข้าพารามิเตอร์แชร์แบบไดนามิกและเส้นทางของ Web JS SDK -->
<!-- ไฟล์: /var/www/html/invite.html -->
<!--
หมายเหตุการรวม:
ตัวอย่างนี้ต้องใช้อะแดปเตอร์ที่ได้รับการตรวจสอบเทียบกับเวอร์ชัน SDK ที่ใช้งานจริง
อย่าสันนิษฐานว่าตัวสร้าง เส้นทาง CDN คอลแบ็ก หรือชื่อเมธอดที่แสดง
ในตัวอย่างนี้ตรงกับ SDK ของผู้ขายปัจจุบัน
-->
<!DOCTYPE html>
<html lang="th">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ยอมรับคำเชิญคำเชิญของคุณ</title>
    <!-- รวม OpoInstall Web JS SDK ที่ได้รับการตรวจสอบแล้วจาก CDN อย่างเป็นทางการ -->
    <script src="https://web.cdn.opoinstallcloud.com/opoinstall.js"></script>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            margin: 0;
            padding: 20px;
            text-align: center;
            background-color: #f8fafc;
        }
        .card {
            background: #ffffff;
            border-radius: 12px;
            padding: 32px 24px;
            max-width: 420px;
            margin: 40px auto;
            box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
        }
        button {
            background-color: #2563eb;
            color: #ffffff;
            border: none;
            padding: 14px 28px;
            font-size: 16px;
            font-weight: 600;
            border-radius: 8px;
            cursor: pointer;
            width: 100%;
            margin-top: 20px;
        }
        button:disabled {
            background-color: #94a3b8;
            cursor: not-allowed;
        }
        /* โอเวอร์레이คำแนะนำทางสายตาสำหรับคอนเทนต์เว็บวิวในแอปที่ถูกจำกัด */
        .webview-guide-mask {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.85);
            color: #ffffff;
            z-index: 9999;
            padding-top: 40px;
            box-sizing: border-box;
        }
        .guide-arrow {
            font-size: 32px;
            text-align: right;
            padding-right: 30px;
        }
        .guide-text {
            font-size: 18px;
            line-height: 1.6;
            margin-top: 20px;
            padding: 0 30px;
        }
    </style>
</head>
<body>
    <main class="card">
        <h1>ยินดีต้อนรับสู่แอป</h1>
        <p id="invite-desc">เพื่อนของคุณได้เชิญคุณเข้าร่วม แตะด้านล่างเพื่อติดตั้งและรับเครดิตต้อนรับของคุณ</p>
        
        <!-- ปุ่มแปลงหลัก -->
        <button id="btn-install" type="button">ดาวน์โหลดและรับโบนัส</button>
        
        <!-- ตัวอย่างปลายทางเปลี่ยนเส้นทางที่เป็นของแอปพลิเคชัน แทนที่ด้วยเส้นทาง HTTPS ที่ใช้งานจริง -->
        <p style="margin-top: 16px; font-size: 14px;">
            <a id="fallback-store-link" href="https://promo.example.com/app-redirect">ลิงก์สโตร์โดยตรง</a>
        </p>
    </main>

    <!-- พรอมต์โอเวอร์เลย์ที่แสดงภายในเว็บวิวข้อความที่ถูกจำกัด (WeChat, Line) -->
    <div id="webview-guide" class="webview-guide-mask">
        <div class="guide-arrow">↗</div>
        <div class="guide-text">
            แตะเมนูที่มุมขวาบน (•••) และเลือก <strong>"เปิดในเบราว์เซอร์"</strong> (Safari / Chrome) เพื่อดาวน์โหลดแอป
        </div>
    </div>

    <script>
    /**
     * แยกและทำความสะอาดพารามิเตอร์คิวรี shareCode จาก window.location
     * บังคับใช้ขอบเขตตัวอักษรและตัวเลขที่ปลอดภัยสำหรับ URL สูงสุด 128 ตัวอักษรตามข้อกำหนด
     */
    function getSanitizedShareCode() {
        try {
            const query = new URLSearchParams(window.location.search);
            const rawCode = query.get("shareCode");
            const pattern = /^[A-Za-z0-9_-]{1,128}$/;
            return (rawCode && pattern.test(rawCode)) ? rawCode : "";
        } catch (e) {
            console.error("ไม่สามารถแยกวิเคราะห์พารามิเตอร์การค้นหา:", e);
            return "";
        }
    }

    /**
     * แยกและทำความสะอาดพารามิเตอร์คิวรี platform
     */
    function getSanitizedPlatform() {
        try {
            const query = new URLSearchParams(window.location.search);
            const rawPlatform = query.get("platform");
            const pattern = /^[A-Za-z0-9_]{1,32}$/;
            return (rawPlatform && pattern.test(rawPlatform)) ? rawPlatform : "";
        } catch (e) {
            return "";
        }
    }

    /**
     * ตรวจจับคอนเทนต์เบราว์เซอร์ในแอปที่ถูกจำกัด (WeChat, Line, WhatsApp, Facebook)
     */
    function detectInAppBrowser() {
        const ua = navigator.userAgent.toLowerCase();
        return {
            isWeChat: ua.indexOf("micromessenger") !== -1,
            isLine: ua.indexOf("line/") !== -1,
            isWhatsApp: ua.indexOf("whatsapp") !== -1,
            isFacebook: ua.indexOf("fbav") !== -1 || ua.indexOf("instagram") !== -1
        };
    }

    /**
     * ถอดรหัสองค์ประกอบ URI อย่างปลอดภัย โดยสำรองไปยังสตริงดิบหากการเข้ารหัสเปอร์เซ็นต์ผิดพลาด
     */
    function safeDecode(str) {
        if (!str) return "";
        try {
            return decodeURIComponent(str.replace(/\+/g, " "));
        } catch (e) {
            return str;
        }
    }

    /**
     * ดำเนินการนำทางสำรอง แทนที่หรืออัปเดตพารามิเตอร์ shareCode
     * ข้ามการแยกวิเคราะห์ URL และเส้นทางสำรองสตริงโดยไม่ทำให้เศษ URL เสียหาย
     */
    function executeFallbackNavigation(fallbackLink, shareCode, platform) {
        if (!fallbackLink || !fallbackLink.href) {
            console.error("ลิงก์ปลายทางสำรองไม่ได้ถูกกำหนด");
            return;
        }

        let destinationUrl = fallbackLink.href;
        try {
            const url = new URL(destinationUrl, window.location.origin);
            if (shareCode) {
                url.searchParams.set("shareCode", shareCode);
            }
            if (platform) {
                url.searchParams.set("platform", platform);
            }
            destinationUrl = url.toString();
        } catch (err) {
            console.warn("การแยกวิเคราะห์ URL API ล้มเหลว กำลังใช้การสำรองสตริงที่แข็งแกร่ง:", err);
            const hashIndex = destinationUrl.indexOf("#");
            const fragment = hashIndex !== -1 ? destinationUrl.substring(hashIndex) : "";
            const rawBase = hashIndex !== -1 ? destinationUrl.substring(0, hashIndex) : destinationUrl;

            const qIndex = rawBase.indexOf("?");
            const pathname = qIndex !== -1 ? rawBase.substring(0, qIndex) : rawBase;
            const queryString = qIndex !== -1 ? rawBase.substring(qIndex + 1) : "";

            const params = {};
            if (queryString) {
                const pairs = queryString.split("&");
                for (let i = 0; i < pairs.length; i++) {
                    if (!pairs[i]) continue;
                    const parts = pairs[i].split("=");
                    const key = safeDecode(parts[0]);
                    const value = parts.length > 1 ? safeDecode(parts.slice(1).join("=")) : "";
                    if (key) {
                        params[key] = value;
                    }
                }
            }

            if (shareCode) params["shareCode"] = shareCode;
            if (platform) params["platform"] = platform;

            const serializedPairs = [];
            for (const k in params) {
                if (Object.prototype.hasOwnProperty.call(params, k)) {
                    serializedPairs.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
                }
            }

            const newQuery = serializedPairs.length > 0 ? "?" + serializedPairs.join("&") : "";
            destinationUrl = pathname + newQuery + fragment;
        }

        window.location.href = destinationUrl;
    }

    document.addEventListener("DOMContentLoaded", function() {
        const shareCode = getSanitizedShareCode();
        const platform = getSanitizedPlatform();
        const inApp = detectInAppBrowser();
        const installBtn = document.getElementById("btn-install");
        const guideOverlay = document.getElementById("webview-guide");
        const fallbackLink = document.getElementById("fallback-store-link");

        // แสดงมาสก์คำแนะนำทางสายตาหากเปิดในคอนเทนต์ที่ถูกจำกัดซึ่งขัดขวางการดาวน์โหลด
        if (inApp.isWeChat || inApp.isLine) {
            if (guideOverlay) guideOverlay.style.display = "block";
        }

        // อัปเดต href ของลิงก์สำรองแบบคงที่ทันทีเมื่อโหลด DOM เพื่อให้การแตะโดยตรงรักษาพารามิเตอร์ไว้
        if (fallbackLink && fallbackLink.href) {
            try {
                const initialUrl = new URL(fallbackLink.href, window.location.origin);
                if (shareCode) initialUrl.searchParams.set("shareCode", shareCode);
                if (platform) initialUrl.searchParams.set("platform", platform);
                fallbackLink.href = initialUrl.toString();
            } catch (e) {
                // การแยกวิเคราะห์สำรองจัดการ ณ เวลาคลิก
            }
        }

        // บรรจุพารามิเตอร์แบบไดนามิกสำหรับการนำเข้า OpoInstall
        const dynamicData = {
            shareCode: shareCode,
            platform: platform
        };

        // สถานะความพร้อมของ SDK และการดำเนินการแบบครั้งเดียว
        let isSdkReady = false;
        let isSdkBlocked = false;
        let isClickQueued = false;
        let actionStarted = false;
        let installerInstance = null;
        let clickTimeoutId = null;

        // เกณฑ์การสำรองการเริ่มต้นใช้งานที่กำหนดโดยแอปพลิเคชัน ไม่ใช่ค่าเริ่มต้นของผู้ขายที่ตรวจสอบแล้ว
        const SDK_CLICK_TIMEOUT_MS = 1500;

        function clearClickTimeout() {
            if (clickTimeoutId !== null) {
                clearTimeout(clickTimeoutId);
                clickTimeoutId = null;
            }
        }

        function restoreInstallButton() {
            if (!installBtn) return;
            installBtn.disabled = false;
            installBtn.textContent = "ดาวน์โหลดและรับโบนัส";
        }

        function triggerSdkWakeup() {
            try {
                if (installerInstance && typeof installerInstance.wakeupOrInstall === "function") {
                    installerInstance.wakeupOrInstall({
                        data: dynamicData,
                        channelCode: "viral_social_share"
                    });
                } else {
                    executeFallbackNavigation(fallbackLink, shareCode, platform);
                }
            } catch (err) {
                console.error("การปลุก SDK ล้มเหลว:", err);
                executeFallbackNavigation(fallbackLink, shareCode, platform);
            }
        }

        // ทุกเส้นทางการนำทางต้องผ่านตัวป้องกันแบบครั้งเดียวนี้
        function runOnce(action) {
            if (actionStarted) return;
            actionStarted = true;
            isClickQueued = false;
            clearClickTimeout();

            if (installBtn) {
                installBtn.disabled = true;
                installBtn.textContent = "กำลังเปิด...";
            }

            action();
        }

        function handleInstallClick() {
            if (actionStarted) return;

            if (isSdkReady) {
                runOnce(triggerSdkWakeup);
                return;
            }

            if (isSdkBlocked) {
                runOnce(function() {
                    executeFallbackNavigation(fallbackLink, shareCode, platform);
                });
                return;
            }

            // คิวคลิกในขณะที่ SDK เริ่มต้นใช้งาน
            isClickQueued = true;
            if (installBtn) {
                installBtn.disabled = true;
                installBtn.textContent = "กำลังเชื่อมต่อกับ App Store...";
            }

            clearClickTimeout();

            clickTimeoutId = setTimeout(function() {
                if (actionStarted || isSdkReady || !isClickQueued) {
                    return;
                }
                isSdkBlocked = true;
                runOnce(function() {
                    executeFallbackNavigation(fallbackLink, shareCode, platform);
                });
            }, SDK_CLICK_TIMEOUT_MS);
        }

        if (installBtn) {
            installBtn.addEventListener("click", handleInstallClick);
        }

        // เริ่มต้นใช้งาน Web JS SDK
        if (typeof OpoInstall === "function") {
            try {
                // แทนที่ด้วยคีย์แอปพลิเคชันที่ได้รับจากคอนโซลผู้ขายที่กำหนดค่าไว้
                // อย่าคอมมิตความลับในการผลิตไปยังการควบคุมซอร์สสาธารณะ
                installerInstance = new OpoInstall({
                    appKey: "OPOINSTALL_APPKEY_PLACEHOLDER",
                    onready: function() {
                        if (this && typeof this.wakeupOrInstall === "function") {
                            installerInstance = this;
                        }

                        isSdkReady = true;
                        clearClickTimeout();

                        // คอลแบ็กที่ล่าช้าต้องไม่ทำซ้ำการดำเนินการที่เสร็จสมบูรณ์แล้ว
                        if (actionStarted) return;

                        if (isClickQueued) {
                            runOnce(triggerSdkWakeup);
                        } else {
                            restoreInstallButton();
                        }
                    }
                });
            } catch (err) {
                console.error("การเริ่มต้นใช้งาน OpoInstall ล้มเหลว:", err);
                isSdkBlocked = true;
                clearClickTimeout();

                if (isClickQueued) {
                    runOnce(function() {
                        executeFallbackNavigation(fallbackLink, shareCode, platform);
                    });
                } else {
                    restoreInstallButton();
                }
            }
        } else {
            console.warn("OpoInstall SDK ไม่พร้อมใช้งาน เปิดใช้งานการนำทางสำรองแล้ว");
            isSdkBlocked = true;
            restoreInstallButton();
        }
    });
    </script>
</body>
</html>

ขั้นตอนที่ 3: การกู้คืนพารามิเตอร์คำเชิญและการผูกบัญชี

การดึงบริบทคำเชิญระหว่างการเปิดแอปครั้งแรก

หลังจากการติดตั้ง แอปพลิเคชันควรพยายามดึงบริบทคำเชิญโดยใช้กลไกที่รองรับโดยแพลตฟอร์มและ SDK การระบุที่มาที่ติดตั้งไว้

อย่าสันนิษฐานว่าพารามิเตอร์ที่มีอยู่ใน URL ของหน้าแลนดิ้งเพจบนเว็บจะรอดพ้นจากการติดตั้งจากสแอปพลิเคชันสโตร์โดยอัตโนมัติ

บน Android แอปพลิเคชันอาจใช้ Google Play Install Referrer เมื่อเส้นทางการติดตั้งและเพย์โหลดรีเฟอร์เรอร์ที่กำหนดค่าไว้รองรับ หาก SDK การระบุที่มาของบุคคลที่สามระบุ API การดึงข้อมูลเพิ่มเติม ให้ตรวจสอบลายเซ็นเมธอด พฤติกรรมคอลแบ็ก และสเกมาเพย์โหลดเทียบกับเวอร์ชัน SDK ที่ใช้งานจริง

บน iOS การจัดการ Universal Link โดยตรงและการกู้คืนคำเชิญหลังการติดตั้งต้องได้รับการปฏิบัติเป็นโฟลว์แยกต่างหาก แอปพลิเคชันต้องใช้กลไกที่ได้รับอนุญาตจากแพลตฟอร์มและมีเอกสารประกอบสำหรับการกู้คืนแบบหน่วงเวลา หากไม่สามารถตรวจสอบกลไกได้หรือไม่สามารถกู้คืนบริบทคำเชิญได้ ให้ดำเนินการเริ่มต้นใช้งานต่อไปโดยไม่ระบุที่มาของการติดตั้งให้กับผู้เชิญ

การดึงข้อมูลแบบไม่บล็อกและพฤติกรรมการลองใหม่

การดึงคำเชิญต้องไม่บล็อกเทรด UI หลักหรือป้องกันไม่ให้ผู้ใช้ลงทะเบียนให้เสร็จสิ้น ปฏิบัติต่อการดำเนินการดึงข้อมูลแบบอะซิงโครนัส ใช้การหมดเวลาที่เหมาะสมกับบริการ SDK หรือแอปพลิเคชันจริง และบันทึกผลลัพธ์ที่แตกต่างกันสำหรับการกู้คืนสำเร็จ ผลลัพธ์ว่าง การหมดเวลา และความล้มเหลว

กำหนดค่าการหมดเวลาตามพฤติกรรม SDK ที่มีเอกสารประกอบและข้อกำหนดการเริ่มต้นใช้งานของแอปพลิเคชัน ค่าการหมดเวลาใดๆ ที่แสดงในตัวอย่างนี้เป็นการกำหนดค่าระดับแอปพลิเคชัน ไม่ใช่ค่าเริ่มต้นของผู้ขาย เว้นแต่เอกสาร SDK ที่เกี่ยวข้องจะยืนยันไว้อย่างชัดเจน

อาจมีการลองใหม่เมื่อ API ที่มีเอกสารประกอบรองรับและแอปพลิเคชันมีนโยบายการลองใหม่ที่กำหนดไว้ ป้องกันคำขอผูกบัญชีซ้ำและหลีกเลี่ยงการออกรางวัลซ้ำๆ เมื่อคอลแบ็กที่ล่าช้ามาถึงหลังจากลงทะเบียนเสร็จสิ้นแล้ว

การตรวจสอบความถูกต้องคำเชิญฝั่งเซิร์ฟเวอร์

ปฏิบัติต่อค่าคำเชิญที่รายงานโดยไคลเอนต์ทั้งหมดว่าเป็นอินพุตที่ไม่น่าเชื่อถือ แบ็กเอนด์ของแอปพลิเคชันต้องตรวจสอบโทเค็นผู้เชิญผู้สมัคร ตรวจสอบสิทธิ์ของผู้ถูกเชิญ และบังคับใช้กฎธุรกิจของโปรแกรมคำเชิญก่อนสร้างการผูกมัด

ใช้ข้อจำกัดความไม่ซ้ำกันระดับฐานข้อมูลหรือการควบคุมทรานแซกชันที่เทียบเท่าเพื่อป้องกันการผูกมัดผู้เชิญหลายรายสำหรับผู้ถูกเชิญคนเดียวกัน แยกการผูกมัดคำเชิญออกจากการออกรางวัล และปล่อยรางวัลเฉพาะหลังจากที่ผ่านการตรวจสอบเหตุการณ์สำคัญในการเปิดใช้งานที่จำเป็นบนเซิร์ฟเวอร์แล้วเท่านั้น

การตรวจสอบความถูกต้องของแบ็กเอนด์แยกโทเค็นคำเชิญที่ไม่น่าเชื่อถือออกจากผูกมัดและรางวัลที่ตรวจสอบแล้ว

การจัดการข้อจำกัดของเบราว์เซอร์ในแอปใน WeChat, Line และ WhatsApp

อุปสรรคแซนด์บ็อกซ์ของคอนเทนต์: แอปข้อความขัดขวางสกีมาที่กำหนดเองและการดาวน์โหลด APK โดยตรงอย่างไร

การแชร์คำเชิญแบบไวรัลส่วนใหญ่เกิดขึ้นภายในแอปพลิเคชันข้อความบนมือถือ อย่างไรก็ตาม แพลตฟอร์มเช่น WeChat, Line และ WhatsApp แสดงผลลิงก์เว็บภายนอกภายในเว็บวิวในแอปแบบแซนด์บ็อกซ์ ซึ่งมักจะขัดขวาง จำกัด หรือแก้ไขการดาวน์โหลดไฟล์ไบนารีโดยตรงและสกีมาลิงก์ลึก ขึ้นอยู่กับเวอร์ชันของระบบปฏิบัติการและนโยบายความปลอดภัยของคอนเทนต์

เมื่อผู้ใช้ที่ได้รับเชิญแตะลิงก์คำเชิญภายในเบราว์เซอร์ในแอป:

  • การดาวน์โหลด APK โดยตรงถูกขัดขวาง: บน Android ความพยายามในการดาวน์โหลดไฟล์ไบนารี .apk โดยตรงจากหน้าแลนดิ้งเพจบนเว็บมักจะถูกขัดขวางหรือบล็อกโดยคอนเทนต์เว็บวิว
  • สกีมา URL ที่กำหนดเองถูกบล็อก: การเรียกใช้สกีมา URL ส่วนตัว (myapp://invite?code=123) มักจะดำเนินการไม่สำเร็จ ทำให้ไม่สามารถเปิดแอปเนทีฟโดยตรงสำหรับผู้ใช้ที่มีแอปติดตั้งอยู่แล้ว
  • Universal Links อาจถูกระงับ: แอปพลิเคชันข้อความบางตัวขัดขวางการนำทางขาออก ทำให้ iOS Universal Links มาตรฐานไม่สามารถแก้ไขออกไปยังระบบปฏิบัติการได้

การตรวจจับส่วนหัว User-Agent ในแอป

เพื่อจัดการข้อจำกัดของคอนเทนต์เหล่านี้ได้อย่างราบรื่น หน้าแลนดิ้งเพจการแชร์ต้องตรวจสอบสตริง User-Agent ของเบราว์เซอร์ที่เข้ามาในระหว่างการโหลด DOM เริ่มต้น:

  • การตรวจจับ WeChat: ตรวจสอบ navigator.userAgent สำหรับ MicroMessenger
  • การตรวจจับ Line: ตรวจสอบ navigator.userAgent สำหรับ Line/
  • การตรวจจับ WhatsApp: ตรวจสอบ navigator.userAgent สำหรับ WhatsApp
  • Facebook / Messenger / Instagram: ตรวจสอบ navigator.userAgent สำหรับ FBAV หรือ Instagram

การประเมินกลยุทธ์การเปลี่ยนเส้นทาง: การส่งต่อแอปโดยตรงเทียบกับการเปลี่ยนผ่านเบราว์เซอร์ของระบบ

ระบบปฏิบัติการและแอปพลิเคชันโซเชียลแสดงพฤติกรรมการนำทางที่แตกต่างกัน:

  • สภาพแวดล้อม WeChat และ Line: คอนเทนต์เหล่านี้จำกัดการดาวน์โหลดไฟล์ไบนารีโดยตรงและการดำเนินการสกีมาภายนอก การพยายามเปลี่ยนเส้นทางอัตโนมัติมักจะทริกเกอร์หน้าจอเตือนของแพลตฟอร์ม หน้าแลนดิ้งเพจแสดงคำแนะนำทางสายตาที่ชัดเจนเพื่อกระตุ้นให้ผู้ใช้เปิดลิงก์ในเบราว์เซอร์ระบบเริ่มต้นของอุปกรณ์ (Safari บน iOS, Chrome บน Android)
  • สภาพแวดล้อม WhatsApp: บนการใช้งาน iOS และ Android สมัยใหม่ โดยทั่วไป WhatsApp จะเปิดลิงก์เว็บในมุมมองในแอปที่ระบบจัดเตรียมให้ (เช่น SFSafariViewController บน iOS หรือ Chrome Custom Tabs บน Android) สภาพแวดล้อมเหล่านี้อนุญาตการนำทางเว็บมาตรฐานและรองรับการเปลี่ยนผ่านไปยังแอปพลิเคชันสโตร์ของแพลตฟอร์ม แม้ว่าสกีมา URL ที่กำหนดเองอาจยังคงต้องมีการแตะโดยผู้ใช้ที่ชัดเจน

การใช้คำแนะนำผู้ใช้ที่ราบรื่น: การซ้อนทับพรอมต์เมนูเนทีฟเมื่อการเปลี่ยนเส้นทางโดยตรงล้มเหลว

เมื่อเว็บวิวในแอปป้องกันการส่งต่อภายนอกผ่านโปรแกรม หน้าแลนดิ้งเพจจะปรับใช้คำแนะนำผู้ใช้ทางสายตา:

  • โอเวอร์เลย์มาสก์แบบมีเงื่อนไข: เมื่อตรวจพบ MicroMessenger หรือ Line/ ให้แสดงโอเวอร์เลย์ภาพที่มีความคมชัดสูงชี้ไปที่มุมขวาบนของหน้าจอ
  • ข้อความการกระทำที่ชัดเจน: พรอมต์แนะนำผู้ใช้ว่า: “แตะเมนูที่มุมขวาบน (•••) และเลือก ‘เปิดในเบราว์เซอร์’ (Safari / Chrome) เพื่อดาวน์โหลดแอป”
  • การรักษาสถานะพารามิเตอร์: หากผู้ใช้เปลี่ยนผ่านไปยังเบราว์เซอร์ภายนอกโดยการโหลด URL เดิม พารามิเตอร์คิวรี (?shareCode=...) จะยังคงอยู่ครบถ้วนในแถบที่อยู่ หากการเปลี่ยนเส้นทางระดับกลางไม่ลบพารามิเตอร์เหล่านั้นออก เมื่อหน้าแลนดิ้งเพจโหลดใหม่ใน Safari หรือ Chrome Web JS SDK จะทำงานได้อย่างสะอาดและจัดเตรียมบริบทคำเชิญ

การแก้ไขปัญหาพารามิเตอร์คำเชิญหลุดข้ามฟวยล์โซเชียล

การวินิจฉัยการตัดพารามิเตอร์ข้ามพร็อกซีการห่อหุ้มลิงก์ในแอปเชิงรุก

แพลตฟอร์มโซเชียลบางแห่งกำหนดเส้นทางลิงก์ภายนอกที่แชร์ผ่านโดเมนห่อหุ้มลิงก์หรือการห่อหุ้มระดับกลาง (เช่น l.facebook.com ของ Facebook หรือ t.co ของ Twitter) ในบางกรณี ฮอปการเปลี่ยนเส้นทางเหล่านี้จะตัดพารามิเตอร์คิวรีที่ไม่ใช่มาตรฐานหรือผนวกตัวระบุการคลิกภายในที่ขัดแย้งกัน (fbclid)

เพื่อป้องกันการสูญหายของพารามิเตอร์ข้ามการเปลี่ยนเส้นทางโซเชียลหลายฮอป:

  • การตั้งชื่อพารามิเตอร์มาตรฐาน: ยึดติดกับคีย์คิวรีตัวอักษรและตัวเลขมาตรฐาน เช่น shareCode และ channelCode ตาม IETF RFC 3986
  • การตรวจสอบความถูกต้องของลิงก์ล่วงหน้า: ทดสอบการแก้ไขลิงก์ข้ามเซสชันแชทสดบนเครือข่ายข้อความเป้าหมาย ตรวจสอบ window.location.search บนหน้าแลนดิ้งเพจสุดท้ายเพื่อให้แน่ใจว่าพารามิเตอร์ shareCode รอดพ้นจากการเปลี่ยนเส้นทางระดับกลางทั้งหมด

การจัดการการแชร์เว็บเดสก์ท็อป: เมื่อผู้ใช้เปิดลิงก์คำเชิญมือถือบนเบราว์เซอร์พีซี

แม้ว่าแคมเปญคำเชิญจะออกแบบมาสำหรับอุปกรณ์มือถือ แต่ผู้คอมมอนบ่อยครั้งจะเปิดลิงก์แชทบนคอมพิวเตอร์เดสก์ท็อป (เช่น WhatsApp Web, WeChat สำหรับ Mac/Windows หรือ Slack) หากผู้ใช้เดสก์ท็อปเข้าถึงหน้าแลนดิ้งเพจดาวน์โหลดมือถือ การเปลี่ยนเส้นทางสโตร์มือถือจะล้มเหลว

หน้าแลนดิ้งเพจระดับองค์กรจัดการการเข้าชมเดสก์ท็อปผ่านการลดระดับแบบตอบสนอง:

  • การตรวจจับสภาพแวดล้อมเดสก์ท็อป: Web JS SDK ตรวจจับระบบปฏิบัติการเดสก์ท็อป (Windows, macOS, Linux)
  • การสร้างรหัส QR แบบไดนามิก: แทนที่จะแสดงปุ่มสโตร์มือถือที่ไม่สามารถคลิกได้ เทมเพลตที่ตอบสนองสามารถแสดงคอนเทนต์รหัส QR แบบมีเงื่อนไขบนวิวพอร์ตเดสก์ท็อป
  • การสแกนข้ามอุปกรณ์: ผู้ใช้สแกนรหัส QR บนหน้าจอเดสก์ท็อปด้วยอุปกรณ์มือถือของตน รหัส QR จะเข้ารหัส URL ปลายทางด้วย shareCode เดิม ซึ่งถ่ายโอนเซสชันคำเชิญจากเดสก์ท็อปไปยังมือถือ

การบรรเทาข้อจำกัดการเข้าถึงกระดานชนวนของระบบปฏิบัติการ

การควบคุมความเป็นส่วนตัวของระบบปฏิบัติการควบคุมการเข้าถึงคลิปบอร์ด:

  • กฎโฟกัสเบื้องหน้าของ Android: ภายใต้ Android 10 (ระดับ API 29) และใหม่กว่า แอปพลิเคชันในเบื้องหลังไม่สามารถอ่านข้อมูลคลิปบอร์ดผ่าน Android ClipboardManager ได้ SDK เนทีฟต้องเลื่อนการตรวจสอบคลิปบอร์ดจนกว่ากิจกรรมหลักของแอปพลิเคชันจะได้รับโฟกัสหน้าต่างที่ใช้งานอยู่
  • พรอมต์กระดานชนวน iOS: บน iOS 16 และใหม่กว่า การเข้าถึง Apple UIPasteboard ผ่านโปรแกรมอาจทริกเกอร์การแจ้งเตือนของระบบหรือพรอมต์ที่ขอให้ผู้ใช้ออนุญาตให้วางจากเบราว์เซอร์ต้นทาง
  • การประสานงานสำรองเครือข่าย: สถาปัตยกรรมตัวระบุที่มาคำนึงถึงข้อจำกัดเหล่านี้โดยปฏิบัติต่อบัฟเฟอร์คลิปบอร์ดภายในเป็นการปรับปรุงเสริม และสำรองไปยังการจับคู่เซสชันฝั่งเซิร์ฟเวอร์เมื่อการอ่านคลิปบอร์ดถูกปฏิเสธหรือไม่สามารถใช้งานได้

โปรโตคอลการตรวจสอบความถูกต้องก่อนการบินสำหรับฟวยล์คำเชิญโซเชียลข้ามอุปกรณ์จริง

ก่อนที่จะเปิดตัวแคมเปญคำเชิญลูกค้า ทีมวิศวกรรมการเติบโตควรปฏิบัติตามรายการตรวจสอบการตรวจสอบความถูกต้องที่เป็นระบบดังต่อไปนี้:

  • ระบบสำรองการแชร์เป้าหมาย Android: ยืนยันว่าเมื่อถอนการติดตั้ง WeChat หรือ Line แล้ว แอปจะสำรองไปยังตัวเลือกการแชร์ของระบบอย่างราบรื่น
  • ระบบสำรองการคัดลอกลิงก์คลิปบอร์ด Android: ยืนยันว่าหากตัวเลือกการแชร์ของระบบล้มเหลวหรือโยนข้อยกเว้น ลิงก์คำเชิญแบบเต็มจะถูกคัดลอกไปยังคลิปบอร์ดและมีการแสดงการแจ้งเตือนแบบโทสต์
  • ความล่าช้าของ H5 SDK และการนำทางแบบครั้งเดียว: จำลองความล่าช้าในการเริ่มต้นใช้งาน SDK เกิน 1500 มิลลิวินาที เพื่อยืนยันว่าการนำทางสำรองดำเนินการได้อย่างสะอาดและเหตุการณ์ onready ที่ล่าช้าจะไม่ทริกเกอร์การนำทางซ้ำ
  • การรักษาพารามิเตอร์และส่วนย่อย H5: ทดสอบการนำทางสำรองของหน้าแลนดิ้งเพจด้วย URL ที่มีพารามิเตอร์คิวรีที่มีอยู่ คีย์ซ้ำ และ #fragments เพื่อยืนยันการแทนที่พารามิเตอร์ที่สะอาด
  • iOS Universal Links และการส่งต่อสโตร์: ทดสอบการเปิดแอปโดยตรงสำหรับผู้ใช้ปัจจุบันและประเมินการกู้คืนพารามิเตอร์หลังการติดตั้งเทียบกับกฎป้องกันการทำลายนิ้วมือของ Apple
  • การส่งข้อความโซเชียลสด: ส่งลิงก์ไปยังเซสชันแชท WeChat และ Line ที่ใช้งานอยู่เพื่อยืนยันว่าโอเวอร์เลย์การตรวจจับเบราว์เซอร์ในแอปแนะนำผู้ไปยัง Safari หรือ Chrome โดยไม่ทำลาย shareCode
  • การผูกมัดแบ็กเอนด์และรางวัล Idempotency: ยืนยันว่าแบ็กเอนด์ของแอปพลิเคชันยอมรับโทเค็นผู้สมัคร ปฏิเสธการผูกมัดซ้ำ และเก็บรางวัลไว้ในสถานะรอดำเนินการจนกว่าจะทำภารกิจสำคัญที่ได้รับการตรวจสอบแล้วเสร็จ

การวิเคราะห์เชิงเปรียบเทียบของสถาปัตยกรรมติดตามคำเชิญ

การเปรียบเทียบสถาปัตยกรรมติดตามคำเชิญ

มิติสถาปัตยกรรม รหัสโปรโมชันแบบใช้มือกรอก การทำลายนิ้วมืออุปกรณ์ พารามิเตอร์คำเชิญที่ชัดเจนพร้อมการกู้คืนที่รองรับโดยแพลตฟอร์ม
อุปสรรคในการเริ่มต้นใช้งานของผู้ใช้ ต้องป้อนข้อมูลด้วยตนเอง อาจต่ำเมื่อการจับคู่สำเร็จ ต่ำเมื่อกู้คืนบริบทคำเชิญสำเร็จ
พื้นฐานการระบุที่มา รหัสที่ผู้ใช้ป้อนซึ่งตรวจสอบโดยแบ็กเอนด์ การจับคู่แบบน่าจะเป็นไปได้ตามสัญญาณเฉพาะการใช้งาน บริบทคำเชิญที่ชัดเจนรวมกับกลไกการกู้คืนที่รองรับ
ความน่าเชื่อถือในการระบุที่มา ขึ้นอยู่กับการป้อนและการตรวจสอบรหัสที่ถูกต้อง อาจได้รับผลกระทบจากการเปลี่ยนแปลงสัญญาณ ข้อจำกัดของแพลตฟอร์ม และข้อกำหนดด้านความเป็นส่วนตัว ขึ้นอยู่กับความต่อเนื่องของพารามิเตอร์ ความสามารถของแพลตฟอร์ม และกลไกการกู้คืนที่เลือก
ความเป็นส่วนตัวและการปฏิบัติตามข้อกำหนด ต้องมีการจัดการโทเค็นที่ปลอดภัยและการควบคุมการป้องกันการละเมิด ต้องได้รับการประเมินเทียบกับนโยบายแพลตฟอร์มที่เกี่ยวข้อง ห้ามใช้การทำลายนิ้วมือที่ถูกห้าม ต้องมีการตรวจสอบการใช้งาน SDK และกฎแพลตฟอร์มที่เกี่ยวข้อง
ความซับซ้อนในการดำเนินงาน การใช้งานเริ่มต้นค่อนข้างง่าย การแลกรับด้วยตนเองสร้างอุปสรรคเพิ่มเติม ต้องมีตรรกะการจับคู่ การตรวจสอบ และการทบทวนนโยบาย ต้องมีการกำหนดเส้นทางลิงก์ การบูรณาการเฉพาะแพลตฟอร์ม การตรวจสอบแบ็กเอนด์ และการจัดการสำรอง

การแลกเปลี่ยนในความซับซ้อนในการใช้งาน อุปสรรคในการแปลง และความแม่นยำในการระบุที่มา

รหัสโปรโมชันแบบใช้มือกรอกค่อนข้างตรงไปตรงมาในการใช้งาน แต่กำหนดให้ผู้ใช้ต้องป้อนและแลกรับรหัส สิ่งนี้สามารถสร้างอุปสรรคและเปิดโอกาสให้มีการแชร์รหัสนอกเหนือจากความสัมพันธ์คำเชิญที่ตั้งใจไว้

การทำลายนิ้วมืออุปกรณ์อาจดูเหมือนจะลดปฏิสัมพันธ์ของผู้ใช้ แต่ความน่าเชื่อถือขึ้นอยู่กับการใช้งานการจับคู่และสัญญาณที่มีอยู่ ห้ามใช้ในที่ที่ขัดแย้งกับข้อกำหนดความเป็นส่วนตัวที่เกี่ยวข้อง รวมถึงข้อจำกัดของ Apple เกี่ยวกับการทำลายนิ้วมือที่ถูกห้าม

พารามิเตอร์คำเชิญที่ชัดเจนให้การแสดงระดับธุรกิจที่ชัดเจนของผู้เชิญที่ตั้งใจไว้ อย่างไรก็ตาม การถือโทเค็นใน URL ของเว็บไม่เท่ากับการกู้คืนโทเค็นนั้นหลังจากการติดตั้งจากแอปสโตร์ โซลูชันโดยรวมต้องรวมความต่อเนื่องของพารามิเตอร์ กลไกการกู้คืนที่รองรับโดยแพลตฟอร์ม และการตรวจสอบความถูกต้องฝั่งเซิร์ฟเวอร์ เมื่อการกู้คืนล้มเหลว การติดตั้งควรยังคงไม่มีการระบุที่มาแทนที่จะถูกกำหนดให้กับผู้เชิญที่ไม่ได้รับการยืนยัน

คำถามที่พบบ่อย (FAQ)

การติดตามคำเชิญแบบอัตโนมัติส่งพารามิเตอร์ผ่าน App Store โดยไม่ต้องใช้รหัสโปรโมชันได้อย่างไร?
การติดตามคำเชิญแบบอัตโนมัติสามารถกำจัดการป้อนรหัสโปรโมชันด้วยตนเองได้ แต่การกู้คืนพารามิเตอร์จะขึ้นอยู่กับแพลตฟอร์มและการใช้งาน บน Android API Install Referrer ของ Google Play สามารถให้ข้อมูลเมตาการเชิญที่รองรับหลังจากการติดตั้ง บน iOS App Store ไม่ได้ส่งพารามิเตอร์คิวรีของหน้าแลนดิ้งเพจตามอำเภอใจไปยังแอปที่เพิ่งติดตั้งใหม่โดยอัตโนมัติ ดังนั้นจึงต้องประเมินกลไกการกู้คืนแบบหน่วงเวลาแยกต่างหากสำหรับการใช้งานจริง การสนับสนุนแพลตฟอร์ม และการปฏิบัติตามความเป็นส่วนตัว ในทั้งสองกรณี แอปควรปฏิบัติต่อข้อมูลคำเชิญที่กู้คืนมาว่าเป็นผู้สมัครที่ไม่น่าเชื่อถือ และตรวจสอบความถูกต้องของผู้เชิญ สิทธิ์ของผู้ถูกเชิญ และเงื่อนไขรางวัลบนแบ็กเอนด์ของแอปพลิเคชัน
เหตุใดพารามิเตอร์คำเชิญจึงหลุดเมื่อแชร์ลิงก์ภายใน WeChat หรือ Line?
แอปข้อความอาจแก้ไขการนำทางลิงก์ จำกัดพฤติกรรมการเปิดแอปบางอย่าง หรือแสดงผลหน้าเว็บภายในเบราว์เซอร์ที่ฝังอยู่ พฤติกรรมเหล่านี้แตกต่างกันไปตามแพลตฟอร์ม เวอร์ชันของระบบปฏิบัติการ และเวอร์ชันของแอปพลิเคชัน หากพารามิเตอร์คำเชิญถูกลบออกหรือกลไกการกู้คืนที่เลือกไม่สามารถจับคู่กับการติดตั้งได้ ความสัมพันธ์คำเชิญเดิมอาจยังคงไม่มีการระบุที่มา ทดสอบห่วงโซ่การเปลี่ยนเส้นทางที่สมบูรณ์ในแอปข้อความเป้าหมาย และตรวจสอบ URL ของหน้าแลนดิ้งเพจสุดท้าย พฤติกรรมการเปิดแอปโดยตรง และการกู้คืนหลังการติดตั้งแยกต่างหาก อย่าสันนิษฐานว่าเบราว์เซอร์ในแอปทุกตัวทำงานเหมือนกันทุกประการ หรือว่าการเปิดลิงก์ในเบราว์เซอร์ของระบบจะรับประกันการระบุที่มาแบบหน่วงเวลาที่สำเร็จ
แบ็กเอนด์ของแอปพลิเคชันป้องกันวงจรคำเชิญฉ้อโกงได้อย่างไร?
แบ็กเอนด์ของแอปพลิเคชันปกป้องโปรแกรมคำเชิญโดยปฏิบัติต่อโทเค็นคำเชิญที่รายงานโดยไคลเอนต์ว่าเป็นอินพุตผู้สมัครที่ไม่ได้รับการยืนยัน เซิร์ฟเวอร์ต้องตรวจสอบว่าบัญชีเชื่อมโยงกับหมายเลขโทรศัพท์หรืออีเมลที่ได้รับการยืนยันแล้ว บังคับใช้ขีดจำกัดอัตราสำหรับบัญชีผู้เชิญ กำหนดให้ต้องทำภารกิจสำคัญในการเปิดใช้งานหลังการติดตั้ง (เช่น การลงทะเบียนบัญชีให้เสร็จสิ้นหรือการทำธุรกรรมครั้งแรก) และบังคับใช้ความไม่ซ้ำกันของฐานข้อมูลก่อนที่จะปล่อยแรงจูงใจทางการเงิน

สรุปและกรอบการตัดสินใจ

การติดตามคำเชิญใช้งานมือถือแบบอัตโนมัติสามารถลดการป้อนรหัสคำเชิญด้วยตนเองด้วยการพกพาโทเค็นผู้เชิญที่ชัดเจนผ่านขั้นตอนการแชร์ผ่านเว็บ และการตรวจสอบความถูกต้องของความสัมพันธ์คำเชิญที่ได้บนแบ็กเอนด์ของแอปพลิเคชัน

การใช้งานที่เชื่อถือได้ต้องทำมากกว่าการเพิ่มพารามิเตอร์คิวรี shareCode หน้าแลนดิ้งเพจต้องรักษาโทเค็นไว้ผ่านการเปลี่ยนเส้นทางที่รองรับ แอปพลิเคชันต้องใช้กลไกการกู้คืนที่ได้รับการตรวจสอบสำหรับแพลตฟอร์มที่เกี่ยวข้อง และแบ็กเอนด์ต้องตรวจสอบความถูกต้องของสิทธิ์บัญชีและบังคับใช้การผูกมัดและการออกรางวัลแบบไอเด็มโพเทนต์

ควรประเมิน Android และ iOS แยกต่างหาก Google Play Install Referrer สามารถให้ข้อมูลเมตาการติดตั้งที่รองรับบน Android บน iOS Universal Links รองรับการกำหนดเส้นทางโดยตรงเมื่อติดตั้งแอปพลิเคชันแล้ว แต่ไม่ได้รับประกันการกู้คืนพารามิเตอร์แบบหน่วงเวลาหลังจากการติดตั้งจาก App Store โดยอัตโนมัติ กลไกการกู้คืนเพิ่มเติมใดๆ ต้องได้รับการจัดทำเอกสาร ทดสอบ และตรวจสอบเทียบกับข้อกำหนดความเป็นส่วนตัวของแพลตฟอร์มที่เกี่ยวข้อง

สำหรับการปรับใช้งานจริง ให้ยืนยันชื่อเมธอด SDK ค่าการกำหนดค่า ตัวระบุช่องทาง และสเกมาคอลแบ็กทั้งหมดเทียบกับเวอร์ชันที่แน่นอนที่กำลังรวมเข้าด้วยกัน ทดสอบเส้นทางที่สมบูรณ์บนอุปกรณ์ที่สะอาดและในแอปพลิเคชันข้อความเป้าหมาย เมื่อไม่สามารถกู้คืนคำเชิญผ่านกลไกที่รองรับได้ ให้ดำเนินการเริ่มต้นใช้งานต่อไปและบันทึกผลลัพธ์ว่าไม่มีการระบุที่มาแทนที่จะสร้างความสัมพันธ์คำเชิญที่ไม่ได้รับการยืนยัน

SDK และเอกสารประกอบสำหรับนักพัฒนาของ OpoInstall อาจมีคอมโพเนนต์สำหรับการใช้งานสถาปัตยกรรมนี้ ยืนยันความสามารถเฉพาะและพฤติกรรมของแพลตฟอร์มที่รองรับโดยรีลีสที่ใช้งานจริงก่อนที่จะพึ่งพิงในแคมเปญคำเชิญการผลิต

สำหรับรายละเอียดการรวม โปรดปรึกษา เอกสารประกอบสำหรับนักพัฒนาของ OpoInstall, ศูนย์ดาวน์โหลด SDK ของ OpoInstall และ คอนโซลนักพัฒนาของ OpoInstall ใช้เอกสารประกอบแพลตฟอร์ม Android และ Apple อย่างเป็นทางการที่เกี่ยวข้องเพื่อตรวจสอบการติดตั้งและพฤติกรรมความเป็นส่วนตัวที่เกี่ยวข้องกับแอปพลิเคชัน

วัสดุที่เกี่ยวข้อง

Share this article

Keep Discovering

Apple อัปเดต iOS 27.2 เพิ่มการจำกัดข้อมูลการเคลื่อนไหวในจีน: สิ่งที่โฆษณาแบบเขย่าเพื่อเปลี่ยนเส้นทางต้องปรับตัว

Apple อัปเดต iOS 27.2 เพิ่มการจำกัดข้อมูลการเคลื่อนไหวในจีน: สิ่งที่โฆษณาแบบเขย่าเพื่อเปลี่ยนเส้นทางต้องปรับตัว

Apple เพิ่มการตั้งค่าจำกัดข้อมูลการเคลื่อนไหวใน iOS 27.2 สำหรับประเทศจีน เรียนรู้วิธีที่นักพัฒนาต้องปรับตัวต่อโฆษณาแบบเขย่าเพื่อเปิดลิงก์ Universal Links และการระบุแหล่งที่มาบนมือถือ

Apple ขยายภาษา Siri AI? สิ่งที่ทีมแอปประจำภูมิภาคต้องรู้

Apple ขยายภาษา Siri AI? สิ่งที่ทีมแอปประจำภูมิภาคต้องรู้

Apple ขยายเวลาทดสอบ Siri AI ไปยัง 5 ภาษาใหม่ใน iOS 27.2 เรียนรู้วิธีที่ทีมแอปประจำภูมิภาคควรตั้งค่า App Intents, การแปลภาษา และ Universal Links

วิธีที่ QR Code แบบไดนามิกป้องกันความล้มเหลวในการตรวจสอบสิทธิ์เมื่อเปลี่ยนเส้นทางเว็บ

วิธีที่ QR Code แบบไดนามิกป้องกันความล้มเหลวในการตรวจสอบสิทธิ์เมื่อเปลี่ยนเส้นทางเว็บ

เรียนรู้ว่าเหตุใด QR Code แบบไดนามิกจึงมีประสิทธิภาพเหนือกว่าบาร์โค้ดแบบคงที่ โดยใช้กระบวนการทางอ้อมของ URI ฝั่งเซิร์ฟเวอร์เพื่อป้องกันลิงก์เสียและการเปลี่ยนเส้นทางล้มเหลวบนสื่อสิ่งพิมพ์จริง