Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent retriggering of event actions #47

Merged
merged 27 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
256f80b
add skip functionalities
robertherber Feb 14, 2025
efddc3d
sync before and after each event
robertherber Feb 14, 2025
9b56595
Fix so renamed INTERNALLY_TEST_EXAMPLE_PROJECT doesnt cause console e…
robertherber Feb 14, 2025
1db62f7
improve test coverage
robertherber Feb 14, 2025
da8295e
improve test coverage, cleaning up
robertherber Feb 15, 2025
49c60ec
add reloadDeviceActivityCenter to index
robertherber Feb 15, 2025
47104e9
introduce COPY_TO_TARGET_FOLDER
robertherber Feb 15, 2025
952c616
publish 0.4.0
robertherber Feb 15, 2025
3019546
Add skipIfLargerEventRecordedSinceMonitoringStarted
robertherber Feb 15, 2025
653b926
rename to skipIfLargerEventRecordedSinceIntervalStarted
robertherber Feb 15, 2025
0e75445
bump version
robertherber Feb 15, 2025
734dc52
change activitynames
robertherber Feb 15, 2025
c1efe8b
reinit onDeviceActivityDetected listener on deviceactivitycenter reload
robertherber Feb 17, 2025
635b5cb
bump version
robertherber Feb 17, 2025
a0d7ea5
add neverTriggerBefore action param
robertherber Feb 17, 2025
5256a63
bump version
robertherber Feb 17, 2025
0f50efe
Clean up blockSelectedApps
robertherber Feb 17, 2025
7fbb3b4
add shield config triggeredBy for traceability
robertherber Feb 17, 2025
318a1b3
fix unblockedselection
robertherber Feb 17, 2025
a00f582
Add triggeredBy to block actions
robertherber Feb 17, 2025
50465e9
bump version
robertherber Feb 17, 2025
87ad04e
add updatedAt to shieldConfiguration/shieldActions
robertherber Feb 17, 2025
7e7a65d
bump version
robertherber Feb 17, 2025
d3a11c2
Expose triggeredBy the whole way
robertherber Feb 17, 2025
acdb4aa
bump version
robertherber Feb 17, 2025
137ce51
Fix build issue and bump version
robertherber Feb 17, 2025
675e8a7
rename to removePrefixIfPresent
robertherber Feb 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ disabled_rules:
- type_body_length
- cyclomatic_complexity
- file_length
- function_parameter_count

included:
- ios
Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Example project setup

The example project is linked specifically to simplify development. This means it's not looking exactly like the published package, but for most intents and purposes it should result in the same outcome.

- The config plugin has a `copyToTargetFolder` option that is set to false. This is to prevent the target folder from being copied to the example project and potentially overwriting the original files.
- The swift files in the targets folder are linked to the root project instead of duplicated. If adding new swift files, try to link them instead of duplicating to keep things clean.
- The entitlements and info.plist files however duplicated - to not mess with the example project/signing etc.
- There is a Shared.swift file that can be accessed by all targets. This is linked to each target in the example project, but in the published package it's copied and duplicated to each target. I prefer this to making more changes in @bacons/xcode package which only supports swift files on the root level of each target directory.
- In addition the example project contains an XCode test target as well as SwiftLint.

## Prebuild
To try out prebuild functionality (i.e. the config plugin) run `npm run prebuild` in the example project (it uses the `INTERNALLY_TEST_PREBUILD` env variable to behave like a published package). This should be used to verify changes are expected, but the result should not in it's full state be commited to the example project, since it will break the DX of the example project as explained above.

