@linqapp/sdk 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/api-promise.d.mts +1 -0
- package/api-promise.d.mts.map +1 -1
- package/api-promise.d.ts +1 -0
- package/api-promise.d.ts.map +1 -1
- package/api-promise.mjs +1 -0
- package/api-promise.mjs.map +1 -1
- package/client.d.mts +162 -84
- package/client.d.mts.map +1 -1
- package/client.d.ts +162 -84
- package/client.d.ts.map +1 -1
- package/client.js +162 -84
- package/client.js.map +1 -1
- package/client.mjs +162 -84
- package/client.mjs.map +1 -1
- package/core/api-promise.d.mts.map +1 -1
- package/core/api-promise.d.ts.map +1 -1
- package/core/api-promise.mjs.map +1 -1
- package/core/error.mjs.map +1 -1
- package/core/pagination.d.mts.map +1 -1
- package/core/pagination.d.ts.map +1 -1
- package/core/pagination.mjs.map +1 -1
- package/core/resource.d.mts.map +1 -1
- package/core/resource.d.ts.map +1 -1
- package/core/uploads.d.mts.map +1 -1
- package/core/uploads.d.ts.map +1 -1
- package/core/uploads.mjs.map +1 -1
- package/error.d.mts +1 -0
- package/error.d.mts.map +1 -1
- package/error.d.ts +1 -0
- package/error.d.ts.map +1 -1
- package/error.mjs +1 -0
- package/error.mjs.map +1 -1
- package/index.d.mts.map +1 -1
- package/index.d.ts.map +1 -1
- package/index.mjs.map +1 -1
- package/internal/detect-platform.mjs.map +1 -1
- package/internal/headers.mjs.map +1 -1
- package/internal/parse.d.mts.map +1 -1
- package/internal/parse.d.ts.map +1 -1
- package/internal/parse.mjs.map +1 -1
- package/internal/request-options.d.mts.map +1 -1
- package/internal/request-options.d.ts.map +1 -1
- package/internal/shims.d.mts +6 -0
- package/internal/shims.d.mts.map +1 -1
- package/internal/shims.d.ts +6 -0
- package/internal/shims.d.ts.map +1 -1
- package/internal/to-file.d.mts.map +1 -1
- package/internal/to-file.d.ts.map +1 -1
- package/internal/to-file.mjs.map +1 -1
- package/internal/types.d.mts +4 -4
- package/internal/types.d.mts.map +1 -1
- package/internal/types.d.ts +4 -4
- package/internal/types.d.ts.map +1 -1
- package/internal/uploads.d.mts.map +1 -1
- package/internal/uploads.d.ts.map +1 -1
- package/internal/uploads.mjs.map +1 -1
- package/internal/utils/base64.mjs.map +1 -1
- package/internal/utils/log.d.mts.map +1 -1
- package/internal/utils/log.d.ts.map +1 -1
- package/internal/utils/log.mjs.map +1 -1
- package/internal/utils/path.mjs.map +1 -1
- package/internal/utils/query.mjs.map +1 -1
- package/internal/utils/values.mjs.map +1 -1
- package/internal/utils.d.mts.map +1 -1
- package/internal/utils.d.ts.map +1 -1
- package/internal/utils.mjs.map +1 -1
- package/package.json +1 -1
- package/pagination.d.mts +1 -0
- package/pagination.d.mts.map +1 -1
- package/pagination.d.ts +1 -0
- package/pagination.d.ts.map +1 -1
- package/pagination.mjs +1 -0
- package/pagination.mjs.map +1 -1
- package/resource.d.mts +1 -0
- package/resource.d.mts.map +1 -1
- package/resource.d.ts +1 -0
- package/resource.d.ts.map +1 -1
- package/resource.mjs +1 -0
- package/resource.mjs.map +1 -1
- package/resources/attachments.d.mts.map +1 -1
- package/resources/attachments.d.ts.map +1 -1
- package/resources/attachments.mjs.map +1 -1
- package/resources/capability.d.mts.map +1 -1
- package/resources/capability.d.ts.map +1 -1
- package/resources/capability.mjs.map +1 -1
- package/resources/chats/chats.d.mts +4 -0
- package/resources/chats/chats.d.mts.map +1 -1
- package/resources/chats/chats.d.ts +4 -0
- package/resources/chats/chats.d.ts.map +1 -1
- package/resources/chats/chats.js +4 -0
- package/resources/chats/chats.js.map +1 -1
- package/resources/chats/chats.mjs +4 -0
- package/resources/chats/chats.mjs.map +1 -1
- package/resources/chats/index.d.mts +1 -0
- package/resources/chats/index.d.mts.map +1 -1
- package/resources/chats/index.d.ts +1 -0
- package/resources/chats/index.d.ts.map +1 -1
- package/resources/chats/index.js +3 -1
- package/resources/chats/index.js.map +1 -1
- package/resources/chats/index.mjs +1 -0
- package/resources/chats/index.mjs.map +1 -1
- package/resources/chats/location.d.mts +107 -0
- package/resources/chats/location.d.mts.map +1 -0
- package/resources/chats/location.d.ts +107 -0
- package/resources/chats/location.d.ts.map +1 -0
- package/resources/chats/location.js +68 -0
- package/resources/chats/location.js.map +1 -0
- package/resources/chats/location.mjs +64 -0
- package/resources/chats/location.mjs.map +1 -0
- package/resources/chats/messages.d.mts.map +1 -1
- package/resources/chats/messages.d.ts.map +1 -1
- package/resources/chats/messages.mjs.map +1 -1
- package/resources/chats/participants.d.mts.map +1 -1
- package/resources/chats/participants.d.ts.map +1 -1
- package/resources/chats/participants.mjs.map +1 -1
- package/resources/chats/typing.d.mts.map +1 -1
- package/resources/chats/typing.d.ts.map +1 -1
- package/resources/chats/typing.mjs.map +1 -1
- package/resources/chats.d.mts.map +1 -1
- package/resources/chats.d.ts.map +1 -1
- package/resources/chats.mjs.map +1 -1
- package/resources/contact-card.d.mts.map +1 -1
- package/resources/contact-card.d.ts.map +1 -1
- package/resources/contact-card.mjs.map +1 -1
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts.map +1 -1
- package/resources/index.mjs.map +1 -1
- package/resources/messages.d.mts.map +1 -1
- package/resources/messages.d.ts.map +1 -1
- package/resources/messages.mjs.map +1 -1
- package/resources/phone-numbers.d.mts.map +1 -1
- package/resources/phone-numbers.d.ts.map +1 -1
- package/resources/phone-numbers.mjs.map +1 -1
- package/resources/phonenumbers.d.mts.map +1 -1
- package/resources/phonenumbers.d.ts.map +1 -1
- package/resources/phonenumbers.mjs.map +1 -1
- package/resources/webhook-events.d.mts +83 -44
- package/resources/webhook-events.d.mts.map +1 -1
- package/resources/webhook-events.d.ts +83 -44
- package/resources/webhook-events.d.ts.map +1 -1
- package/resources/webhook-events.js +81 -42
- package/resources/webhook-events.js.map +1 -1
- package/resources/webhook-events.mjs +81 -42
- package/resources/webhook-events.mjs.map +1 -1
- package/resources/webhook-subscriptions.d.mts +89 -44
- package/resources/webhook-subscriptions.d.mts.map +1 -1
- package/resources/webhook-subscriptions.d.ts +89 -44
- package/resources/webhook-subscriptions.d.ts.map +1 -1
- package/resources/webhook-subscriptions.js +90 -45
- package/resources/webhook-subscriptions.js.map +1 -1
- package/resources/webhook-subscriptions.mjs +90 -45
- package/resources/webhook-subscriptions.mjs.map +1 -1
- package/resources/webhooks.d.mts +4 -4
- package/resources/webhooks.d.mts.map +1 -1
- package/resources/webhooks.d.ts +4 -4
- package/resources/webhooks.d.ts.map +1 -1
- package/resources/webhooks.mjs.map +1 -1
- package/resources.d.mts.map +1 -1
- package/resources.d.ts.map +1 -1
- package/resources.mjs.map +1 -1
- package/src/client.ts +162 -84
- package/src/resources/chats/chats.ts +10 -0
- package/src/resources/chats/index.ts +1 -0
- package/src/resources/chats/location.ts +138 -0
- package/src/resources/webhook-events.ts +85 -44
- package/src/resources/webhook-subscriptions.ts +89 -44
- package/src/resources/webhooks.ts +6 -4
- package/src/version.ts +1 -1
- package/uploads.d.mts +1 -0
- package/uploads.d.mts.map +1 -1
- package/uploads.d.ts +1 -0
- package/uploads.d.ts.map +1 -1
- package/uploads.mjs +1 -0
- package/uploads.mjs.map +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/src/client.ts
CHANGED
|
@@ -1093,55 +1093,82 @@ export class LinqAPIV3 {
|
|
|
1093
1093
|
*
|
|
1094
1094
|
* ## Webhook Headers
|
|
1095
1095
|
*
|
|
1096
|
-
*
|
|
1096
|
+
* All webhook requests include two sets of headers. **If you have an existing integration
|
|
1097
|
+
* using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
|
|
1098
|
+
* every delivery and work exactly as before. The new `webhook-*` headers follow the
|
|
1099
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
|
|
1100
|
+
* You can safely ignore them if your current verification code works and you don't want to use this convention.
|
|
1101
|
+
*
|
|
1102
|
+
* ### Standard Webhooks Headers (Recommended)
|
|
1103
|
+
*
|
|
1104
|
+
* Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
|
|
1097
1105
|
*
|
|
1098
1106
|
* | Header | Description |
|
|
1099
1107
|
* |--------|-------------|
|
|
1100
|
-
* | `
|
|
1101
|
-
* | `
|
|
1102
|
-
* | `
|
|
1103
|
-
* | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
|
|
1108
|
+
* | `webhook-id` | Unique event identifier (use as idempotency key) |
|
|
1109
|
+
* | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
|
|
1110
|
+
* | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
|
|
1104
1111
|
*
|
|
1105
|
-
*
|
|
1112
|
+
* ### Legacy Headers (Deprecated)
|
|
1113
|
+
*
|
|
1114
|
+
* Still sent on every delivery for backwards compatibility. Existing verification code
|
|
1115
|
+
* using these headers continues to work — no changes required.
|
|
1106
1116
|
*
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1117
|
+
* | Header | Description |
|
|
1118
|
+
* |--------|-------------|
|
|
1119
|
+
* | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
|
|
1120
|
+
* | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
|
|
1121
|
+
* | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
|
|
1122
|
+
* | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
|
|
1109
1123
|
*
|
|
1110
|
-
*
|
|
1124
|
+
* ## Signing Secrets
|
|
1111
1125
|
*
|
|
1112
|
-
*
|
|
1126
|
+
* Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
|
|
1127
|
+
* by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
|
|
1113
1128
|
*
|
|
1114
|
-
*
|
|
1115
|
-
* {timestamp}.{payload}
|
|
1116
|
-
* ```
|
|
1129
|
+
* Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
|
|
1117
1130
|
*
|
|
1118
|
-
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1131
|
+
* ## Verifying Webhook Signatures
|
|
1132
|
+
*
|
|
1133
|
+
* Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
|
|
1134
|
+
* You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
|
|
1135
|
+
* signatures, or implement verification manually:
|
|
1136
|
+
*
|
|
1137
|
+
* **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
|
|
1121
1138
|
*
|
|
1122
1139
|
* **Verification Steps:**
|
|
1123
1140
|
*
|
|
1124
|
-
* 1. Extract the `
|
|
1125
|
-
* 2.
|
|
1126
|
-
* 3.
|
|
1127
|
-
* 4.
|
|
1128
|
-
* 5.
|
|
1129
|
-
* 6.
|
|
1141
|
+
* 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
|
|
1142
|
+
* 2. Reject if the timestamp is more than 5 minutes old (replay protection)
|
|
1143
|
+
* 3. Get the raw request body bytes (do not parse and re-serialize)
|
|
1144
|
+
* 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
|
|
1145
|
+
* 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
|
|
1146
|
+
* 6. Compute HMAC-SHA256 using the key bytes over the signed content
|
|
1147
|
+
* 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
|
|
1148
|
+
* 8. Use constant-time comparison to prevent timing attacks
|
|
1130
1149
|
*
|
|
1131
1150
|
* **Example (Python):**
|
|
1132
1151
|
*
|
|
1133
1152
|
* ```python
|
|
1134
|
-
* import hmac
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1153
|
+
* import base64, hmac, hashlib
|
|
1154
|
+
*
|
|
1155
|
+
* def verify_webhook(secret, body, headers):
|
|
1156
|
+
* msg_id = headers['webhook-id']
|
|
1157
|
+
* timestamp = headers['webhook-timestamp']
|
|
1158
|
+
* signature = headers['webhook-signature']
|
|
1159
|
+
*
|
|
1160
|
+
* secret_str = secret.removeprefix('whsec_')
|
|
1161
|
+
* key = base64.b64decode(secret_str)
|
|
1162
|
+
*
|
|
1163
|
+
* signed_content = f"{msg_id}.{timestamp}.{body}"
|
|
1164
|
+
* expected = base64.b64encode(
|
|
1165
|
+
* hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
|
|
1166
|
+
* ).decode()
|
|
1167
|
+
*
|
|
1168
|
+
* for sig in signature.split(' '):
|
|
1169
|
+
* if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
|
|
1170
|
+
* return True
|
|
1171
|
+
* return False
|
|
1145
1172
|
* ```
|
|
1146
1173
|
*
|
|
1147
1174
|
* **Example (Node.js):**
|
|
@@ -1149,16 +1176,28 @@ export class LinqAPIV3 {
|
|
|
1149
1176
|
* ```javascript
|
|
1150
1177
|
* const crypto = require('crypto');
|
|
1151
1178
|
*
|
|
1152
|
-
* function verifyWebhook(
|
|
1153
|
-
* const
|
|
1179
|
+
* function verifyWebhook(secret, rawBody, headers) {
|
|
1180
|
+
* const msgId = headers['webhook-id'];
|
|
1181
|
+
* const timestamp = headers['webhook-timestamp'];
|
|
1182
|
+
* const signature = headers['webhook-signature'];
|
|
1183
|
+
*
|
|
1184
|
+
* const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
|
|
1185
|
+
* const keyBytes = Buffer.from(secretStr, 'base64');
|
|
1186
|
+
* const signedContent = `${msgId}.${timestamp}.${rawBody}`;
|
|
1154
1187
|
* const expected = crypto
|
|
1155
|
-
* .createHmac('sha256',
|
|
1156
|
-
* .update(
|
|
1157
|
-
* .digest('
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1188
|
+
* .createHmac('sha256', keyBytes)
|
|
1189
|
+
* .update(signedContent)
|
|
1190
|
+
* .digest('base64');
|
|
1191
|
+
*
|
|
1192
|
+
* return signature.split(' ').some(sig => {
|
|
1193
|
+
* if (!sig.startsWith('v1,')) return false;
|
|
1194
|
+
* try {
|
|
1195
|
+
* return crypto.timingSafeEqual(
|
|
1196
|
+
* Buffer.from(expected, 'base64'),
|
|
1197
|
+
* Buffer.from(sig.slice(3), 'base64')
|
|
1198
|
+
* );
|
|
1199
|
+
* } catch { return false; }
|
|
1200
|
+
* });
|
|
1162
1201
|
* }
|
|
1163
1202
|
* ```
|
|
1164
1203
|
*
|
|
@@ -1184,55 +1223,82 @@ export class LinqAPIV3 {
|
|
|
1184
1223
|
*
|
|
1185
1224
|
* ## Webhook Headers
|
|
1186
1225
|
*
|
|
1187
|
-
*
|
|
1226
|
+
* All webhook requests include two sets of headers. **If you have an existing integration
|
|
1227
|
+
* using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
|
|
1228
|
+
* every delivery and work exactly as before. The new `webhook-*` headers follow the
|
|
1229
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
|
|
1230
|
+
* You can safely ignore them if your current verification code works and you don't want to use this convention.
|
|
1231
|
+
*
|
|
1232
|
+
* ### Standard Webhooks Headers (Recommended)
|
|
1233
|
+
*
|
|
1234
|
+
* Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
|
|
1188
1235
|
*
|
|
1189
1236
|
* | Header | Description |
|
|
1190
1237
|
* |--------|-------------|
|
|
1191
|
-
* | `
|
|
1192
|
-
* | `
|
|
1193
|
-
* | `
|
|
1194
|
-
* | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
|
|
1238
|
+
* | `webhook-id` | Unique event identifier (use as idempotency key) |
|
|
1239
|
+
* | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
|
|
1240
|
+
* | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
|
|
1195
1241
|
*
|
|
1196
|
-
*
|
|
1242
|
+
* ### Legacy Headers (Deprecated)
|
|
1243
|
+
*
|
|
1244
|
+
* Still sent on every delivery for backwards compatibility. Existing verification code
|
|
1245
|
+
* using these headers continues to work — no changes required.
|
|
1197
1246
|
*
|
|
1198
|
-
*
|
|
1199
|
-
*
|
|
1247
|
+
* | Header | Description |
|
|
1248
|
+
* |--------|-------------|
|
|
1249
|
+
* | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
|
|
1250
|
+
* | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
|
|
1251
|
+
* | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
|
|
1252
|
+
* | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
|
|
1200
1253
|
*
|
|
1201
|
-
*
|
|
1254
|
+
* ## Signing Secrets
|
|
1202
1255
|
*
|
|
1203
|
-
*
|
|
1256
|
+
* Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
|
|
1257
|
+
* by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
|
|
1204
1258
|
*
|
|
1205
|
-
*
|
|
1206
|
-
* {timestamp}.{payload}
|
|
1207
|
-
* ```
|
|
1259
|
+
* Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
|
|
1208
1260
|
*
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1211
|
-
*
|
|
1261
|
+
* ## Verifying Webhook Signatures
|
|
1262
|
+
*
|
|
1263
|
+
* Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
|
|
1264
|
+
* You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
|
|
1265
|
+
* signatures, or implement verification manually:
|
|
1266
|
+
*
|
|
1267
|
+
* **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
|
|
1212
1268
|
*
|
|
1213
1269
|
* **Verification Steps:**
|
|
1214
1270
|
*
|
|
1215
|
-
* 1. Extract the `
|
|
1216
|
-
* 2.
|
|
1217
|
-
* 3.
|
|
1218
|
-
* 4.
|
|
1219
|
-
* 5.
|
|
1220
|
-
* 6.
|
|
1271
|
+
* 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
|
|
1272
|
+
* 2. Reject if the timestamp is more than 5 minutes old (replay protection)
|
|
1273
|
+
* 3. Get the raw request body bytes (do not parse and re-serialize)
|
|
1274
|
+
* 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
|
|
1275
|
+
* 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
|
|
1276
|
+
* 6. Compute HMAC-SHA256 using the key bytes over the signed content
|
|
1277
|
+
* 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
|
|
1278
|
+
* 8. Use constant-time comparison to prevent timing attacks
|
|
1221
1279
|
*
|
|
1222
1280
|
* **Example (Python):**
|
|
1223
1281
|
*
|
|
1224
1282
|
* ```python
|
|
1225
|
-
* import hmac
|
|
1226
|
-
*
|
|
1227
|
-
*
|
|
1228
|
-
*
|
|
1229
|
-
*
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
1283
|
+
* import base64, hmac, hashlib
|
|
1284
|
+
*
|
|
1285
|
+
* def verify_webhook(secret, body, headers):
|
|
1286
|
+
* msg_id = headers['webhook-id']
|
|
1287
|
+
* timestamp = headers['webhook-timestamp']
|
|
1288
|
+
* signature = headers['webhook-signature']
|
|
1289
|
+
*
|
|
1290
|
+
* secret_str = secret.removeprefix('whsec_')
|
|
1291
|
+
* key = base64.b64decode(secret_str)
|
|
1292
|
+
*
|
|
1293
|
+
* signed_content = f"{msg_id}.{timestamp}.{body}"
|
|
1294
|
+
* expected = base64.b64encode(
|
|
1295
|
+
* hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
|
|
1296
|
+
* ).decode()
|
|
1297
|
+
*
|
|
1298
|
+
* for sig in signature.split(' '):
|
|
1299
|
+
* if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
|
|
1300
|
+
* return True
|
|
1301
|
+
* return False
|
|
1236
1302
|
* ```
|
|
1237
1303
|
*
|
|
1238
1304
|
* **Example (Node.js):**
|
|
@@ -1240,16 +1306,28 @@ export class LinqAPIV3 {
|
|
|
1240
1306
|
* ```javascript
|
|
1241
1307
|
* const crypto = require('crypto');
|
|
1242
1308
|
*
|
|
1243
|
-
* function verifyWebhook(
|
|
1244
|
-
* const
|
|
1309
|
+
* function verifyWebhook(secret, rawBody, headers) {
|
|
1310
|
+
* const msgId = headers['webhook-id'];
|
|
1311
|
+
* const timestamp = headers['webhook-timestamp'];
|
|
1312
|
+
* const signature = headers['webhook-signature'];
|
|
1313
|
+
*
|
|
1314
|
+
* const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
|
|
1315
|
+
* const keyBytes = Buffer.from(secretStr, 'base64');
|
|
1316
|
+
* const signedContent = `${msgId}.${timestamp}.${rawBody}`;
|
|
1245
1317
|
* const expected = crypto
|
|
1246
|
-
* .createHmac('sha256',
|
|
1247
|
-
* .update(
|
|
1248
|
-
* .digest('
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1318
|
+
* .createHmac('sha256', keyBytes)
|
|
1319
|
+
* .update(signedContent)
|
|
1320
|
+
* .digest('base64');
|
|
1321
|
+
*
|
|
1322
|
+
* return signature.split(' ').some(sig => {
|
|
1323
|
+
* if (!sig.startsWith('v1,')) return false;
|
|
1324
|
+
* try {
|
|
1325
|
+
* return crypto.timingSafeEqual(
|
|
1326
|
+
* Buffer.from(expected, 'base64'),
|
|
1327
|
+
* Buffer.from(sig.slice(3), 'base64')
|
|
1328
|
+
* );
|
|
1329
|
+
* } catch { return false; }
|
|
1330
|
+
* });
|
|
1253
1331
|
* }
|
|
1254
1332
|
* ```
|
|
1255
1333
|
*
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { APIResource } from '../../core/resource';
|
|
4
4
|
import * as MessagesAPI from '../messages';
|
|
5
5
|
import * as Shared from '../shared';
|
|
6
|
+
import * as LocationAPI from './location';
|
|
7
|
+
import { GetChatLocationResponse, Location, LocationRequestResponse } from './location';
|
|
6
8
|
import * as ChatsMessagesAPI from './messages';
|
|
7
9
|
import { MessageListParams, MessageSendParams, MessageSendResponse, Messages, SentMessage } from './messages';
|
|
8
10
|
import * as ParticipantsAPI from './participants';
|
|
@@ -25,6 +27,7 @@ export class Chats extends APIResource {
|
|
|
25
27
|
participants: ParticipantsAPI.Participants = new ParticipantsAPI.Participants(this._client);
|
|
26
28
|
typing: TypingAPI.Typing = new TypingAPI.Typing(this._client);
|
|
27
29
|
messages: ChatsMessagesAPI.Messages = new ChatsMessagesAPI.Messages(this._client);
|
|
30
|
+
location: LocationAPI.Location = new LocationAPI.Location(this._client);
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* Create a new chat with specified participants and send an initial message. The
|
|
@@ -817,6 +820,7 @@ export interface ChatSendVoicememoParams {
|
|
|
817
820
|
Chats.Participants = Participants;
|
|
818
821
|
Chats.Typing = Typing;
|
|
819
822
|
Chats.Messages = Messages;
|
|
823
|
+
Chats.Location = Location;
|
|
820
824
|
|
|
821
825
|
export declare namespace Chats {
|
|
822
826
|
export {
|
|
@@ -853,4 +857,10 @@ export declare namespace Chats {
|
|
|
853
857
|
type MessageSendParams as MessageSendParams,
|
|
854
858
|
type MessageListParams as MessageListParams,
|
|
855
859
|
};
|
|
860
|
+
|
|
861
|
+
export {
|
|
862
|
+
Location as Location,
|
|
863
|
+
type GetChatLocationResponse as GetChatLocationResponse,
|
|
864
|
+
type LocationRequestResponse as LocationRequestResponse,
|
|
865
|
+
};
|
|
856
866
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2
|
+
|
|
3
|
+
import { APIResource } from '../../core/resource';
|
|
4
|
+
import { APIPromise } from '../../core/api-promise';
|
|
5
|
+
import { buildHeaders } from '../../internal/headers';
|
|
6
|
+
import { RequestOptions } from '../../internal/request-options';
|
|
7
|
+
import { path } from '../../internal/utils/path';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Request and retrieve real-time location data via iMessage.
|
|
11
|
+
*
|
|
12
|
+
* Use these endpoints to request a contact's location, retrieve location data
|
|
13
|
+
* for contacts who are sharing with you, and subscribe to webhooks when someone
|
|
14
|
+
* starts or stops sharing their location.
|
|
15
|
+
*
|
|
16
|
+
* **Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
|
|
17
|
+
* `[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
|
|
18
|
+
*/
|
|
19
|
+
export class Location extends APIResource {
|
|
20
|
+
/**
|
|
21
|
+
* Send a location sharing request to a contact. They will receive an iMessage
|
|
22
|
+
* prompt asking them to share their location.
|
|
23
|
+
*
|
|
24
|
+
* Location requests only work in **1:1 iMessage chats** (Apple limitation).
|
|
25
|
+
* Attempting to request location in a group chat, or in an SMS or RCS chat,
|
|
26
|
+
* returns `409` (Operation not supported on this chat's service type).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const locationRequestResponse =
|
|
31
|
+
* await client.chats.location.request(
|
|
32
|
+
* '975d0776-bd17-4273-8337-f346b4c661b0',
|
|
33
|
+
* );
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
request(chatID: string, options?: RequestOptions): APIPromise<LocationRequestResponse> {
|
|
37
|
+
return this._client.post(path`/v3/chats/${chatID}/location/request`, options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retrieve the current location for contacts sharing with you in a chat.
|
|
42
|
+
*
|
|
43
|
+
* Returns a [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946)
|
|
44
|
+
* `FeatureCollection` with a `Feature` for each participant actively sharing their
|
|
45
|
+
* location.
|
|
46
|
+
*
|
|
47
|
+
* Works for both 1:1 and group chats. In group chats, returns a separate feature
|
|
48
|
+
* for each participant who is sharing. Each feature's `properties.handle`
|
|
49
|
+
* identifies the user.
|
|
50
|
+
*
|
|
51
|
+
* Returns an empty `features` array if no one is sharing or no location data is
|
|
52
|
+
* available yet.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const getChatLocationResponse =
|
|
57
|
+
* await client.chats.location.retrieve(
|
|
58
|
+
* '975d0776-bd17-4273-8337-f346b4c661b0',
|
|
59
|
+
* );
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
retrieve(chatID: string, options?: RequestOptions): APIPromise<GetChatLocationResponse> {
|
|
63
|
+
return this._client.get(path`/v3/chats/${chatID}/location`, {
|
|
64
|
+
...options,
|
|
65
|
+
headers: buildHeaders([{ Accept: 'application/geo+json' }, options?.headers]),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface GetChatLocationResponse {
|
|
71
|
+
data: GetChatLocationResponse.Data;
|
|
72
|
+
|
|
73
|
+
success: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export namespace GetChatLocationResponse {
|
|
77
|
+
export interface Data {
|
|
78
|
+
features: Array<Data.Feature>;
|
|
79
|
+
|
|
80
|
+
type: 'FeatureCollection';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export namespace Data {
|
|
84
|
+
export interface Feature {
|
|
85
|
+
geometry: Feature.Geometry;
|
|
86
|
+
|
|
87
|
+
properties: Feature.Properties;
|
|
88
|
+
|
|
89
|
+
type: 'Feature';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export namespace Feature {
|
|
93
|
+
export interface Geometry {
|
|
94
|
+
/**
|
|
95
|
+
* [longitude, latitude] or [longitude, latitude, altitude]
|
|
96
|
+
*/
|
|
97
|
+
coordinates: Array<number>;
|
|
98
|
+
|
|
99
|
+
type: 'Point';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface Properties {
|
|
103
|
+
/**
|
|
104
|
+
* Phone number or email of the person sharing their location
|
|
105
|
+
*/
|
|
106
|
+
handle: string;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Full street address
|
|
110
|
+
*/
|
|
111
|
+
address?: string;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* City or locality name
|
|
115
|
+
*/
|
|
116
|
+
locality?: string;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* When the location was last updated
|
|
120
|
+
*/
|
|
121
|
+
updated_at?: string;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface LocationRequestResponse {
|
|
128
|
+
message: string;
|
|
129
|
+
|
|
130
|
+
success: boolean;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export declare namespace Location {
|
|
134
|
+
export {
|
|
135
|
+
type GetChatLocationResponse as GetChatLocationResponse,
|
|
136
|
+
type LocationRequestResponse as LocationRequestResponse,
|
|
137
|
+
};
|
|
138
|
+
}
|