Skip to content

Commit c966016

Browse files
authored
Merge pull request #104 from Countly/CustomStorageOption
Custom Storage Option
2 parents d245d64 + 7c5c523 commit c966016

17 files changed

+1064
-762
lines changed

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
## xx.xx.xx
1+
## 24.10.0
22
- Default max segmentation value count changed from 30 to 100
3-
- Mitigated an issue where attempting to read a missing or empty file could result in the creation of an unintended empty file.
4-
- Added a new init time config option (conf.storage_type) which can make user set among these storage options:
3+
- Mitigated an issue where SDK could create an unintended dump file
4+
- Added a new init time config option (conf.storage_type) which can make user set the SDK storage option:
55
- File Storage
66
- Memory Only Storage
7+
- Added a new init time config option (conf.custom_storage_method) which enables user to provide custom storage methods
78

89
## 22.06.0
910
- Fixed a bug where remote config requests were rejected

lib/countly-bulk.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var cc = require("./countly-common");
2525
var BulkUser = require("./countly-bulk-user");
2626
var CountlyStorage = require("./countly-storage");
2727

28-
const StorageTypes = cc.storageTypeEnums;
28+
CountlyBulk.StorageTypes = cc.storageTypeEnums;
2929

3030
/**
3131
* @lends module:lib/countly-bulk
@@ -39,7 +39,6 @@ const StorageTypes = cc.storageTypeEnums;
3939
* @param {number} [conf.fail_timeout=60] - set time in seconds to wait after failed connection to server in seconds
4040
* @param {number} [conf.session_update=60] - how often in seconds should session be extended
4141
* @param {number} [conf.max_events=100] - maximum amount of events to send in one batch
42-
* @param {boolean} [conf.persist_queue=false] - persistently store queue until processed, default is false if you want to keep queue in memory and process all in one process run
4342
* @param {boolean} [conf.force_post=false] - force using post method for all requests
4443
* @param {string} [conf.storage_path] - where SDK would store data, including id, queues, etc
4544
* @param {string} [conf.http_options=] - function to get http options by reference and overwrite them, before running each request
@@ -50,6 +49,8 @@ const StorageTypes = cc.storageTypeEnums;
5049
* @param {number} [conf.max_stack_trace_lines_per_thread=30] - maximum amount of stack trace lines would be recorded per thread
5150
* @param {number} [conf.max_stack_trace_line_length=200] - maximum amount of characters are allowed per stack trace line. This limits also the crash message length
5251
* @param {StorageTypes} conf.storage_type - to determine which storage type is going to be applied
52+
* @param {Object} conf.custom_storage_method - user given storage methods
53+
* @param {boolean} [conf.persist_queue=false] - *DEPRECATED* persistent mode instead of using in-memory queue. Use storage_type and storage_path instead
5354
* @example
5455
* var server = new CountlyBulk({
5556
* app_key: "{YOUR-API-KEY}",
@@ -75,8 +76,6 @@ function CountlyBulk(conf) {
7576
var maxBreadcrumbCount = 100;
7677
var maxStackTraceLinesPerThread = 30;
7778
var maxStackTraceLineLength = 200;
78-
var storageType = StorageTypes.FILE;
79-
8079
cc.debugBulk = conf.debug || false;
8180
if (!conf.app_key) {
8281
cc.log(cc.logLevelEnums.ERROR, "CountlyBulk, 'app_key' is missing.");
@@ -105,9 +104,13 @@ function CountlyBulk(conf) {
105104
conf.maxBreadcrumbCount = conf.max_breadcrumb_count || maxBreadcrumbCount;
106105
conf.maxStackTraceLinesPerThread = conf.max_stack_trace_lines_per_thread || maxStackTraceLinesPerThread;
107106
conf.maxStackTraceLineLength = conf.max_stack_trace_line_length || maxStackTraceLineLength;
108-
conf.storage_type = conf.storage_type || storageType;
109107

110-
CountlyStorage.initStorage(conf.storage_path, conf.storage_type, true, conf.persist_queue);
108+
// bulk mode is memory only by default
109+
if (typeof conf.storage_type === "undefined" && conf.persist_queue === false) {
110+
conf.storage_type = CountlyBulk.StorageTypes.MEMORY;
111+
}
112+
113+
CountlyStorage.initStorage(conf.storage_path, conf.storage_type, true, conf.custom_storage_method);
111114

112115
this.conf = conf;
113116
/**
@@ -599,6 +602,27 @@ function CountlyBulk(conf) {
599602
var requestQueue = CountlyStorage.storeGet("cly_req_queue", []);
600603
var eventQueue = CountlyStorage.storeGet("cly_bulk_event", {});
601604
var bulkQueue = CountlyStorage.storeGet("cly_bulk_queue", []);
605+
/**
606+
* getBulkEventQueue is a testing purposed method which returns the event queue object
607+
* @returns {Object} eventQueue
608+
*/
609+
this._getBulkEventQueue = function() {
610+
return eventQueue;
611+
};
612+
/**
613+
* getBulkRequestQueue is a testing purposed method which returns the request queue object
614+
* @returns {Object} requestQueue
615+
*/
616+
this._getBulkRequestQueue = function() {
617+
return requestQueue;
618+
};
619+
/**
620+
* getBulkQueue is a testing purposed method which returns the bulk queue object
621+
* @returns {Object} bulkQueue
622+
*/
623+
this._getBulkQueue = function() {
624+
return bulkQueue;
625+
};
602626
}
603627

