ขั้นตอนที่ 2: นำเข้าเว็บแอปที่มีอยู่

ในขั้นตอนนี้ คุณจะได้เรียนรู้สิ่งต่อไปนี้

  • วิธีปรับเว็บแอปพลิเคชันที่มีอยู่ให้เหมาะกับแพลตฟอร์มแอป Chrome
  • วิธีทำให้สคริปต์แอปเป็นไปตามนโยบายรักษาความปลอดภัยเนื้อหา (CSP)
  • วิธีนำพื้นที่เก็บข้อมูลในเครื่องโดยใช้ chrome.storage.local

เวลาที่ใช้โดยประมาณในการดําเนินการขั้นตอนนี้: 20 นาที
หากต้องการดูตัวอย่างสิ่งที่คุณจะทําในขั้นตอนนี้ ให้เลื่อนลงไปที่ด้านล่างของหน้านี้ ↓

นำเข้าแอป Todo ที่มีอยู่

เริ่มต้นด้วยการนำTodoMVC ซึ่งเป็นแอปการเปรียบเทียบทั่วไปในเวอร์ชัน JavaScript พื้นฐานมาไว้ในโปรเจ็กต์

เราได้รวมแอป TodoMVC เวอร์ชันหนึ่งไว้ในไฟล์ ZIP ของโค้ดอ้างอิงในโฟลเดอร์ todomvc คัดลอกไฟล์ทั้งหมด (รวมถึงโฟลเดอร์) จาก todomvc ไปยังโฟลเดอร์โปรเจ็กต์

คัดลอกโฟลเดอร์ todomvc ไปยังโฟลเดอร์ codelab

ระบบจะขอให้คุณแทนที่ index.html โปรดยอมรับ

แทนที่index.html

ตอนนี้คุณควรมีโครงสร้างไฟล์ต่อไปนี้ในโฟลเดอร์แอปพลิเคชันของคุณ

โฟลเดอร์โปรเจ็กต์ใหม่

ไฟล์ที่ไฮไลต์ด้วยสีน้ำเงินมาจากโฟลเดอร์ todomvc

โหลดแอปซ้ำเลย (คลิกขวา > โหลดแอปซ้ำ) คุณควรเห็น UI พื้นฐาน แต่เพิ่มสิ่งที่ต้องทำไม่ได้

ทําให้สคริปต์เป็นไปตามนโยบายรักษาความปลอดภัยเนื้อหา (CSP)

เปิดคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ (คลิกขวา > ตรวจสอบองค์ประกอบ แล้วเลือกแท็บคอนโซล) คุณจะเห็นข้อผิดพลาดเกี่ยวกับการปฏิเสธที่จะเรียกใช้สคริปต์ในบรรทัดดังนี้

แอป Todo มีข้อผิดพลาดบันทึกคอนโซล CSP

ลองแก้ไขข้อผิดพลาดนี้โดยทำให้แอปเป็นไปตามนโยบายรักษาความปลอดภัยเนื้อหา สาเหตุที่พบบ่อยที่สุดอย่างหนึ่งของการไม่ปฏิบัติตามข้อกำหนด CSP คือ JavaScript ในบรรทัด ตัวอย่าง JavaScript ในบรรทัด ได้แก่ ตัวแฮนเดิลเหตุการณ์เป็นแอตทริบิวต์ DOM (เช่น <button onclick=''>) และแท็ก <script> ที่มีเนื้อหาภายใน HTML

โดยมีวิธีแก้ไขง่ายๆ คือย้ายเนื้อหาในบรรทัดไปยังไฟล์ใหม่

1. ใกล้กับด้านล่างของ index.html ให้นํา JavaScript แบบแทรกในบรรทัดออก แล้วใส่ js/bootstrap.js แทน

<script src="bower_components/director/build/director.js"></script>
<script>
  // Bootstrap app data
  window.app = {};
</script>
<script src="js/bootstrap.js"></script>
<script src="js/helpers.js"></script>
<script src="js/store.js"></script>

