プッシュ通知の設定と受信

スマートウォッチ コレクションのメソッドを使用すると、フォームのデータが変更されたときに通知を受け取ることができます。このページでは、プッシュ通知の設定と受信のコンセプトの概要と手順について説明します。

概要

Google Form API のプッシュ通知機能を使用すると、フォームでデータが変更されたときに、アプリケーションで通知に登録できます。通知は、通常変更から数分以内に Cloud Pub/Sub トピックに配信されます。

プッシュ通知を受信するには、Cloud Pub/Sub トピックを設定し、適切なイベントタイプのウォッチを作成する際にそのトピックの名前を指定する必要があります。

このドキュメントで使用する主なコンセプトの定義を以下に示します。

  • ターゲットとは、通知が送信される場所です。サポートされているターゲットは Cloud Pub/Sub トピックのみです。
  • イベントタイプは、サードパーティ アプリが登録できる通知のカテゴリです。
  • ウォッチは、特定のフォームでの特定のイベントタイプの通知をターゲットに配信するために、Forms API に指示を出します。

特定のフォームでイベントタイプのウォッチを作成すると、そのウォッチのターゲット(Cloud Pub/Sub トピック)は、ウォッチの有効期限が切れるまでそのフォーム上のイベントからの通知を受信します。スマートウォッチは 1 週間持続しますが、watches.renew() をリクエストすることで、有効期限が切れる前であればいつでも延長できます。

Cloud Pub/Sub トピックは、指定した認証情報で表示できるフォームに関する通知のみを受信します。たとえば、ユーザーがアプリから権限を取り消した場合や、監視対象フォームの編集権限を失った場合、通知は配信されなくなります。

使用可能なイベントタイプ

Google Form API には現在、次の 2 つのカテゴリのイベントが用意されています。

  • EventType.SCHEMA: フォームのコンテンツと設定に対する編集について通知します。
  • EventType.RESPONSES: フォームの回答(新規と更新の両方)が送信されたときに通知します。

通知レスポンス

通知は JSON でエンコードされ、以下を含みます。

  • トリガーとなるフォームの ID
  • トリガーとなるスマートウォッチの ID
  • 通知をトリガーしたイベントの種類
  • Cloud Pub/Sub によって設定されるその他のフィールド(messageIdpublishTime など)

通知には、詳細なフォームやレスポンス データは含まれません。通知を受信するたびに、新しいデータを取得するために個別の API 呼び出しが必要になります。これを行う方法については、推奨される使用方法をご覧ください。

次のスニペットは、スキーマ変更のサンプル通知を示しています。

{
  "attributes": {
    "eventType": "SCHEMA",
    "formId": "18Xgmr4XQb-l0ypfCNGQoHAw2o82foMr8J0HPHdagS6g",
    "watchId": "892515d1-a902-444f-a2fe-42b718fe8159"
  },
  "messageId": "767437830649",
  "publishTime": "2021-03-31T01:34:08.053Z"
}

次のスニペットは、新しいレスポンスの通知のサンプルです。

{
  "attributes": {
    "eventType": "RESPONSES",
    "formId": "18Xgmr4XQb-l0ypfCNGQoHAw2o82foMr8J0HPHdagS6g",
    "watchId": "5d7e5690-b1ff-41ce-8afb-b469912efd7d"
  },
  "messageId": "767467004397",
  "publishTime": "2021-03-31T01:43:57.285Z"
}

Cloud Pub/Sub トピックを設定する

通知は Cloud Pub/Sub トピックに配信されます。Cloud Pub/Sub から通知を受け取るには、ウェブフックを使用するか、サブスクリプション エンドポイントをポーリングします。

Cloud Pub/Sub トピックを設定するには、次の手順を行います。

  1. Cloud Pub/Sub の前提条件を満たしている。
  2. Cloud Pub/Sub クライアントを設定します
  3. Cloud Pub/Sub の料金を確認し、Play Console プロジェクトの課金を有効にします。
  4. Cloud Pub/Sub トピックは、次の 3 つの方法のいずれかで作成します。

  5. Cloud Pub/Sub でサブスクリプションを作成して、Cloud Pub/Sub に通知の配信方法を指示します。

  6. 最後に、トピックをターゲットとするスマートウォッチを作成する前に、フォームの通知サービス アカウント(forms-notifications@system.gserviceaccount.com)に、トピックに公開するための権限を付与する必要があります。

スマートウォッチの作成

Form API のプッシュ通知サービス アカウントが公開できるトピックを取得したら、watches.create() メソッドを使用して通知を作成できます。このメソッドは、指定した Cloud Pub/Sub トピックにプッシュ通知サービス アカウントがアクセスできることを確認し、トピックに到達できない場合(トピックが存在しない場合や、そのトピックに対するパブリッシュ権限を付与していない場合など)に失敗します。

Python