604628
module.exports = CountlyBulk;

lib/countly-common.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ var cc = {
5454
storageTypeEnums: {
5555
FILE: "file",
5656
MEMORY: "memory",
57-
CUSTOM: "custom",
5857
},
5958

6059
/**

lib/countly-storage.js

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,50 @@ const fs = require('fs');
22
const path = require('path');
33
var cc = require("./countly-common");
44

5+
// Constants
6+
const defaultPath = "../data/"; // Default storage path
7+
const defaultBulkPath = "../bulk_data/"; // Default bulk storage path
8+
const StorageTypes = cc.storageTypeEnums;
9+
const defaultStorageType = StorageTypes.FILE;
10+
const customTypeName = "custom";
11+
512
var storagePath;
6-
var __data = {};
7-
var defaultPath = "../data/"; // Default path
8-
var defaultBulkPath = "../bulk_data/"; // Default path
13+
let storageMethod = {};
14+
var __cache = {};
915
var asyncWriteLock = false;
1016
var asyncWriteQueue = [];
11-
let storageMethod = {};
12-
const StorageTypes = cc.storageTypeEnums;
17+
18+
/**
19+
* Sets the storage method, by default sets file storage and storage path.
20+
* @param {String} userPath - User provided storage path
21+
* @param {StorageTypes} storageType - Whether to use memory only storage or not
22+
* @param {Boolean} isBulk - Whether the storage is for bulk data
23+
* @param {varies} customStorageMethod - Storage methods provided by the user
24+
*/
25+
var initStorage = function(userPath, storageType, isBulk = false, customStorageMethod = null) {
26+
cc.log(cc.logLevelEnums.INFO, `Initializing storage with userPath: [${userPath}], storageType: [${storageType}], isBulk: [${isBulk}], customStorageMethod type: [${typeof customStorageMethod}].`);
27+
28+
// set storage type
29+
storageType = storageType || defaultStorageType;
30+
storageMethod = fileStorage; // file storage is default
31+
32+
if (storageType === StorageTypes.MEMORY) {
33+
storageMethod = memoryStorage;
34+
cc.log(cc.logLevelEnums.DEBUG, `Using memory storage!`);
35+
}
36+
37+
// at this point we either use memory or file storage. If custom storage is provided, check if it is valid and use it instead
38+
if (isCustomStorageValid(customStorageMethod)) {
39+
storageMethod = customStorageMethod;
40+
storageType = customTypeName;
41+
cc.log(cc.logLevelEnums.DEBUG, `Using custom storage!`);
42+
}
43+
44+
// set storage path if not memory storage
45+
if (storageType !== StorageTypes.MEMORY) {
46+
setStoragePath(userPath, isBulk);
47+
}
48+
};
1349

1450
// Memory-only storage methods
1551
const memoryStorage = {
@@ -22,7 +58,7 @@ const memoryStorage = {
2258
storeSet: function(key, value, callback) {
2359
if (key) {
2460
cc.log(cc.logLevelEnums.DEBUG, `storeSet, Setting key: [${key}] & value: [${value}]!`);
25-
__data[key] = value;
61+
__cache[key] = value;
2662
if (typeof callback === "function") {
2763
callback(null);
2864
}
@@ -39,14 +75,14 @@ const memoryStorage = {
3975
*/
4076
storeGet: function(key, def) {
4177
cc.log(cc.logLevelEnums.DEBUG, `storeGet, Fetching item from memory with key: [${key}].`);
42-
return typeof __data[key] !== "undefined" ? __data[key] : def;
78+
return typeof __cache[key] !== "undefined" ? __cache[key] : def;
4379
},
4480
/**
4581
* Remove value from memory
4682
* @param {String} key - key of value to remove
4783
*/
4884
storeRemove: function(key) {
49-
delete __data[key];
85+
delete __cache[key];
5086
},
5187
};
5288

@@ -59,7 +95,7 @@ const fileStorage = {
5995
* @param {Function} callback - callback to call when done storing
6096
*/
6197
storeSet: function(key, value, callback) {
62-
__data[key] = value;
98+
__cache[key] = value;
6399
if (!asyncWriteLock) {
64100
asyncWriteLock = true;
65101
writeFile(key, value, callback);
@@ -76,7 +112,7 @@ const fileStorage = {
76112
*/
77113
storeGet: function(key, def) {
78114
cc.log(cc.logLevelEnums.DEBUG, `storeGet, Fetching item from storage with key: [${key}].`);
79-
if (typeof __data[key] === "undefined") {
115+
if (typeof __cache[key] === "undefined") {
80116
var ob = readFile(key);
81117
var obLen;
82118
// check if the 'read object' is empty or not
@@ -90,17 +126,17 @@ const fileStorage = {
90126

91127
// if empty or falsy set default value
92128
if (!ob || obLen === 0) {
93-
__data[key] = def;
129+
__cache[key] = def;
94130
}
95131
// else set the value read file has
96132
else {
97-
__data[key] = ob[key];
133+
__cache[key] = ob[key];
98134
}
99135
}
100-
return __data[key];
136+
return __cache[key];
101137
},
102138
storeRemove: function(key) {
103-
delete __data[key];
139+
delete __cache[key];
104140
var filePath = path.resolve(__dirname, `${getStoragePath()}__${key}.json`);
105141
fs.access(filePath, fs.constants.F_OK, (accessErr) => {
106142
if (accessErr) {
@@ -119,35 +155,31 @@ const fileStorage = {
119155
},
120156
};
121157

122-
/**
123-
* Sets the storage method, by default sets file storage and storage path.
124-
* @param {String} userPath - User provided storage path
125-
* @param {StorageTypes} storageType - Whether to use memory only storage or not
126-
* @param {Boolean} isBulk - Whether the storage is for bulk data
127-
* @param {Boolean} persistQueue - Whether to persist the queue until processed
128-
*/
129-
var initStorage = function(userPath, storageType, isBulk = false, persistQueue = false) {
130-
if (storageType === StorageTypes.MEMORY) {
131-
storageMethod = memoryStorage;
158+
var isCustomStorageValid = function(storage) {
159+
if (!storage) {
160+
return false;
161+
}
162+
if (typeof storage.storeSet !== 'function') {
163+
return false;
164+
}
165+
if (typeof storage.storeGet !== 'function') {
166+
return false;
132167
}
133-
else {
134-
storageMethod = fileStorage;
135-
setStoragePath(userPath, isBulk, persistQueue);
168+
if (typeof storage.storeRemove !== 'function') {
169+
return false;
136170
}
171+
return true;
137172
};
138173

139174
/**
140175
* Sets the storage path, defaulting to a specified path if none is provided.
141176
* @param {String} userPath - User provided storage path
142177
* @param {Boolean} isBulk - Whether the storage is for bulk data
143-
* @param {Boolean} persistQueue - Whether to persist the queue until processed
144178
*/
145-
var setStoragePath = function(userPath, isBulk = false, persistQueue = false) {
179+
var setStoragePath = function(userPath, isBulk = false) {
146180
storagePath = userPath || (isBulk ? defaultBulkPath : defaultPath);
147181

148-
if (!isBulk || persistQueue) {
149-
createDirectory(path.resolve(__dirname, storagePath));
150-
}
182+
createDirectory(path.resolve(__dirname, storagePath));
151183
};
152184

153185
/**
@@ -178,9 +210,10 @@ var createDirectory = function(dir) {
178210
*/
179211
var resetStorage = function() {
180212
storagePath = undefined;
181-
__data = {};
213+
__cache = {};
182214
asyncWriteLock = false;
183215
asyncWriteQueue = [];
216+
storageMethod = {};
184217
};
185218

186219
/**
@@ -226,10 +259,10 @@ var readFile = function(key) {
226259
* Force store data synchronously on unrecoverable errors to preserve it for next launch
227260
*/
228261
var forceStore = function() {
229-
for (var i in __data) {
262+
for (var i in __cache) {
230263
var dir = path.resolve(__dirname, `${getStoragePath()}__${i}.json`);
231264
var ob = {};
232-
ob[i] = __data[i];
265+
ob[i] = __cache[i];
233266
try {
234267
fs.writeFileSync(dir, JSON.stringify(ob));
235268
}
@@ -292,7 +325,12 @@ var getStorageType = function() {
292325
if (storageMethod === memoryStorage) {
293326
return StorageTypes.MEMORY;
294327
}
295-
return StorageTypes.FILE;
328+
329+
if (storageMethod === fileStorage) {
330+
return StorageTypes.FILE;
331+
}
332+
333+
return null;
296334
};
297335

298336
module.exports = {

lib/countly.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ var Bulk = require("./countly-bulk");
2929
var CountlyStorage = require("./countly-storage");
3030

3131
var Countly = {};
32-
const StorageTypes = cc.storageTypeEnums;
33-
32+
Countly.StorageTypes = cc.storageTypeEnums;
3433
Countly.Bulk = Bulk;
3534
(function() {
3635
var SDK_VERSION = "24.10.0";
@@ -72,7 +71,7 @@ Countly.Bulk = Bulk;
7271
var maxStackTraceLinesPerThread = 30;
7372
var maxStackTraceLineLength = 200;
7473
var deviceIdType = null;
75-
var storageType = StorageTypes.FILE;
74+
var heartBeatTimer = null;
7675
/**
7776
* Array with list of available features that you can require consent for
7877
*/
@@ -124,6 +123,7 @@ Countly.Bulk = Bulk;
124123
* @param {string} conf.metrics._locale - locale or language of the device in ISO format
125124
* @param {string} conf.metrics._store - source from where the user/device/installation came from
126125
* @param {StorageTypes} conf.storage_type - to determine which storage type is going to be applied
126+
* @param {Object} conf.custom_storage_method - user given storage methods
127127
* @example
128128
* Countly.init({
129129
* app_key: "{YOUR-APP-KEY}",
@@ -168,11 +168,12 @@ Countly.Bulk = Bulk;
168168
Countly.maxBreadcrumbCount = conf.max_breadcrumb_count || Countly.max_breadcrumb_count || conf.max_logs || Countly.max_logs || maxBreadcrumbCount;
169169
Countly.maxStackTraceLinesPerThread = conf.max_stack_trace_lines_per_thread || Countly.max_stack_trace_lines_per_thread || maxStackTraceLinesPerThread;
170170
Countly.maxStackTraceLineLength = conf.max_stack_trace_line_length || Countly.max_stack_trace_line_length || maxStackTraceLineLength;
171-
conf.storage_type = conf.storage_type || storageType;
171+
conf.storage_path = conf.storage_path || Countly.storage_path;
172+
conf.storage_type = conf.storage_type || Countly.storage_type;
172173
// Common module debug value is set to init time debug value
173174
cc.debug = conf.debug;
174175

175-
CountlyStorage.initStorage(conf.storage_path, conf.storage_type);
176+
CountlyStorage.initStorage(conf.storage_path, conf.storage_type, false, conf.custom_storage_method);
176177

177178
// clear stored device ID if flag is set
178179
if (conf.clear_stored_device_id) {
@@ -320,6 +321,10 @@ Countly.Bulk = Bulk;
320321
maxStackTraceLinesPerThread = 30;
321322
maxStackTraceLineLength = 200;
322323
deviceIdType = null;
324+
if (heartBeatTimer) {
325+
clearInterval(heartBeatTimer);
326+
heartBeatTimer = null;
327+
}
323328

324329
// cc DEBUG
325330
cc.debug = false;
@@ -334,6 +339,7 @@ Countly.Bulk = Bulk;
334339

335340
// device_id
336341
Countly.device_id = undefined;
342+
Countly.device_id_type = undefined;
337343
Countly.remote_config = undefined;
338344
Countly.require_consent = false;
339345
Countly.debug = undefined;
@@ -1478,7 +1484,7 @@ Countly.Bulk = Bulk;
14781484
}, "heartBeat", false);
14791485
}
14801486

1481-
setTimeout(heartBeat, beatInterval);
1487+
heartBeatTimer = setTimeout(heartBeat, beatInterval);
14821488
}
14831489

14841490
/**

0 commit comments

Comments
 (0)