2. สร้างไฟล์ในโฟลเดอร์ js ชื่อ bootstrap.js ย้ายโค้ดแทรกในบรรทัดก่อนหน้ามาอยู่ในไฟล์นี้

// Bootstrap app data
window.app = {};

หากคุณโหลดแอปซ้ำตอนนี้แต่คุณกลับใช้งานใกล้ขึ้นแล้ว คุณจะยังมีแอป Todo ที่ใช้งานไม่ได้

แปลง localStorage เป็น chrome.storage.local

หากคุณเปิดคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บในตอนนี้ ข้อผิดพลาดก่อนหน้าจะหายไป อย่างไรก็ตาม เกิดข้อผิดพลาดใหม่เกี่ยวกับ window.localStorage ที่ไม่พร้อมใช้งาน

แอป Todo ที่มีข้อผิดพลาดในบันทึกคอนโซล localStorage

แอป Chrome ไม่รองรับ localStorage เนื่องจาก localStorage เป็นการดำเนินการแบบซิงค์ การเข้าถึงแบบซิงค์กับทรัพยากรที่บล็อก (I/O) ในรันไทม์แบบเธรดเดียวอาจทําให้แอปไม่ตอบสนอง

แอป Chrome มี API ที่เทียบเท่าซึ่งจัดเก็บออบเจ็กต์แบบไม่พร้อมกันได้ เพื่อช่วยหลีกเลี่ยงบางครั้งที่มีต้นทุนสูงออบเจ็กต์ ->string-> กระบวนการเรียงลำดับออบเจ็กต์

หากต้องการแก้ไขข้อความแสดงข้อผิดพลาดในแอป คุณต้องแปลง localStorage เป็น chrome.storage.local

อัปเดตสิทธิ์ของแอป

หากต้องการใช้ chrome.storage.local คุณต้องขอสิทธิ์ storage ใน manifest.json ให้เพิ่ม "storage" ลงในอาร์เรย์ permissions ดังนี้

"permissions": ["storage"],

ดูข้อมูลเกี่ยวกับ local.storage.set() และ local.storage.get()

หากต้องการบันทึกและเรียกข้อมูลรายการสิ่งที่ต้องทํา คุณต้องทราบเกี่ยวกับเมธอด set() และ get() ของ chrome.storage API

เมธอด set() จะยอมรับออบเจ็กต์ของคู่คีย์-ค่าเป็นพารามิเตอร์แรก ฟังก์ชัน Callback ที่ไม่บังคับคือพารามิเตอร์ที่สอง เช่น

chrome.storage.local.set({secretMessage:'Psst!',timeSet:Date.now()}, function() {
  console.log("Secret message saved");
});

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

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

chrome.storage.local.get(['secretMessage','timeSet'], function(data) {
  console.log("The secret message:", data.secretMessage, "saved at:", data.timeSet);
});

หากต้องการget()ทุกอย่างที่อยู่ใน chrome.storage.local ในปัจจุบัน ให้ละเว้นพารามิเตอร์แรก

chrome.storage.local.get(function(data) {
  console.log(data);
});

คุณจะตรวจสอบรายการที่จัดเก็บไว้ในเครื่องโดยใช้แผงทรัพยากรของเครื่องมือสำหรับนักพัฒนาเว็บไม่ได้ ซึ่งต่างจาก localStorage อย่างไรก็ตาม คุณสามารถโต้ตอบกับ chrome.storage จากคอนโซล JavaScript ได้ดังนี้

ใช้คอนโซลเพื่อแก้ไขข้อบกพร่องของ chrome.storage

ดูตัวอย่างการเปลี่ยนแปลง API ที่จำเป็น

ขั้นตอนส่วนใหญ่ที่เหลือในการแปลงแอป Todo เป็นการเปลี่ยนแปลงการเรียก API เพียงเล็กน้อย คุณต้องเปลี่ยนแปลงทุกตำแหน่งที่ใช้ localStorage อยู่ แม้ว่าจะใช้เวลานานและมีโอกาสเกิดข้อผิดพลาดก็ตาม

