const ARCHIVER_KEY = '$archiver' const TOP_KEY = '$top' const OBJECTS_KEY = '$objects' const CLASS_KEY = '$class' const CLASSNAME_KEY = '$classname' const NS_OBJECTS = 'NS.objects' const NS_KEYS = 'NS.keys' const NS_TIME = 'NS.time' const NS_MUTABLE_DICTIONARY = 'NSMutableDictionary' const DEFAULT_KEYS = ['AppNotificationCreationDate', 'RequestedDate', 'TriggerDate', 'AppNotificationMessage'] const UNIX_OFFSET = 978307200 const run = (pushstore) => { const pushstoreEntries = [] if (is_valid_pushstore(pushstore)) { let top = get_top(pushstore) if (top == -1) { throw "Unable to get $top location!" return false } pushstore.objects_list = pushstore[OBJECTS_KEY] let pointer_to_entries = load_from_location(pushstore, top) pointer_to_entries['objects'].every((entry_offset) => { let entry_dict = make_entry_dict(pushstore, load_from_location(pushstore, entry_offset)) let formatted = format_entry(pushstore, entry_dict) pushstoreEntries.push(formatted) return true }) } return pushstoreEntries } const is_valid_pushstore = (pushstore) => { // Check version and archiver key return pushstore[ARCHIVER_KEY] === "NSKeyedArchiver" } const get_top = (pushstore) => { // Find pointer in $objects to starting point try { return parseInt(pushstore[TOP_KEY]['root']['UID']) } catch (e) { throw e } } const load_from_location = (pushstore, offset) => { // Load objects (and keys) from a location let loaded_dict = {} let start = pushstore.objects_list[offset] //pushstore.start = start let loaded_class = get_classname_at(start, pushstore) if (!loaded_class) { throw "Unable to determine $classname of key!" } loaded_dict['class'] = loaded_class loaded_dict['objects'] = start[NS_OBJECTS].map((x) => parseInt(x['UID'])) if (loaded_class === NS_MUTABLE_DICTIONARY) { loaded_dict['keys'] = start[NS_KEYS].map((x) => parseInt(x['UID'])) } return loaded_dict } const get_classname_at = (start, pushstore) => { // Get the classname of the object referenced try { return pushstore.objects_list[parseInt(start[CLASS_KEY]['UID'])][CLASSNAME_KEY] } catch (e) { throw e } } const make_entry_dict = (pushstore, loaded) => { // Make dict from offset and keys let entries = {} let offsets = loaded['objects'] let keys = loaded['keys'] let i = 0 while (i < keys.length) { entries[pushstore.objects_list[keys[i]]] = pushstore.objects_list[offsets[i]] i++ } return entries } const format_entry = (pushstore, entry_dict) => { // Format each of the entries let formatted = {} formatted['AppNotificationCreationDate'] = safe_get_time(pushstore, entry_dict, 'AppNotificationCreationDate') formatted['RequestedDate'] = safe_get_time(pushstore, entry_dict, 'RequestedDate') formatted['TriggerDate'] = safe_get_time(pushstore, entry_dict, 'TriggerDate') formatted['AppNotificationMessage'] = entry_dict['AppNotificationMessage'] formatted['AppNotificationTitle'] = entry_dict['AppNotificationTitle'] ? entry_dict['AppNotificationTitle'] : 'N/A' return formatted } const safe_get_time = (pushstore, in_dict, key) => { // Safely get a timestamp try { if (in_dict && in_dict[key] && in_dict[key][NS_TIME]) return to_real_time(in_dict[key][NS_TIME]) return 'N/A' } catch (e) { throw e } } const to_real_time = (ns_time) => { // Convert an NSTime to UTC timestamp string return new Date((ns_time + UNIX_OFFSET) * 1000).toISOString() } module.exports = { run: run }