To try out prebuild functionality (i.e. the config plugin) run `bun run prebuild` in the example project (it uses the `INTERNALLY_TEST_EXAMPLE_PROJECT` and `COPY_TO_TARGET_FOLDER` env variables to behave like a published package). This should be used to verify changes are expected, but the result should not in it's full state be commited to the example project, since it will break the DX of the example project as explained above.
2 changes: 1 addition & 1 deletion config-plugin/getAppGroupFromExpoConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const getAppGroupFromExpoConfig = (config) => {
const [pluginName] = plugin;
return (
pluginName === "react-native-device-activity" ||
(process.env.INTERNALLY_TEST_PREBUILD &&
(process.env.INTERNALLY_TEST_EXAMPLE_PROJECT &&
pluginName === "../app.plugin.js")
);
}
Expand Down
2 changes: 1 addition & 1 deletion config-plugin/withCopyTargetFolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const fs = require("fs");

/** @type {import('@expo/config-plugins').ConfigPlugin<{ appGroup: string; copyToTargetFolder?: boolean }>} */
const withCopyTargetFolder = (config, { copyToTargetFolder = true }) => {
if (!copyToTargetFolder && !process.env.INTERNALLY_TEST_PREBUILD) {
if (!copyToTargetFolder && !process.env.COPY_TO_TARGET_FOLDER) {
return config;
}

Expand Down
301 changes: 301 additions & 0 deletions example/ios/Tests/SharedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,304 @@ class SharedTests: XCTestCase {
XCTAssertEqual(result["score"] as? String, "{asNumber:missingValue}")
}
}

class SkipActionTests: XCTestCase {
func testNeverTriggerBefore() {
let activityName = "myActivityWithSkipIfAlreadyTriggeredAfter"
let callbackName = "eventDidReachThreshold"
let eventName = "10"

let shouldTriggerTime = Date().timeIntervalSince1970 * 1000 - 10000
let shouldNotTriggerTime = Date().timeIntervalSince1970 * 1000 + 10000

let shouldNotExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: shouldNotTriggerTime,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

let shouldExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: shouldTriggerTime,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

XCTAssertFalse(shouldNotExecute)
XCTAssertTrue(shouldExecute)
}

func testShouldSkipIfAlreadyTriggeredAfter() {
let activityName = "myActivityWithSkipIfAlreadyTriggeredAfter"
let callbackName = "eventDidReachThreshold"
let eventName = "10"
let key = userDefaultKeyForEvent(
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

userDefaults?.set(1000, forKey: key)

let shouldNotExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: 999,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

let shouldExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: 1000,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

XCTAssertFalse(shouldNotExecute)
XCTAssertTrue(shouldExecute)
}

func testSkipIfAlreadyTriggeredWithinMS() {
let activityName = "myActivity"
let callbackName = "eventDidReachThreshold"
let eventName = "10"
let key = userDefaultKeyForEvent(
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

let time = Date.now.addingTimeInterval(-1)

userDefaults?.set(time.timeIntervalSince1970 * 1000, forKey: key)

let shouldExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: 100,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

let shouldNotExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: 10000,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

XCTAssertTrue(shouldExecute)
XCTAssertFalse(shouldNotExecute)
}

func testShouldSkipIfLargerTriggeredAfter() {
let activityName = "myActivity"
let callbackName = "eventDidReachThreshold"
let eventName = "10"
let higherThanEventName = "15"
let key = userDefaultKeyForEvent(
activityName: activityName,
callbackName: callbackName,
eventName: higherThanEventName
)

userDefaults?.set(1000, forKey: key)

CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)

let shouldNotExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: 999,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

let shouldExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: 1000,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

XCTAssertFalse(shouldNotExecute)
XCTAssertTrue(shouldExecute)
}

func testSkipIfLargerTriggeredWithinMS() {
let activityName = "myActivitySkipIfLargerEventRecordedWithinMS"
let callbackName = "eventDidReachThreshold"
let eventName = "10"
let higherThanEventName = "15"
let key = userDefaultKeyForEvent(
activityName: activityName,
callbackName: callbackName,
eventName: higherThanEventName
)

let time = Date.now.addingTimeInterval(-1)

userDefaults?.set(time.timeIntervalSince1970 * 1000, forKey: key)

CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)

let shouldExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: 100,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

let shouldNotExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: 10000,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: false,
activityName: activityName,
callbackName: callbackName,
eventName: eventName
)

XCTAssertTrue(shouldExecute)
XCTAssertFalse(shouldNotExecute)
}

func testSkipIfLargerTriggeredAfterIntervalStarted() {
let activityName = "myActivity"
let callbackName = "eventDidReachThreshold"

let key = userDefaultKeyForEvent(
activityName: activityName,
callbackName: callbackName,
eventName: "10"
)

let keyForMonitoringStarted = userDefaultKeyForEvent(
activityName: activityName,
callbackName: "intervalDidStart"
)

let time = Date.now.addingTimeInterval(-1)
let intervalStartTime = Date.now.addingTimeInterval(-2)

userDefaults?.set(time.timeIntervalSince1970 * 1000, forKey: key)
userDefaults?.set(
intervalStartTime.timeIntervalSince1970 * 1000, forKey: keyForMonitoringStarted)

CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)

let shouldExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: true,
activityName: activityName,
callbackName: callbackName,
eventName: "15"
)

let shouldNotExecute = shouldExecuteAction(
skipIfAlreadyTriggeredAfter: nil,
skipIfLargerEventRecordedAfter: nil,
skipIfAlreadyTriggeredWithinMS: nil,
skipIfLargerEventRecordedWithinMS: nil,
neverTriggerBefore: nil,
skipIfLargerEventRecordedSinceIntervalStarted: true,
activityName: activityName,
callbackName: callbackName,
eventName: "5"
)

XCTAssertTrue(shouldExecute)
XCTAssertFalse(shouldNotExecute)
}