ความแตกต่างสำคัญระหว่าง localStorage กับ chrome.storage มาจากลักษณะที่ไม่พร้อมกันของ chrome.storage ดังนี้

  • แทนที่จะเขียนลงใน localStorage โดยใช้การกําหนดค่าแบบง่าย คุณต้องใช้ chrome.storage.local.set() กับคอลแบ็กที่ไม่บังคับ

    var data = { todos: [] };
    localStorage[dbName] = JSON.stringify(data);
    

    ปะทะกับ

    var storage = {};
    storage[dbName] = { todos: [] };
    chrome.storage.local.set( storage, function() {
      // optional callback
    });
    
  • คุณต้องใช้ chrome.storage.local.get(myStorageName,function(storage){...}) แล้วแยกวิเคราะห์ออบเจ็กต์ storage ที่แสดงผลในฟังก์ชัน Callback แทนการเข้าถึง localStorage[myStorageName] โดยตรง

    var todos = JSON.parse(localStorage[dbName]).todos;
    

    ปะทะกับ

    chrome.storage.local.get(dbName, function(storage) {
      var todos = storage[dbName].todos;
    });
    
  • ฟังก์ชัน .bind(this) จะใช้ใน Callback ทั้งหมดเพื่อให้ this อ้างถึง this ของต้นแบบ Store (ดูข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชันที่เชื่อมโยงได้ในเอกสาร MDN: Function.prototype.bind())

    function Store() {
      this.scope = 'inside Store';
      chrome.storage.local.set( {}, function() {
        console.log(this.scope); // outputs: 'undefined'
      });
    }
    new Store();
    

    ปะทะกับ

    function Store() {
      this.scope = 'inside Store';
      chrome.storage.local.set( {}, function() {
        console.log(this.scope); // outputs: 'inside Store'
      }.bind(this));
    }
    new Store();
    

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

เรียกข้อมูลรายการสิ่งที่ต้องทำ

มาอัปเดตแอป Todo เพื่อเรียกรายการสิ่งที่ต้องทำกัน:

1. Store เมธอดคอนสตรัคเตอร์จะดูแลการเริ่มต้นแอป Todo ด้วยรายการ Todo ที่มีอยู่ทั้งหมดจากดาต้าสตोर โดยวิธีนี้จะตรวจสอบก่อนว่าที่เก็บข้อมูลมีอยู่หรือไม่ หากไม่มี ระบบจะสร้างอาร์เรย์ todos ว่างและบันทึกลงในที่เก็บข้อมูลเพื่อให้ไม่มีข้อผิดพลาดในการอ่านรันไทม์

ใน js/store.js ให้แปลงการใช้ localStorage ในเมธอดคอนสตรัคเตอร์ไปใช้ chrome.storage.local แทน ดังนี้

function Store(name, callback) {
  var data;
  var dbName;

  callback = callback || function () {};

  dbName = this._dbName = name;

  if (!localStorage[dbName]) {
    data = {
      todos: []
    };
    localStorage[dbName] = JSON.stringify(data);
  }
  callback.call(this, JSON.parse(localStorage[dbName]));

  chrome.storage.local.get(dbName, function(storage) {
    if ( dbName in storage ) {
      callback.call(this, storage[dbName].todos);
    } else {
      storage = {};
      storage[dbName] = { todos: [] };
      chrome.storage.local.set( storage, function() {
        callback.call(this, storage[dbName].todos);
      }.bind(this));
    }
  }.bind(this));
}

2. ระบบจะใช้เมธอด find() เมื่ออ่านข้อมูล Todo จากโมเดล ผลลัพธ์ที่แสดงจะเปลี่ยนไปตามการกรองตาม "ทั้งหมด" "ใช้งานอยู่" หรือ "เสร็จสมบูรณ์"

วิธีแปลง find() เพื่อใช้ chrome.storage.local

Store.prototype.find = function (query, callback) {
  if (!callback) {
    return;
  }

  var todos = JSON.parse(localStorage[this._dbName]).todos;

  callback.call(this, todos.filter(function (todo) {
  chrome.storage.local.get(this._dbName, function(storage) {
    var todos = storage[this._dbName].todos.filter(function (todo) {
      for (var q in query) {
         return query[q] === todo[q];
      }
      });
    callback.call(this, todos);
  }.bind(this));
  }));
};

