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

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

概要

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

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

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

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

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

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

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

現在、Google Forms 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 の料金を確認し、Developer Console プロジェクトの課金を有効にします。
  4. Cloud Pub/Sub トピックは、次の 3 つの方法のいずれかで作成できます。

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

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

ウォッチを作成する

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

PythonNode.js
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)
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;

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

PythonNode.js
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)
forms/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;

承認

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

  • スキーマの変更の場合、これは forms.get() を使用してフォームへの読み取りアクセス権を付与するスコープを意味します。
  • 回答の場合、これは フォームの回答への読み取りアクセス権を付与するスコープ(forms.responses.list() の使用など)を意味します。

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

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

PythonNode.js
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)
forms/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;

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

PythonNode.js
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)
forms/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 コンソール プロジェクトにはいつでも次のものが存在できます。

  • 合計 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() を呼び出します。