Skip to content

Commit bb5f12a

Browse files
committed
chore(demo): handle all transient send failure shapes
1 parent 7f93266 commit bb5f12a

5 files changed

Lines changed: 75 additions & 19 deletions

File tree

examples/demo/src/pages/HomeScreen.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,15 @@
113113

114114
.section-head .icon-btn {
115115
margin-left: auto;
116+
margin-right: -11px;
116117
color: var(--os-grey-500);
117118
font-size: 18px;
119+
width: 32px;
120+
height: 32px;
121+
padding: 0;
122+
display: inline-flex;
123+
align-items: center;
124+
justify-content: center;
118125
}
119126

120127
.card {

examples/demo/src/services/OneSignalApiService.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ export const API_KEY = import.meta.env.VITE_ONESIGNAL_API_KEY as string | undefi
88
const ANDROID_CHANNEL_ID = import.meta.env.VITE_ONESIGNAL_ANDROID_CHANNEL_ID as string | undefined;
99
const DEFAULT_ANDROID_CHANNEL_ID = 'b3b015d9-c050-4042-8548-dcc34aa44aa4';
1010

11+
function isTransientSendFailure(data: unknown): boolean {
12+
if (!data || typeof data !== 'object') return false;
13+
const record = data as { id?: unknown; errors?: unknown; recipients?: unknown };
14+
const errors = record.errors;
15+
const hasErrors =
16+
(Array.isArray(errors) && errors.length > 0) ||
17+
(errors != null && typeof errors === 'object' && Object.keys(errors).length > 0);
18+
const missingId = typeof record.id !== 'string' || record.id.length === 0;
19+
const zeroRecipients = typeof record.recipients === 'number' && record.recipients === 0;
20+
return hasErrors || missingId || zeroRecipients;
21+
}
22+
1123
class OneSignalApiService {
1224
private static instance: OneSignalApiService;
1325

@@ -85,9 +97,13 @@ class OneSignalApiService {
8597
const maxAttempts = 5;
8698
const backoffMs = (n: number) => 2_000 * 2 ** (n - 1);
8799

88-
// Retry on `invalid_player_ids` to absorb the brief race where the
89-
// subscription has been created locally but is not yet visible to the
90-
// /notifications endpoint.
100+
// Retry while the OneSignal backend hasn't yet indexed the freshly
101+
// created subscription. The /notifications endpoint reports this race in
102+
// a few different shapes, all of which return HTTP 200:
103+
// {"errors":{"invalid_player_ids":[...]}}
104+
// {"id":"","errors":["All included players are not subscribed"]}
105+
// {"id":"","errors":[...]}
106+
// Treat any 200 response without a real notification id as transient.
91107
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
92108
try {
93109
const response = await CapacitorHttp.post({
@@ -104,15 +120,12 @@ class OneSignalApiService {
104120
return false;
105121
}
106122

107-
const invalidIds = response.data?.errors?.invalid_player_ids;
108-
if (Array.isArray(invalidIds) && invalidIds.length > 0) {
123+
if (isTransientSendFailure(response.data)) {
109124
if (attempt < maxAttempts) {
110125
await new Promise((resolve) => setTimeout(resolve, backoffMs(attempt)));
111126
continue;
112127
}
113-
console.error(
114-
`Send notification failed: invalid_player_ids ${JSON.stringify(invalidIds)}`,
115-
);
128+
console.error(`Send notification failed: ${JSON.stringify(response.data)}`);
116129
return false;
117130
}
118131

examples/demo_cap7/src/app/app.component.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import OneSignal, { LogLevel } from '@onesignal/capacitor-plugin';
44

55
const ONESIGNAL_APP_ID = '77e32082-ea27-42e3-a898-c72e141824ef';
66

7+
function isTransientSendFailure(data: unknown): boolean {
8+
if (!data || typeof data !== 'object') return false;
9+
const record = data as { id?: unknown; errors?: unknown };
10+
const errors = record.errors;
11+
const hasErrors =
12+
(Array.isArray(errors) && errors.length > 0) ||
13+
(errors != null && typeof errors === 'object' && Object.keys(errors).length > 0);
14+
const missingId = typeof record.id !== 'string' || record.id.length === 0;
15+
return hasErrors || missingId;
16+
}
17+
718
@Component({
819
selector: 'app-root',
920
standalone: true,
@@ -98,6 +109,13 @@ export class AppComponent {
98109
};
99110
const maxAttempts = 3;
100111

112+
// Retry while the OneSignal backend hasn't yet indexed the freshly
113+
// created subscription. The /notifications endpoint reports this race
114+
// in a few different shapes, all of which return HTTP 200:
115+
// {"errors":{"invalid_player_ids":[...]}}
116+
// {"id":"","errors":["All included players are not subscribed"]}
117+
// {"id":"","errors":[...]}
118+
// Treat any 200 response without a real notification id as transient.
101119
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
102120
const response = await CapacitorHttp.post({
103121
url: 'https://onesignal.com/api/v1/notifications',
@@ -113,13 +131,12 @@ export class AppComponent {
113131
return;
114132
}
115133

116-
const invalidIds = response.data?.errors?.invalid_player_ids;
117-
if (Array.isArray(invalidIds) && invalidIds.length > 0) {
134+
if (isTransientSendFailure(response.data)) {
118135
if (attempt < maxAttempts) {
119136
await new Promise((resolve) => setTimeout(resolve, 3_000 * attempt));
120137
continue;
121138
}
122-
this.log(`Send failed: invalid_player_ids ${JSON.stringify(invalidIds)}`);
139+
this.log(`Send failed: ${JSON.stringify(response.data)}`);
123140
return;
124141
}
125142

examples/demo_pods/src/pages/HomeScreen.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,15 @@
113113

114114
.section-head .icon-btn {
115115
margin-left: auto;
116+
margin-right: -11px;
116117
color: var(--os-grey-500);
117118
font-size: 18px;
119+
width: 32px;
120+
height: 32px;
121+
padding: 0;
122+
display: inline-flex;
123+
align-items: center;
124+
justify-content: center;
118125
}
119126

120127
.card {

examples/demo_pods/src/services/OneSignalApiService.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ export const API_KEY = import.meta.env.VITE_ONESIGNAL_API_KEY as string | undefi
88
const ANDROID_CHANNEL_ID = import.meta.env.VITE_ONESIGNAL_ANDROID_CHANNEL_ID as string | undefined;
99
const DEFAULT_ANDROID_CHANNEL_ID = 'b3b015d9-c050-4042-8548-dcc34aa44aa4';
1010

11+
function isTransientSendFailure(data: unknown): boolean {
12+
if (!data || typeof data !== 'object') return false;
13+
const record = data as { id?: unknown; errors?: unknown };
14+
const errors = record.errors;
15+
const hasErrors =
16+
(Array.isArray(errors) && errors.length > 0) ||
17+
(errors != null && typeof errors === 'object' && Object.keys(errors).length > 0);
18+
const missingId = typeof record.id !== 'string' || record.id.length === 0;
19+
return hasErrors || missingId;
20+
}
21+
1122
class OneSignalApiService {
1223
private static instance: OneSignalApiService;
1324

@@ -85,9 +96,13 @@ class OneSignalApiService {
8596
const maxAttempts = 5;
8697
const backoffMs = (n: number) => 2_000 * 2 ** (n - 1);
8798

88-
// Retry on `invalid_player_ids` to absorb the brief race where the
89-
// subscription has been created locally but is not yet visible to the
90-
// /notifications endpoint.
99+
// Retry while the OneSignal backend hasn't yet indexed the freshly
100+
// created subscription. The /notifications endpoint reports this race in
101+
// a few different shapes, all of which return HTTP 200:
102+
// {"errors":{"invalid_player_ids":[...]}}
103+
// {"id":"","errors":["All included players are not subscribed"]}
104+
// {"id":"","errors":[...]}
105+
// Treat any 200 response without a real notification id as transient.
91106
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
92107
try {
93108
const response = await CapacitorHttp.post({
@@ -104,15 +119,12 @@ class OneSignalApiService {
104119
return false;
105120
}
106121

107-
const invalidIds = response.data?.errors?.invalid_player_ids;
108-
if (Array.isArray(invalidIds) && invalidIds.length > 0) {
122+
if (isTransientSendFailure(response.data)) {
109123
if (attempt < maxAttempts) {
110124
await new Promise((resolve) => setTimeout(resolve, backoffMs(attempt)));
111125
continue;
112126
}
113-
console.error(
114-
`Send notification failed: invalid_player_ids ${JSON.stringify(invalidIds)}`,
115-
);
127+
console.error(`Send notification failed: ${JSON.stringify(response.data)}`);
116128
return false;
117129
}
118130

0 commit comments

Comments
 (0)