3. findAll() ได้รับสิ่งที่ต้องทำทั้งหมดจากโมเดลนี้ เช่นเดียวกับ find() แปลง findAll() เพื่อใช้งาน chrome.storage.local

Store.prototype.findAll = function (callback) {
  callback = callback || function () {};
  callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
  chrome.storage.local.get(this._dbName, function(storage) {
    var todos = storage[this._dbName] && storage[this._dbName].todos || [];
    callback.call(this, todos);
  }.bind(this));
};

บันทึกรายการสิ่งที่ต้องทำ

เมธอด save() ปัจจุบันแสดงความท้าทาย การดำเนินการนี้ขึ้นอยู่กับการดำเนินการแบบแอซิงค์ 2 รายการ (get และ set) ที่ดำเนินการกับพื้นที่เก็บข้อมูล JSON แบบโมโนลิธิคทั้งหมดทุกครั้ง การอัปเดตแบบกลุ่มในรายการสิ่งที่ต้องทำมากกว่า 1 รายการ เช่น "ทำเครื่องหมายสิ่งที่ต้องทำทั้งหมดว่าเสร็จแล้ว" จะทำให้เกิดอันตรายของข้อมูลที่เรียกว่า Read-After-Write ปัญหานี้จะไม่เกิดขึ้นหากเราใช้พื้นที่เก็บข้อมูลที่เหมาะสมกว่า เช่น IndexedDB แต่เราพยายามลดขั้นตอนการแปลงสําหรับโค้ดแล็บนี้

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

1. เริ่มต้นด้วยการรวมทุกอย่างที่อยู่ใน save() ไว้ด้วยกันด้วย chrome.storage.local.get() callback ดังนี้

Store.prototype.save = function (id, updateData, callback) {
  chrome.storage.local.get(this._dbName, function(storage) {
    var data = JSON.parse(localStorage[this._dbName]);
    // ...
    if (typeof id !== 'object') {
      // ...
    }else {
      // ...
    }
  }.bind(this));
};

2. แปลงอินสแตนซ์ localStorage ทั้งหมดด้วย chrome.storage.local

Store.prototype.save = function (id, updateData, callback) {
  chrome.storage.local.get(this._dbName, function(storage) {
    var data = JSON.parse(localStorage[this._dbName]);
    var data = storage[this._dbName];
    var todos = data.todos;

    callback = callback || function () {};

    // If an ID was actually given, find the item and update each property
    if ( typeof id !== 'object' ) {
      // ...

      localStorage[this._dbName] = JSON.stringify(data);
      callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
      chrome.storage.local.set(storage, function() {
        chrome.storage.local.get(this._dbName, function(storage) {
          callback.call(this, storage[this._dbName].todos);
        }.bind(this));
      }.bind(this));
    } else {
      callback = updateData;

      updateData = id;

      // Generate an ID
      updateData.id = new Date().getTime();

      localStorage[this._dbName] = JSON.stringify(data);
      callback.call(this, [updateData]);
      chrome.storage.local.set(storage, function() {
        callback.call(this, [updateData]);
      }.bind(this));
    }
  }.bind(this));
};

3. จากนั้นอัปเดตตรรกะให้ทำงานกับอาร์เรย์แทนรายการเดียว

Store.prototype.save = function (id, updateData, callback) {
  chrome.storage.local.get(this._dbName, function(storage) {
    var data = storage[this._dbName];
    var todos = data.todos;

    callback = callback || function () {};

    // If an ID was actually given, find the item and update each property
    if ( typeof id !== 'object' || Array.isArray(id) ) {
      var ids = [].concat( id );
      ids.forEach(function(id) {
        for (var i = 0; i < todos.length; i++) {
          if (todos[i].id == id) {
            for (var x in updateData) {
              todos[i][x] = updateData[x];
            }
          }
        }
      });

      chrome.storage.local.set(storage, function() {
        chrome.storage.local.get(this._dbName, function(storage) {
          callback.call(this, storage[this._dbName].todos);
        }.bind(this));
      }.bind(this));
    } else {
      callback = updateData;

      updateData = id;

      // Generate an ID
      updateData.id = new Date().getTime();

      todos.push(updateData);
      chrome.storage.local.set(storage, function() {
        callback.call(this, [updateData]);
      }.bind(this));
    }
  }.bind(this));
};