func testIsHigherEventNum() {
let isLower = isHigherEvent(eventName: "5", higherThan: "10")
let isEqual = isHigherEvent(eventName: "10", higherThan: "10")
let isHigher = isHigherEvent(eventName: "15", higherThan: "10")

XCTAssertTrue(isHigher)
XCTAssertFalse(isEqual)
XCTAssertFalse(isLower)
}

func testIsHigherEventString() {
let isHigherBecauseString = isHigherEvent(eventName: "prefix_5", higherThan: "prefix_10")
let isEqual = isHigherEvent(eventName: "prefix_10", higherThan: "prefix_10")
let isHigher = isHigherEvent(eventName: "prefix_15", higherThan: "prefix_10")

XCTAssertTrue(isHigher)
XCTAssertFalse(isEqual)
XCTAssertTrue(isHigherBecauseString)
}

func testReplaceText() {
let five = removePrefixIfPresent(
key: "event_with_prefix_5",
prefix: "event_with_prefix_"
)

let empty = removePrefixIfPresent(
key: "event_with_prefix_",
prefix: "event_with_prefix_"
)

let nonmatching = removePrefixIfPresent(
key: "dfgsfgsdfgsdfg",
prefix: "event_with_prefix_"
)

XCTAssertEqual(five, "5")
XCTAssertEqual(empty, "")
XCTAssertEqual(nonmatching, "dfgsfgsdfgsdfg")
}
}
10 changes: 5 additions & 5 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
"version": "1.0.0",
"main": "expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"start": "INTERNALLY_TEST_EXAMPLE_PROJECT=true expo start",
"android": "INTERNALLY_TEST_EXAMPLE_PROJECT=true expo run:android",
"ios": "INTERNALLY_TEST_EXAMPLE_PROJECT=true expo run:ios",
"web": "INTERNALLY_TEST_EXAMPLE_PROJECT=true expo start --web",
"typecheck": "tsc --noEmit",
"prebuild": "rm -rf targets && INTERNALLY_TEST_PREBUILD=true npx expo prebuild --platform=ios --clean"
"prebuild": "rm -rf targets && COPY_TO_TARGET_FOLDER=true INTERNALLY_TEST_EXAMPLE_PROJECT=true npx expo prebuild --platform=ios --clean"
},
"dependencies": {
"expo": "~51.0.39",
Expand Down
Loading
Loading