forms/snippets/create_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secret.json", SCOPES)
  creds = tools.run_flow(flow, store)

service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

watch = {
    "watch": {
        "target": {"topic": {"topicName": "<YOUR_TOPIC_PATH>"}},
        "eventType": "RESPONSES",
    }
}

form_id = "<YOUR_FORM_ID>"

# Print JSON response after form watch creation
result = service.forms().watches().create(formId=form_id, body=watch).execute()
print(result)

Node.js

forms/snippets/create_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const watchRequest = {
    watch: {
      target: {
        topic: {
          topicName: 'projects/<YOUR_TOPIC_PATH>',
        },
      },
      eventType: 'RESPONSES',
    },
  };
  const res = await forms.forms.watches.create({
    formId: formID,
    requestBody: watchRequest,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

スマートウォッチを削除する

Python

forms/snippets/delete_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secret.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"
watch_id = "<YOUR_WATCH_ID>"

# Print JSON response after deleting a form watch
result = (
    service.forms().watches().delete(formId=form_id, watchId=watch_id).execute()
)
print(result)

Node.js

form/snippets/delete_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';
const watchID = '<YOUR_FORMS_WATCH_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const res = await forms.forms.watches.delete({
    formId: formID,
    watchId: watchID,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

承認

Form API のすべての呼び出しと同様に、watches.create() の呼び出しは認証トークンを使用して承認する必要があります。トークンには、送信される通知に関するデータに対する読み取りアクセス権を付与するスコープが含まれている必要があります。

通知を配信するには、アプリケーションが、必要なスコープを持つ承認済みユーザーからの OAuth 権限付与を保持する必要があります。ユーザーがアプリを切断すると、通知が停止し、スマートウォッチがエラーで停止することがあります。再び認証された後に通知を再開するには、スマートウォッチの更新をご覧ください。

フォームのウォッチを一覧表示する

Python

forms/snippets/list_watches.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secrets.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"

# Print JSON list of form watches
result = service.forms().watches().list(formId=form_id).execute()
print(result)

Node.js

form/snippets/list_watches.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';

async function runSample(query) {
  const auth = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/forms.responses.readonly',
  });
  const forms = google.forms({
    version: 'v1',
    auth: auth,
  });
  const res = await forms.forms.watches.list({formId: formID});
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

スマートウォッチを更新する

Python

forms/snippets/renew_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secrets.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"
watch_id = "<YOUR_WATCH_ID>"

# Print JSON response after renewing a form watch
result = (
    service.forms().watches().renew(formId=form_id, watchId=watch_id).execute()
)
print(result)

Node.js

form/snippets/renew_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';
const watchID = '<YOUR_FORMS_WATCH_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const res = await forms.forms.watches.renew({
    formId: formID,
    watchId: watchID,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

スロットリング

通知はスロットリングされており、各スマートウォッチは 30 秒ごとに最大 1 件の通知を受信できます。この頻度のしきい値は変更される可能性があります。

スロットリングにより、1 つの通知が複数のイベントに対応する場合があります。つまり、最後の通知以降に 1 つ以上のイベントが発生したことを示します。

上限

各 Cloud Console プロジェクトでは、特定のフォームとイベントタイプについて、いつでも次のことを行えます。

  • 合計最大 20 本まで
  • エンドユーザーあたり最大 1 個のスマートウォッチ

さらに、各フォームで、すべての Cloud コンソール プロジェクトでイベントタイプごとに合計 50 個のウォッチに制限されています。

スマートウォッチは、エンドユーザーの認証情報で作成または更新されると、そのエンドユーザーに関連付けられます。関連付けられているエンドユーザーがフォームへのアクセス権を失うか、アプリのフォームへのアクセス権を取り消すと、スマートウォッチは一時停止されます。

信頼性

例外的な状況を除き、イベントごとに少なくとも 1 回は各スマートウォッチに通知されます。ほとんどの場合、通知はイベント発生から数分以内に配信されます。

エラー

スマートウォッチの通知配信に失敗し続けると、ウォッチ状態は SUSPENDED になり、スマートウォッチの errorType フィールドが設定されます。一時停止されたスマートウォッチの状態を ACTIVE にリセットして通知を再開するには、スマートウォッチを更新するをご覧ください。

推奨される使用方法

  • 多数のスマートウォッチのターゲットとして単一の Cloud Pub/Sub トピックを使用する。
  • トピックに関する通知を受信すると、通知ペイロードにフォーム ID が含まれます。イベントタイプとともに使用して、取得するデータと取得するフォームを確認します。
  • 通知の後に EventType.RESPONSES で更新されたデータをフェッチするには、forms.responses.list() を呼び出します。
    • リクエストのフィルタを timestamp > timestamp_of_the_last_response_you_fetched に設定します。
  • 通知の後に EventType.SCHEMA で更新されたデータを取得するには、forms.get() を呼び出します。