ทำเครื่องหมายรายการสิ่งที่ต้องทำว่าเสร็จสมบูรณ์

ตอนนี้แอปทำงานโดยใช้อาร์เรย์ คุณต้องเปลี่ยนวิธีที่แอปจัดการผู้ใช้โดยคลิกปุ่ม ล้างข้อมูลที่เสร็จสมบูรณ์ (#) ดังนี้

1. ใน controller.js ให้อัปเดต toggleAll() ให้เรียก toggleComplete() เพียงครั้งเดียวด้วยอาร์เรย์ของ Todo แทนการทำเครื่องหมาย Todo ว่าเสร็จสมบูรณ์ทีละรายการ และลบการเรียกใช้ _filter() ด้วยเนื่องจากคุณจะปรับ toggleComplete _filter()

Controller.prototype.toggleAll = function (e) {
  var completed = e.target.checked ? 1 : 0;
  var query = 0;
  if (completed === 0) {
    query = 1;
  }
  this.model.read({ completed: query }, function (data) {
    var ids = [];
    data.forEach(function (item) {
      this.toggleComplete(item.id, e.target, true);
      ids.push(item.id);
    }.bind(this));
    this.toggleComplete(ids, e.target, false);
  }.bind(this));

  this._filter();
};

2. ตอนนี้อัปเดต toggleComplete() ให้ยอมรับทั้งรายการสิ่งที่ต้องทำรายการเดียวหรือรายการสิ่งที่ต้องทำหลายรายการ ซึ่งรวมถึงการย้าย filter() ไปไว้ภายใน update() แทนที่จะอยู่นอก

Controller.prototype.toggleComplete = function (ids, checkbox, silent) {
  var completed = checkbox.checked ? 1 : 0;
  this.model.update(ids, { completed: completed }, function () {
    if ( ids.constructor != Array ) {
      ids = [ ids ];
    }
    ids.forEach( function(id) {
      var listItem = $$('[data-id="' + id + '"]');
      
      if (!listItem) {
        return;
      }
      
      listItem.className = completed ? 'completed' : '';
      
      // In case it was toggled from an event and not by clicking the checkbox
      listItem.querySelector('input').checked = completed;
    });

    if (!silent) {
      this._filter();
    }

  }.bind(this));
};

Count todo items

After switching to async storage, there is a minor bug that shows up when getting the number of todos. You'll need to wrap the count operation in a callback function:

1. In model.js, update getCount() to accept a callback:

  Model.prototype.getCount = function (callback) {
  var todos = {
    active: 0,
    completed: 0,
    total: 0
  };
  this.storage.findAll(function (data) {
    data.each(function (todo) {
      if (todo.completed === 1) {
        todos.completed++;
      } else {
        todos.active++;
      }
      todos.total++;
    });
    if (callback) callback(todos);
  });
  return todos;
};

2. Back in controller.js, update _updateCount() to use the async getCount() you edited in the previous step:

Controller.prototype._updateCount = function () {
  var todos = this.model.getCount();
  this.model.getCount(function(todos) {
    this.$todoItemCounter.innerHTML = this.view.itemCounter(todos.active);

    this.$clearCompleted.innerHTML = this.view.clearCompletedButton(todos.completed);
    this.$clearCompleted.style.display = todos.completed > 0 ? 'block' : 'none';

    this.$toggleAll.checked = todos.completed === todos.total;

    this._toggleFrame(todos);
  }.bind(this));

};

You are almost there! If you reload the app now, you will be able to insert new todos without any console errors.

Remove todos items

Now that the app can save todo items, you're close to being done! You still get errors when you attempt to remove todo items:

Todo app with localStorage console log error

1. In store.js, convert all the localStorage instances to use chrome.storage.local:

a) To start off, wrap everything already inside remove() with a get() callback:

