Skip to content

Commit dc29600

Browse files
authored
Fix for missing appId in UpdateManager (#437)
Includes: * Fix for missing appId in UpdateManager when not passing a device record * Guards against empty app and player ids in api and updated tests * Adding more guards against missing app id in API native call and updating tests * Adding a TODO comment to appId in the Device Record class
1 parent 587e64b commit dc29600

File tree

16 files changed

+324
-162
lines changed

16 files changed

+324
-162
lines changed

src/OneSignalApiBase.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Environment from './Environment';
22
import SdkEnvironment from './managers/SdkEnvironment';
33
import { Utils } from "./utils/Utils";
4+
import { OneSignalApiError, OneSignalApiErrorKind } from './errors/OneSignalApiError';
45

56
type Headers = any[] & {[key: string]: any};
67
type SupportedMethods = "GET" | "POST" | "PUT" | "DELETE";
@@ -22,7 +23,19 @@ export class OneSignalApiBase {
2223
return OneSignalApiBase.call('DELETE', action, data, headers);
2324
}
2425

25-
private static call(method: SupportedMethods, action: string, data: any, headers: Headers | undefined) {
26+
private static call(method: SupportedMethods, action: string, data: any, headers: Headers | undefined): Promise<any> {
27+
if (method === "GET") {
28+
if (action.indexOf("players") > -1 && action.indexOf("app_id=") === -1) {
29+
console.error("Calls to player api are not permitted without app_id");
30+
return Promise.reject(new OneSignalApiError(OneSignalApiErrorKind.MissingAppId));
31+
}
32+
} else {
33+
if (action.indexOf("players") > -1 && (!data || !data["app_id"])) {
34+
console.error("Calls to player api are not permitted without app_id");
35+
return Promise.reject(new OneSignalApiError(OneSignalApiErrorKind.MissingAppId));
36+
}
37+
}
38+
2639
let callHeaders: any = new Headers();
2740
callHeaders.append('SDK-Version', `onesignal/web/${Environment.version()}`);
2841
callHeaders.append('Content-Type', 'application/json;charset=UTF-8');

src/OneSignalApiSW.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { ServerAppConfig } from "./models/AppConfig";
22
import { OneSignalApiBase } from "./OneSignalApiBase";
33
import { SubscriptionStateKind } from "./models/SubscriptionStateKind";
44
import Log from "./libraries/Log";
5+
import { Utils } from "./utils/Utils";
56

67
export class OneSignalApiSW {
78
static async downloadServerAppConfig(appId: string): Promise<ServerAppConfig> {
9+
Utils.enforceAppId(appId);
810
return await new Promise<ServerAppConfig>((resolve, _reject) => {
911
resolve(OneSignalApiBase.get(`sync/${appId}/web`, null));
1012
});
@@ -16,6 +18,7 @@ export class OneSignalApiSW {
1618
*/
1719
static getUserIdFromSubscriptionIdentifier(appId: string, deviceType: number, identifier: string): Promise<string> {
1820
// Calling POST /players with an existing identifier returns us that player ID
21+
Utils.enforceAppId(appId);
1922
return OneSignalApiBase.post("players", {
2023
app_id: appId,
2124
device_type: deviceType,
@@ -34,6 +37,8 @@ export class OneSignalApiSW {
3437
}
3538

3639
static updatePlayer(appId: string, playerId: string, options?: Object) {
40+
Utils.enforceAppId(appId);
41+
Utils.enforcePlayerId(playerId);
3742
return OneSignalApiBase.put(`players/${playerId}`, {app_id: appId, ...options});
3843
}
3944
}

src/OneSignalApiShared.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import Utils from "./utils/Utils";
88

99
export default class OneSignalApiShared {
1010
static getPlayer(appId: string, playerId: string) {
11+
Utils.enforceAppId(appId);
12+
Utils.enforcePlayerId(playerId);
1113
return OneSignalApiBase.get(`players/${playerId}?app_id=${appId}`);
1214
}
1315

1416
static updatePlayer(appId: string, playerId: string, options?: Object) {
17+
Utils.enforceAppId(appId);
18+
Utils.enforcePlayerId(playerId);
1519
return OneSignalApiBase.put(`players/${playerId}`, {app_id: appId, ...options});
1620
}
1721

@@ -39,7 +43,9 @@ export default class OneSignalApiShared {
3943
}
4044

4145
static async createUser(deviceRecord: DeviceRecord): Promise<string | null> {
42-
const response = await OneSignalApiBase.post(`players`, deviceRecord.serialize());
46+
const serializedDeviceRecord = deviceRecord.serialize();
47+
Utils.enforceAppId(serializedDeviceRecord.app_id);
48+
const response = await OneSignalApiBase.post(`players`, serializedDeviceRecord);
4349
if (response && response.success)
4450
return response.id;
4551
return null;
@@ -50,6 +56,7 @@ export default class OneSignalApiShared {
5056
emailProfile: EmailProfile,
5157
pushDeviceRecordId?: string
5258
): Promise<string | null> {
59+
Utils.enforceAppId(appConfig.appId);
5360
const emailRecord = new EmailDeviceRecord(emailProfile.emailAddress, emailProfile.emailAuthHash);
5461
emailRecord.appId = appConfig.appId;
5562
emailRecord.pushDeviceRecordId = pushDeviceRecordId;
@@ -66,6 +73,8 @@ export default class OneSignalApiShared {
6673
emailProfile: EmailProfile,
6774
pushDeviceRecordId?: string
6875
): Promise<string | null> {
76+
Utils.enforceAppId(appConfig.appId);
77+
Utils.enforcePlayerId(emailProfile.emailId);
6978
const emailRecord = new EmailDeviceRecord(emailProfile.emailAddress, emailProfile.emailAuthHash);
7079
emailRecord.appId = appConfig.appId;
7180
emailRecord.pushDeviceRecordId = pushDeviceRecordId;
@@ -78,6 +87,8 @@ export default class OneSignalApiShared {
7887
}
7988

8089
static async logoutEmail(appConfig: AppConfig, emailProfile: EmailProfile, deviceId: string): Promise<boolean> {
90+
Utils.enforceAppId(appConfig.appId);
91+
Utils.enforcePlayerId(deviceId);
8192
const response = await OneSignalApiBase.post(`players/${deviceId}/email_logout`, {
8293
app_id: appConfig.appId,
8394
parent_player_id: emailProfile.emailId,
@@ -95,7 +106,10 @@ export default class OneSignalApiShared {
95106
deviceRecord: DeviceRecord,
96107
): Promise<string> {
97108
try {
98-
const response = await OneSignalApiBase.post(`players/${userId}/on_session`, deviceRecord.serialize());
109+
const serializedDeviceRecord = deviceRecord.serialize();
110+
Utils.enforceAppId(serializedDeviceRecord.app_id);
111+
Utils.enforcePlayerId(userId);
112+
const response = await OneSignalApiBase.post(`players/${userId}/on_session`, serializedDeviceRecord);
99113
if (response.id) {
100114
// A new user ID can be returned
101115
return response.id;

src/managers/UpdateManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export class UpdateManager {
2727

2828
private async createDeviceRecord(): Promise<PushDeviceRecord> {
2929
const deviceRecord = new PushDeviceRecord();
30+
deviceRecord.appId = this.context.appConfig.appId;
3031
deviceRecord.subscriptionState = await MainHelper.getCurrentNotificationType();
3132
return deviceRecord;
3233
}

src/models/DeviceRecord.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,33 @@ import { Serializable } from './Serializable';
88
import { SubscriptionStateKind } from './SubscriptionStateKind';
99
import { OneSignalUtils } from "../utils/OneSignalUtils";
1010

11+
export interface FlattenedDeviceRecord {
12+
/* Old Parameters */
13+
device_type: DeliveryPlatformKind;
14+
language: string;
15+
timezone: number;
16+
device_os: number;
17+
sdk: string;
18+
notification_types: SubscriptionStateKind | undefined;
19+
20+
/* New Parameters */
21+
delivery_platform: DeliveryPlatformKind;
22+
browser_name: string;
23+
browser_version: number;
24+
operating_system: string;
25+
operating_system_version: string;
26+
device_platform: DevicePlatformKind;
27+
device_model: string;
28+
// TODO: Make it a required parameter
29+
app_id?: string;
30+
}
1131

1232
/**
1333
* Describes the fields of a OneSignal "player" device record.
1434
*
1535
* This is used when creating or modifying push and email records.
1636
*/
1737
export abstract class DeviceRecord implements Serializable {
18-
public appId: string;
1938
public deliveryPlatform: DeliveryPlatformKind;
2039
public language: string;
2140
public timezone: number;
@@ -26,9 +45,12 @@ export abstract class DeviceRecord implements Serializable {
2645
public devicePlatform: DevicePlatformKind;
2746
public deviceModel: string;
2847
public sdkVersion: string;
29-
public subscriptionState: SubscriptionStateKind;
48+
public appId: string | undefined;
49+
public subscriptionState: SubscriptionStateKind | undefined;
3050

3151
constructor() {
52+
// TODO: Possible implementation for appId initialization
53+
// this.appId = OneSignal.context.appConfig.appId;
3254
this.language = Environment.getLanguage();
3355
this.timezone = new Date().getTimezoneOffset() * -60;
3456
this.browserName = bowser.name;
@@ -39,7 +61,7 @@ export abstract class DeviceRecord implements Serializable {
3961
this.deviceModel = navigator.platform;
4062
this.sdkVersion = Environment.version().toString();
4163
this.deliveryPlatform = this.getDeliveryPlatform();
42-
// Unimplemented properties are appId, deliveryPlatform, subscriptionState, and subscription
64+
// Unimplemented properties are appId, subscriptionState, and subscription
4365
}
4466

4567
getDevicePlatform(): DevicePlatformKind {
@@ -129,8 +151,8 @@ export abstract class DeviceRecord implements Serializable {
129151
}
130152
}
131153

132-
serialize() {
133-
const serializedBundle: any = {
154+
serialize(): FlattenedDeviceRecord {
155+
const serializedBundle: FlattenedDeviceRecord = {
134156
/* Old Parameters */
135157
device_type: this.deliveryPlatform,
136158
language: this.language,

src/utils/Utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ export class Utils {
9797
}
9898
return defaultValue;
9999
}
100+
101+
public static enforceAppId(appId: string | undefined | null): void {
102+
if (!appId) {
103+
throw new Error("App id cannot be empty");
104+
}
105+
}
106+
107+
public static enforcePlayerId(playerId: string | undefined | null): void {
108+
if (!playerId) {
109+
throw new Error("Player id cannot be empty");
110+
}
111+
}
100112
}
101113

102114
export default Utils;

test/unit/managers/ServiceWorkerManager.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ test('notification clicked - While page is opened in background', async t => {
159159
const workerMessageReplyBuffer = new WorkerMessengerReplyBuffer();
160160
OneSignal.context.workerMessenger = new WorkerMessenger(OneSignal.context, workerMessageReplyBuffer);
161161

162-
sandbox.stub(Event, 'trigger', function(event: string) {
162+
sandbox.stub(Event, 'trigger').callsFake(function(event: string) {
163163
if (event === OneSignal.EVENTS.NOTIFICATION_CLICKED)
164164
t.pass();
165165
});
@@ -199,9 +199,12 @@ test('installWorker() installs worker A with the correct file name and query par
199199
t.is(await manager.getActiveState(), ServiceWorkerActiveState.None);
200200
await manager.installWorker();
201201
t.is(await manager.getActiveState(), ServiceWorkerActiveState.WorkerA);
202-
t.true(navigator.serviceWorker.controller.scriptURL.endsWith(
203-
`/Worker-A.js?appId=${OneSignal.context.appConfig.appId}`)
204-
);
202+
t.not(navigator.serviceWorker.controller, null);
203+
if (navigator.serviceWorker.controller !== null) {
204+
t.true(navigator.serviceWorker.controller.scriptURL.endsWith(
205+
`/Worker-A.js?appId=${OneSignal.context.appConfig.appId}`)
206+
);
207+
}
205208
});
206209

207210
test('installWorker() installs worker A when a third party service worker exists', async t => {
@@ -296,7 +299,7 @@ test("Service worker failed to install due to 404 on host page. Send notificatio
296299
.get(function(uri) {
297300
return uri.indexOf(workerPath) !== -1;
298301
})
299-
.reply(404, (uri, requestBody) => {
302+
.reply(404, (_uri: string, _requestBody: any) => {
300303
return {
301304
status: 404,
302305
statusText: "404 Not Found"
@@ -332,7 +335,7 @@ test("Service worker failed to install in popup. No handling.", async t => {
332335
.get(function(uri) {
333336
return uri.indexOf(workerPath) !== -1;
334337
})
335-
.reply(404, (uri, requestBody) => {
338+
.reply(404, (_uri: string, _requestBody: any) => {
336339
return {
337340
status: 404,
338341
statusText: "404 Not Found"

test/unit/managers/UpdateManager.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,23 @@ test("sendOnSessionUpdate triggers on_session for existing unsubscribed user if
127127
t.is(onSessionSpy.called, false);
128128
});
129129

130+
test("sendOnSessionUpdate includes appId at all times", async t => {
131+
const deviceId = Random.getRandomUuid();
132+
sandbox.stub(Database, "getSubscription").resolves({ deviceId });
133+
134+
OneSignal.context.sessionManager.setPageViewCount(1);
135+
OneSignal.context.updateManager = new UpdateManager(OneSignal.context);
136+
t.is(OneSignal.context.updateManager.onSessionAlreadyCalled(), false);
137+
138+
const onSessionApiSpy = sandbox.stub(OneSignalApiShared, "updateUserSession").resolves();
139+
await OneSignal.context.updateManager.sendOnSessionUpdate();
140+
t.is(onSessionApiSpy.calledOnce, true);
141+
t.is(onSessionApiSpy.getCall(0).args.length, 2);
142+
t.is(onSessionApiSpy.getCall(0).args[0], deviceId);
143+
t.not(OneSignal.context.appConfig.appId, undefined);
144+
t.is(onSessionApiSpy.getCall(0).args[1].appId, OneSignal.context.appConfig.appId);
145+
});
146+
130147
test("sendPlayerCreate returns user id", async t => {
131148
const onCreateSpy = sandbox.stub(OneSignalApiShared, "createUser").resolves(Random.getRandomUuid());
132149

Lines changed: 20 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,51 @@
11
import '../../support/polyfills/polyfills';
22
import test from 'ava';
33
import { TestEnvironment, HttpHttpsEnvironment } from '../../support/sdk/TestEnvironment';
4-
import CookieSyncer from '../../../src/modules/CookieSyncer';
54
import OneSignal from '../../../src/OneSignal';
6-
import MainHelper from '../../../src/helpers/MainHelper';
75
import sinon from 'sinon';
86
import SubscriptionHelper from '../../../src/helpers/SubscriptionHelper';
97
import { SubscriptionManager } from '../../../src/managers/SubscriptionManager';
10-
import { AppConfig } from '../../../src/models/AppConfig';
11-
12-
import Context from '../../../src/models/Context';
138
import { SessionManager } from '../../../src/managers/SessionManager';
14-
import Random from '../../support/tester/Random';
159

16-
test.beforeEach(async t => {
10+
const sinonSandbox = sinon.sandbox.create();
11+
12+
test.beforeEach(async () => {
1713
await TestEnvironment.initialize({
1814
httpOrHttps: HttpHttpsEnvironment.Https
1915
});
20-
21-
const appConfig = TestEnvironment.getFakeAppConfig();
22-
appConfig.appId = Random.getRandomUuid();
23-
OneSignal.context = new Context(appConfig);
16+
TestEnvironment.mockInternalOneSignal();
2417
});
2518

19+
test.afterEach(() => {
20+
sinonSandbox.restore();
21+
})
22+
2623
test('should not resubscribe user on subsequent page views if the user is already subscribed', async t => {
27-
const isPushEnabledStub = sinon.stub(OneSignal, 'privateIsPushNotificationsEnabled').resolves(true);
28-
const getSessionCountStub = sinon.stub(SessionManager.prototype, 'getPageViewCount').returns(2);
29-
const subscribeSpy = sinon.spy(SubscriptionManager.prototype, 'subscribe');
24+
sinonSandbox.stub(OneSignal, 'privateIsPushNotificationsEnabled').resolves(true);
25+
sinonSandbox.stub(SessionManager.prototype, 'getPageViewCount').returns(2);
26+
const subscribeSpy = sinonSandbox.spy(SubscriptionManager.prototype, 'subscribe');
3027

3128
await SubscriptionHelper.registerForPush();
32-
3329
t.true(subscribeSpy.notCalled);
34-
35-
subscribeSpy.restore();
36-
isPushEnabledStub.restore();
37-
getSessionCountStub.restore();
3830
});
3931

4032
test('should subscribe user on subsequent page views if the user is not subscribed', async t => {
41-
await TestEnvironment.initialize({
42-
httpOrHttps: HttpHttpsEnvironment.Https
43-
});
44-
45-
const appConfig = TestEnvironment.getFakeAppConfig();
46-
appConfig.appId = Random.getRandomUuid();
47-
OneSignal.context = new Context(appConfig);
48-
49-
const isPushEnabledStub = sinon.stub(OneSignal, 'isPushNotificationsEnabled').resolves(false);
50-
const getSessionCountStub = sinon.stub(SessionManager.prototype, 'getPageViewCount').returns(2);
51-
const subscribeStub = sinon.stub(SubscriptionManager.prototype, 'subscribe').resolves(null);
52-
33+
sinonSandbox.stub(OneSignal, 'isPushNotificationsEnabled').resolves(false);
34+
sinonSandbox.stub(SessionManager.prototype, 'getPageViewCount').returns(2);
35+
sinonSandbox.stub(SubscriptionManager.prototype, 'registerSubscription').resolves();
36+
37+
const subscribeStub = sinonSandbox.stub(SubscriptionManager.prototype, 'subscribe').resolves(null);
5338
await SubscriptionHelper.registerForPush();
54-
5539
t.true(subscribeStub.called);
56-
57-
subscribeStub.restore();
58-
isPushEnabledStub.restore();
59-
getSessionCountStub.restore();
6040
});
6141

6242
test('should resubscribe an already subscribed user on first page view', async t => {
63-
await TestEnvironment.initialize({
64-
httpOrHttps: HttpHttpsEnvironment.Https
65-
});
66-
67-
const appConfig = TestEnvironment.getFakeAppConfig();
68-
appConfig.appId = Random.getRandomUuid();
69-
OneSignal.context = new Context(appConfig);
70-
71-
const isPushEnabledStub = sinon.stub(OneSignal, 'isPushNotificationsEnabled').resolves(true);
72-
const getSessionCountStub = sinon.stub(SessionManager.prototype, 'getPageViewCount').returns(1);
73-
const subscribeStub = sinon.stub(SubscriptionManager.prototype, 'subscribe').resolves(null);
43+
sinonSandbox.stub(OneSignal, 'isPushNotificationsEnabled').resolves(true);
44+
sinonSandbox.stub(SessionManager.prototype, 'getPageViewCount').returns(1);
45+
sinonSandbox.stub(SubscriptionManager.prototype, 'registerSubscription').resolves();
7446

47+
const subscribeStub = sinonSandbox.stub(SubscriptionManager.prototype, 'subscribe').resolves(null);
7548
await SubscriptionHelper.registerForPush();
7649

7750
t.true(subscribeStub.called);
78-
79-
subscribeStub.restore();
80-
isPushEnabledStub.restore();
81-
getSessionCountStub.restore();
8251
});

0 commit comments

Comments
 (0)