Store.prototype.remove = function (id, callback) {
  chrome.storage.local.get(this._dbName, function(storage) {
    var data = JSON.parse(localStorage[this._dbName]);
    var todos = data.todos;

    for (var i = 0; i < todos.length; i++) {
      if (todos[i].id == id) {
        todos.splice(i, 1);
        break;
      }
    }

    localStorage[this._dbName] = JSON.stringify(data);
    callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
  }.bind(this));
};

b) Then convert the contents within the get() callback:

Store.prototype.remove = function (id, callback) {
  chrome.storage.local.get(this._dbName, function(storage) {
    var data = JSON.parse(localStorage[this._dbName]);
    var data = storage[this._dbName];
    var todos = data.todos;

    for (var i = 0; i < todos.length; i++) {
      if (todos[i].id == id) {
        todos.splice(i, 1);
        break;
      }
    }

    localStorage[this._dbName] = JSON.stringify(data);
    callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
    chrome.storage.local.set(storage, function() {
      callback.call(this, todos);
    }.bind(this));
  }.bind(this));
};

2. The same Read-After-Write data hazard issue previously present in the save() method is also present when removing items so you will need to update a few more places to allow for batch operations on a list of todo IDs.

a) Still in store.js, update remove():

Store.prototype.remove = function (id, callback) {
  chrome.storage.local.get(this._dbName, function(storage) {
    var data = storage[this._dbName];
    var todos = data.todos;

    var ids = [].concat(id);
    ids.forEach( function(id) {
      for (var i = 0; i < todos.length; i++) {
        if (todos[i].id == id) {
          todos.splice(i, 1);
          break;
        }
      }
    });

    chrome.storage.local.set(storage, function() {
      callback.call(this, todos);
    }.bind(this));
  }.bind(this));
};

b) In controller.js, change removeCompletedItems() to make it call removeItem() on all IDs at once:

Controller.prototype.removeCompletedItems = function () {
  this.model.read({ completed: 1 }, function (data) {
    var ids = [];
    data.forEach(function (item) {
      this.removeItem(item.id);
      ids.push(item.id);
    }.bind(this));
    this.removeItem(ids);
  }.bind(this));

  this._filter();
};

c) Finally, still in controller.js, change the removeItem() to support removing multiple items from the DOM at once, and move the _filter() call to be inside the callback:

Controller.prototype.removeItem = function (id) {
  this.model.remove(id, function () {
    var ids = [].concat(id);
    ids.forEach( function(id) {
      this.$todoList.removeChild($$('[data-id="' + id + '"]'));
    }.bind(this));
    this._filter();
  }.bind(this));
  this._filter();
};

วางรายการสิ่งที่ต้องทำทั้งหมด

มีอีก 1 วิธีใน store.js ที่ใช้ localStorage ดังนี้

Store.prototype.drop = function (callback) {
  localStorage[this._dbName] = JSON.stringify({todos: []});
  callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
};

ไม่มีการเรียกใช้เมธอดนี้ในแอปปัจจุบัน ดังนั้นหากต้องการความท้าทายเพิ่มเติม ให้ลองติดตั้งใช้งานด้วยตนเอง เคล็ดลับ: ดูที่ chrome.storage.local.clear()

เปิดแอป Todo ที่เสร็จแล้ว

ขั้นตอนที่ 2 เสร็จแล้ว โหลดแอปซ้ำ คุณควรมี TodoMVC เวอร์ชันแพ็กเกจ Chrome ที่ใช้งานได้อย่างสมบูรณ์แล้ว

สำหรับข้อมูลเพิ่มเติม

ดูข้อมูลโดยละเอียดเพิ่มเติมเกี่ยวกับ API บางรายการที่แนะนำในขั้นตอนนี้ได้ที่

พร้อมที่จะดำเนินการขั้นตอนถัดไปไหม ไปที่ขั้นตอนที่ 3 - เพิ่มการปลุกและการแจ้งเตือน »