diff --git a/README.md b/README.md index 382eb0d..3579511 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Let the people using your app connect their EV chargers to Plugchoice, without l ## Features -- **Charger setup**: add a charger, set it up, connect it to Wi-Fi, or reconnect it, guided screen by screen. +- **Charger setup**: add a charger, set it up, or reconnect it (which also changes its network, such as its Wi-Fi), guided screen by screen. - **Many brands, no app update**: new charger brands and fixes reach your users without a new release of your app. - **One backend endpoint**: your server creates a session, scoped to the chargers your user may touch; the SDK fetches it when it needs one. - **Native where it counts**: Wi-Fi hotspots, local network discovery, Bluetooth LE and QR scanning, behind each platform's own permission prompts. diff --git a/android/example/src/main/java/com/plugchoice/example/MainActivity.kt b/android/example/src/main/java/com/plugchoice/example/MainActivity.kt index 67c5d49..4f6cba0 100644 --- a/android/example/src/main/java/com/plugchoice/example/MainActivity.kt +++ b/android/example/src/main/java/com/plugchoice/example/MainActivity.kt @@ -126,7 +126,7 @@ class MainActivity : ComponentActivity() { // Testing aid, like the iOS example's `-autoOpen`: // `adb shell am start -n com.plugchoice.example/.MainActivity --es clientSecret cs_… \ - // --es host 10.0.2.2:5173 --es action network --es chargerId 42 --ez autoOpen true` + // --es host 10.0.2.2:5173 --es action reconnect --es chargerId 42 --ez autoOpen true` if (savedInstanceState == null) { intent.getStringExtra(EXTRA_CLIENT_SECRET)?.let(secretField::setText) intent.getStringExtra(EXTRA_HOST)?.let(hostField::setText) @@ -174,7 +174,6 @@ class MainActivity : ComponentActivity() { val siteId = siteIdField.text.toString().trim().ifEmpty { null } return when (val name = ACTIONS[actionPicker.selectedItemPosition]) { LinkAction.ADD -> LinkAction.addCharger(siteId) - LinkAction.NETWORK -> LinkAction.network(chargerId) LinkAction.SETUP -> LinkAction.setup(chargerId) LinkAction.RECONNECT -> LinkAction.reconnect(chargerId) else -> customActionField.text.toString().trim().takeIf { it.isNotEmpty() && name == CUSTOM } @@ -217,6 +216,6 @@ class MainActivity : ComponentActivity() { const val EXTRA_AUTO_OPEN = "autoOpen" const val CUSTOM = "custom…" - val ACTIONS = listOf(LinkAction.ADD, LinkAction.NETWORK, LinkAction.SETUP, LinkAction.RECONNECT, CUSTOM) + val ACTIONS = listOf(LinkAction.ADD, LinkAction.SETUP, LinkAction.RECONNECT, CUSTOM) } } diff --git a/android/example/src/main/res/values/strings.xml b/android/example/src/main/res/values/strings.xml index 589671b..ef938fb 100644 --- a/android/example/src/main/res/values/strings.xml +++ b/android/example/src/main/res/values/strings.xml @@ -9,7 +9,7 @@ Action Action name Enter an action name - Charger id (network, setup, reconnect) + Charger id (setup, reconnect) 42 Site id (add, optional) Optional diff --git a/android/plugchoice/src/main/java/com/plugchoice/LinkAction.kt b/android/plugchoice/src/main/java/com/plugchoice/LinkAction.kt index f261165..e24da31 100644 --- a/android/plugchoice/src/main/java/com/plugchoice/LinkAction.kt +++ b/android/plugchoice/src/main/java/com/plugchoice/LinkAction.kt @@ -6,16 +6,15 @@ package com.plugchoice * ```kotlin * LinkAction.addCharger() // add a charger * LinkAction.addCharger(siteId) // add a charger at a site - * LinkAction.network(chargerId) // change a charger's network (Wi-Fi) * LinkAction.setup(chargerId) // set a charger up - * LinkAction.reconnect(chargerId) // reconnect a charger to Plugchoice + * LinkAction.reconnect(chargerId) // get a charger reaching Plugchoice again, or change its network * LinkAction.custom("later-action", chargerId) * ``` * * The SDK passes the action to the page without reading it, so an action the page learns later * works through [custom] before it gets a helper here. Empty ids are left out. * - * @property name The action, for example `"add"` or `"network"`. + * @property name The action, for example `"add"` or `"reconnect"`. */ public class LinkAction private constructor( public val name: String, @@ -31,7 +30,6 @@ public class LinkAction private constructor( public companion object { public const val ADD: String = "add" - public const val NETWORK: String = "network" public const val SETUP: String = "setup" public const val RECONNECT: String = "reconnect" @@ -40,15 +38,14 @@ public class LinkAction private constructor( @JvmOverloads public fun addCharger(siteId: String? = null): LinkAction = of(ADD, chargerId = null, siteId = siteId) - /** Change the network (Wi-Fi) of the charger [chargerId]. */ - @JvmStatic - public fun network(chargerId: String): LinkAction = of(NETWORK, chargerId, siteId = null) - /** Set up the charger [chargerId]. */ @JvmStatic public fun setup(chargerId: String): LinkAction = of(SETUP, chargerId, siteId = null) - /** Reconnect the charger [chargerId] to Plugchoice. */ + /** + * Get the charger [chargerId], already on Plugchoice, reaching Plugchoice again. This also + * changes its network (Wi-Fi and the like), so open it from a "Network settings" button too. + */ @JvmStatic public fun reconnect(chargerId: String): LinkAction = of(RECONNECT, chargerId, siteId = null) diff --git a/android/plugchoice/src/main/java/com/plugchoice/Plugchoice.kt b/android/plugchoice/src/main/java/com/plugchoice/Plugchoice.kt index 2333d14..2a85caf 100644 --- a/android/plugchoice/src/main/java/com/plugchoice/Plugchoice.kt +++ b/android/plugchoice/src/main/java/com/plugchoice/Plugchoice.kt @@ -16,7 +16,7 @@ import com.plugchoice.internal.Transports * val plugchoice = Plugchoice(fetchClientSecret = { action -> backend.plugchoiceClientSecret(action) }) * * private val openLink = registerForActivityResult(plugchoice.link.contract()) { result: LinkResult -> … } - * openLink.launch(LinkAction.network(chargerId)) + * openLink.launch(LinkAction.reconnect(chargerId)) * ``` * * @param fetchClientSecret Returns a client secret your server got from @@ -34,7 +34,7 @@ public class Plugchoice( ) { internal val fetchClientSecret: suspend (LinkAction) -> String = fetchClientSecret - /** Link, the onboarding feature: add a charger, change its network, set it up, reconnect it. */ + /** Link, the onboarding feature: add a charger, set it up, reconnect it (or change its network). */ public val link: Link = Link(this) /** diff --git a/android/plugchoice/src/test/java/com/plugchoice/internal/BridgeTest.kt b/android/plugchoice/src/test/java/com/plugchoice/internal/BridgeTest.kt index 4157177..fab4ed3 100644 --- a/android/plugchoice/src/test/java/com/plugchoice/internal/BridgeTest.kt +++ b/android/plugchoice/src/test/java/com/plugchoice/internal/BridgeTest.kt @@ -61,13 +61,13 @@ class BridgeTest { json( "status" to "success", "sessionId" to "ls_1", - "action" to "network", + "action" to "reconnect", "devices" to JSONArray().put(json("type" to "charger", "id" to "42")).put(json("type" to "meter", "id" to "m1")), ), - openedAction = "network", + openedAction = "reconnect", ) assertEquals( - LinkResult(LinkResult.Status.SUCCESS, "network", "ls_1", listOf(Device("charger", "42"), Device("meter", "m1"))), + LinkResult(LinkResult.Status.SUCCESS, "reconnect", "ls_1", listOf(Device("charger", "42"), Device("meter", "m1"))), result, ) } diff --git a/android/plugchoice/src/test/java/com/plugchoice/internal/InstancesTest.kt b/android/plugchoice/src/test/java/com/plugchoice/internal/InstancesTest.kt index 8bdfd3b..8e79d2d 100644 --- a/android/plugchoice/src/test/java/com/plugchoice/internal/InstancesTest.kt +++ b/android/plugchoice/src/test/java/com/plugchoice/internal/InstancesTest.kt @@ -21,13 +21,13 @@ class InstancesTest { seen += action "cs_${action.name}_${action.chargerId}" }) - val action = LinkAction.network("42") + val action = LinkAction.reconnect("42") val id = Instances.register(plugchoice, action) try { val secrets = ClientSecrets(this, Instances.clientSecretSource(id)!!) secrets.prefetch() - assertEquals("cs_network_42", secrets.next()) - assertEquals("cs_network_42", secrets.next()) + assertEquals("cs_reconnect_42", secrets.next()) + assertEquals("cs_reconnect_42", secrets.next()) assertEquals(2, seen.size) seen.forEach { assertSame("the very action launched", action, it) } } finally { diff --git a/android/plugchoice/src/test/java/com/plugchoice/internal/LinkDestinationTest.kt b/android/plugchoice/src/test/java/com/plugchoice/internal/LinkDestinationTest.kt index 78582d2..b501021 100644 --- a/android/plugchoice/src/test/java/com/plugchoice/internal/LinkDestinationTest.kt +++ b/android/plugchoice/src/test/java/com/plugchoice/internal/LinkDestinationTest.kt @@ -18,7 +18,6 @@ class LinkDestinationTest { fun `the URL carries the action and ids in the fragment`() { assertEquals("https://connect.plugchoice.com/#action=add", resolve(LinkAction.addCharger()).url) assertEquals("https://connect.plugchoice.com/#action=add&site_id=site_1", resolve(LinkAction.addCharger("site_1")).url) - assertEquals("https://connect.plugchoice.com/#action=network&charger_id=42", resolve(LinkAction.network("42")).url) assertEquals("https://connect.plugchoice.com/#action=setup&charger_id=42", resolve(LinkAction.setup("42")).url) assertEquals("https://connect.plugchoice.com/#action=reconnect&charger_id=42", resolve(LinkAction.reconnect("42")).url) assertEquals( @@ -42,7 +41,7 @@ class LinkDestinationTest { @Test fun `empty ids are left out`() { assertEquals("action=add", LinkDestination.fragment(LinkAction.addCharger(""))) - assertNull(LinkAction.custom("network", chargerId = "").chargerId) + assertNull(LinkAction.custom("reconnect", chargerId = "").chargerId) } @Test @@ -58,8 +57,8 @@ class LinkDestinationTest { @Test fun `actions compare by value`() { - assertEquals(LinkAction.network("42"), LinkAction.custom("network", "42")) - assertEquals(LinkAction.network("42").hashCode(), LinkAction.custom("network", "42").hashCode()) + assertEquals(LinkAction.reconnect("42"), LinkAction.custom("reconnect", "42")) + assertEquals(LinkAction.reconnect("42").hashCode(), LinkAction.custom("reconnect", "42").hashCode()) assertEquals("add", LinkAction.addCharger().name) } @@ -73,8 +72,8 @@ class LinkDestinationTest { @Test fun `the override replaces scheme, host and port in debug builds`() { val ignored = mutableListOf() - val destination = resolve(LinkAction.network("42"), "http://192.168.1.20:5173", ignored = ignored) - assertEquals("http://192.168.1.20:5173/#action=network&charger_id=42", destination.url) + val destination = resolve(LinkAction.reconnect("42"), "http://192.168.1.20:5173", ignored = ignored) + assertEquals("http://192.168.1.20:5173/#action=reconnect&charger_id=42", destination.url) assertEquals("http://192.168.1.20:5173", destination.origin) assertTrue(ignored.isEmpty()) } diff --git a/bridge/PROTOCOL.md b/bridge/PROTOCOL.md index ce52e24..32baf49 100644 --- a/bridge/PROTOCOL.md +++ b/bridge/PROTOCOL.md @@ -34,7 +34,7 @@ The host app creates one SDK instance with a callback that fetches a **client se Before showing anything the shell: -1. Builds `https://connect.plugchoice.com/#action=`, plus `&charger_id=` and `&site_id=` when given (and not empty), each value percent-encoded (everything but RFC 3986's unreserved characters). The action is an open string (`add`, `network`, `setup`, `reconnect`, or a later one) that the shell passes on without reading it. The URL never holds the secret. +1. Builds `https://connect.plugchoice.com/#action=`, plus `&charger_id=` and `&site_id=` when given (and not empty), each value percent-encoded (everything but RFC 3986's unreserved characters). The action is an open string (`add`, `setup`, `reconnect`, or a later one) that the shell passes on without reading it. The URL never holds the secret. 2. Uses `https://connect.plugchoice.com` as the **only** allowed origin, for the bridge and for main-frame navigation (an explicit `:443` is the same origin). 3. **Debug override**: `options.hostOverride` (for example `http://192.168.1.20:5173`, a local copy of the page) replaces the scheme, host and port and becomes the only allowed origin. It must be `http` or `https`, a host and an optional port, with at most a trailing `/`. It is honoured only in debug builds of the host app (iOS: `#if DEBUG`, as the package builds from source with the app's configuration; Android: the app's `FLAG_DEBUGGABLE`), and otherwise ignored with a log line, as is a malformed one. 4. Calls the host's callback once with the action, in parallel with loading the page, and keeps the answer for the page's first `auth.clientSecret` (§7). diff --git a/ios/Example/Example/ContentView.swift b/ios/Example/Example/ContentView.swift index 396ee8f..dcbee5a 100644 --- a/ios/Example/Example/ContentView.swift +++ b/ios/Example/Example/ContentView.swift @@ -15,7 +15,7 @@ struct ContentView: View { } enum ActionKind: String, CaseIterable, Identifiable { - case add, network, setup, reconnect, custom + case add, setup, reconnect, custom var id: String { rawValue } } @@ -180,16 +180,12 @@ struct ContentView: View { return nil } return .custom(name, chargerId: chargerId.isEmpty ? nil : chargerId) - case .network, .setup, .reconnect: + case .setup, .reconnect: guard !chargerId.isEmpty else { inputError = "Enter the charger's id." return nil } - switch actionKind { - case .network: return .network(chargerId: chargerId) - case .setup: return .setup(chargerId: chargerId) - default: return .reconnect(chargerId: chargerId) - } + return actionKind == .setup ? .setup(chargerId: chargerId) : .reconnect(chargerId: chargerId) } } diff --git a/ios/Sources/PlugchoiceSDK/Plugchoice.swift b/ios/Sources/PlugchoiceSDK/Plugchoice.swift index a610441..7e4b936 100644 --- a/ios/Sources/PlugchoiceSDK/Plugchoice.swift +++ b/ios/Sources/PlugchoiceSDK/Plugchoice.swift @@ -94,7 +94,7 @@ public final class Plugchoice: Sendable { /// /// - Parameters: /// - action: What to do, such as `.addCharger()` or - /// `.network(chargerId:)`. + /// `.reconnect(chargerId:)`. /// - presenter: The view controller to present from. /// - completion: Called with the result. @MainActor @@ -120,7 +120,7 @@ public final class Plugchoice: Sendable { /// open string; the hosted page decides what it means. `fetchClientSecret` /// gets it too, so your server can scope the client secret. public struct LinkAction: Sendable, Equatable, Hashable { - /// `add`, `network`, `setup`, `reconnect`, or a later one. + /// `add`, `setup`, `reconnect`, or a later one. public let action: String public let chargerId: String? public let siteId: String? @@ -136,17 +136,14 @@ public struct LinkAction: Sendable, Equatable, Hashable { LinkAction(action: "add", siteId: siteId) } - /// Change a charger's network connection. - public static func network(chargerId: String) -> LinkAction { - LinkAction(action: "network", chargerId: chargerId) - } - /// Set up a charger's electrical settings. public static func setup(chargerId: String) -> LinkAction { LinkAction(action: "setup", chargerId: chargerId) } - /// Reconnect a charger to Plugchoice. + /// Get a charger already on Plugchoice reaching Plugchoice again. This + /// also changes its network (Wi-Fi and the like), so open it from a + /// "Network settings" button too. public static func reconnect(chargerId: String) -> LinkAction { LinkAction(action: "reconnect", chargerId: chargerId) } diff --git a/ios/Tests/PlugchoiceSDKTests/BridgeTests.swift b/ios/Tests/PlugchoiceSDKTests/BridgeTests.swift index 79b8e78..49c6604 100644 --- a/ios/Tests/PlugchoiceSDKTests/BridgeTests.swift +++ b/ios/Tests/PlugchoiceSDKTests/BridgeTests.swift @@ -61,12 +61,12 @@ final class BridgeTests: XCTestCase { let close = try close([ "status": "success", "sessionId": "s1", - "action": "network", + "action": "reconnect", "devices": [["type": "charger", "id": "c1"], ["type": "meter", "id": "m1"]], ]) XCTAssertEqual(close, Bridge.SessionClose( status: .success, - action: "network", + action: "reconnect", sessionId: "s1", devices: [Device(type: "charger", id: "c1"), Device(type: "meter", id: "m1")], error: nil diff --git a/ios/Tests/PlugchoiceSDKTests/ClientSecretsTests.swift b/ios/Tests/PlugchoiceSDKTests/ClientSecretsTests.swift index 2363cd1..6da73f7 100644 --- a/ios/Tests/PlugchoiceSDKTests/ClientSecretsTests.swift +++ b/ios/Tests/PlugchoiceSDKTests/ClientSecretsTests.swift @@ -43,7 +43,7 @@ final class ClientSecretsTests: XCTestCase { private struct Failure: Error {} - private let opened = LinkAction.network(chargerId: "c-1") + private let opened = LinkAction.reconnect(chargerId: "c-1") private func secrets(_ backend: Backend, timeout: TimeInterval = ClientSecrets.defaultTimeout) -> ClientSecrets { ClientSecrets(action: opened, fetch: { try await backend.fetch($0) }, timeout: timeout) @@ -84,7 +84,7 @@ final class ClientSecretsTests: XCTestCase { XCTAssertEqual([first, second, third], ["cs_test_1", "cs_test_2", "cs_test_3"]) XCTAssertEqual(backend.calls, 3) XCTAssertEqual(backend.actions, [opened, opened, opened], "refreshes get the same action") - XCTAssertEqual(backend.actions.first?.action, "network") + XCTAssertEqual(backend.actions.first?.action, "reconnect") XCTAssertEqual(backend.actions.first?.chargerId, "c-1") } diff --git a/ios/Tests/PlugchoiceSDKTests/LinkTargetTests.swift b/ios/Tests/PlugchoiceSDKTests/LinkTargetTests.swift index 7203b80..325ef80 100644 --- a/ios/Tests/PlugchoiceSDKTests/LinkTargetTests.swift +++ b/ios/Tests/PlugchoiceSDKTests/LinkTargetTests.swift @@ -16,7 +16,6 @@ final class LinkTargetTests: XCTestCase { func testEveryActionAndItsIds() { let cases: [(LinkAction, String)] = [ (.addCharger(siteId: "site-1"), "#action=add&site_id=site-1"), - (.network(chargerId: "c-1"), "#action=network&charger_id=c-1"), (.setup(chargerId: "c-1"), "#action=setup&charger_id=c-1"), (.reconnect(chargerId: "c-1"), "#action=reconnect&charger_id=c-1"), (.custom("diagnose", chargerId: "c-1"), "#action=diagnose&charger_id=c-1"), @@ -54,8 +53,8 @@ final class LinkTargetTests: XCTestCase { // MARK: - hostOverride func testOverrideReplacesSchemeHostAndPortInDebug() { - let target = LinkTarget(action: .network(chargerId: "c-1"), hostOverride: "http://192.168.1.20:5173", honourOverride: true) - XCTAssertEqual(target.url.absoluteString, "http://192.168.1.20:5173/#action=network&charger_id=c-1") + let target = LinkTarget(action: .reconnect(chargerId: "c-1"), hostOverride: "http://192.168.1.20:5173", honourOverride: true) + XCTAssertEqual(target.url.absoluteString, "http://192.168.1.20:5173/#action=reconnect&charger_id=c-1") XCTAssertEqual(target.allowedOrigin.description, "http://192.168.1.20:5173") let allowList = OriginAllowList([target.allowedOrigin]) XCTAssertTrue(allowList.allows(url: target.url)) @@ -95,12 +94,11 @@ final class LinkTargetTests: XCTestCase { func testActions() { XCTAssertEqual(LinkAction.addCharger(), LinkAction(action: "add")) XCTAssertEqual(LinkAction.addCharger(siteId: "s"), LinkAction(action: "add", siteId: "s")) - XCTAssertEqual(LinkAction.network(chargerId: "c"), LinkAction(action: "network", chargerId: "c")) XCTAssertEqual(LinkAction.setup(chargerId: "c"), LinkAction(action: "setup", chargerId: "c")) XCTAssertEqual(LinkAction.reconnect(chargerId: "c"), LinkAction(action: "reconnect", chargerId: "c")) XCTAssertEqual(LinkAction.custom("x", chargerId: "c"), LinkAction(action: "x", chargerId: "c")) - let network = LinkAction.network(chargerId: "c") - XCTAssertEqual([network.action, network.chargerId, network.siteId], ["network", "c", nil]) + let reconnect = LinkAction.reconnect(chargerId: "c") + XCTAssertEqual([reconnect.action, reconnect.chargerId, reconnect.siteId], ["reconnect", "c", nil]) } func testTransportsInTheTestHost() { @@ -125,9 +123,9 @@ final class LinkTargetTests: XCTestCase { func testAScreenClosedByTheHostReportsTheOpenedAction() throws { let plugchoice = Plugchoice(fetchClientSecret: { _ in "cs_test_secret" }) var result: LinkResult? - let controller = plugchoice.link.makeController(.network(chargerId: "c-1")) { result = $0 } + let controller = plugchoice.link.makeController(.reconnect(chargerId: "c-1")) { result = $0 } controller.closeForHost() - XCTAssertEqual(result, LinkResult(status: .cancelled, action: "network")) + XCTAssertEqual(result, LinkResult(status: .cancelled, action: "reconnect")) } @MainActor @@ -137,8 +135,8 @@ final class LinkTargetTests: XCTestCase { LinkResult(status: .cancelled, action: "add", sessionId: nil, devices: [], error: nil) ) XCTAssertEqual( - LinkViewController.ownResult(action: "network", loadErrorMessage: "offline"), - LinkResult(status: .error, action: "network", error: LinkError(code: "pageLoadFailed", message: "offline")) + LinkViewController.ownResult(action: "reconnect", loadErrorMessage: "offline"), + LinkResult(status: .error, action: "reconnect", error: LinkError(code: "pageLoadFailed", message: "offline")) ) } diff --git a/react-native/__tests__/clientSecret.test.ts b/react-native/__tests__/clientSecret.test.ts index f1ec993..bb628e6 100644 --- a/react-native/__tests__/clientSecret.test.ts +++ b/react-native/__tests__/clientSecret.test.ts @@ -45,10 +45,10 @@ describe('the client-secret bridge', () => { const { configurePlugchoice } = await load(); configurePlugchoice({ fetchClientSecret }); - const answer = await fake.requestClientSecret({ action: 'network', chargerId: 'ch_1' }); + const answer = await fake.requestClientSecret({ action: 'reconnect', chargerId: 'ch_1' }); expect(answer).toEqual({ secret: 'cs_test_123' }); - expect(fetchClientSecret).toHaveBeenCalledWith({ action: 'network', chargerId: 'ch_1' }); + expect(fetchClientSecret).toHaveBeenCalledWith({ action: 'reconnect', chargerId: 'ch_1' }); expect(fake.module.provideClientSecret).toHaveBeenCalledWith('request-1', 'cs_test_123'); expect(fake.module.rejectClientSecret).not.toHaveBeenCalled(); }); @@ -217,17 +217,17 @@ describe('the client-secret bridge', () => { const { configurePlugchoice, openLink } = await load(); configurePlugchoice({ fetchClientSecret }); - const result = await openLink({ action: 'network', chargerId: 'ch_1' }); + const result = await openLink({ action: 'reconnect', chargerId: 'ch_1' }); expect(result).toEqual({ status: 'success', - action: 'network', + action: 'reconnect', sessionId: 'ls_1', devices: [{ type: 'charger', id: 'ch_1' }], }); expect(fetchClientSecret.mock.calls).toEqual([ - [{ action: 'network', chargerId: 'ch_1' }], - [{ action: 'network', chargerId: 'ch_1' }], + [{ action: 'reconnect', chargerId: 'ch_1' }], + [{ action: 'reconnect', chargerId: 'ch_1' }], ]); }); diff --git a/react-native/__tests__/openLink.test.ts b/react-native/__tests__/openLink.test.ts index f6b0fc0..2711fbc 100644 --- a/react-native/__tests__/openLink.test.ts +++ b/react-native/__tests__/openLink.test.ts @@ -47,11 +47,11 @@ describe('openLink', () => { }); it('passes chargerId and siteId, and leaves out empty or missing ids', async () => { - fake.module.openLink.mockResolvedValue({ status: 'cancelled', action: 'network', devices: [] }); + fake.module.openLink.mockResolvedValue({ status: 'cancelled', action: 'reconnect', devices: [] }); const { openLink } = await configured(); - await openLink({ action: 'network', chargerId: 'ch_1' }); - expect(fake.module.openLink).toHaveBeenLastCalledWith({ action: 'network', chargerId: 'ch_1' }, {}); + await openLink({ action: 'reconnect', chargerId: 'ch_1' }); + expect(fake.module.openLink).toHaveBeenLastCalledWith({ action: 'reconnect', chargerId: 'ch_1' }, {}); await openLink({ action: 'add', siteId: 'site_1', chargerId: '' }); expect(fake.module.openLink).toHaveBeenLastCalledWith({ action: 'add', siteId: 'site_1' }, {}); @@ -107,9 +107,9 @@ describe('openLink', () => { fake.module.openLink.mockResolvedValue({ status: 'cancelled', devices: [] }); const { openLink } = await configured(); - await expect(openLink({ action: 'network', chargerId: 'ch_1' })).resolves.toEqual({ + await expect(openLink({ action: 'reconnect', chargerId: 'ch_1' })).resolves.toEqual({ status: 'cancelled', - action: 'network', + action: 'reconnect', sessionId: null, devices: [], }); @@ -169,7 +169,7 @@ describe('openLink', () => { const { openLink } = await configured(); const firstCall = openLink({ action: 'add' }); - await expect(openLink({ action: 'network', chargerId: 'ch_1' })).rejects.toMatchObject({ + await expect(openLink({ action: 'reconnect', chargerId: 'ch_1' })).rejects.toMatchObject({ code: 'ERR_LINK_ALREADY_OPEN', }); expect(fake.module.openLink).toHaveBeenCalledTimes(1); @@ -179,11 +179,11 @@ describe('openLink', () => { fake.module.openLink.mockResolvedValueOnce({ status: 'success', - action: 'network', + action: 'reconnect', sessionId: 'ls_2', devices: [{ type: 'charger', id: 'ch_1' }], }); - await expect(openLink({ action: 'network', chargerId: 'ch_1' })).resolves.toMatchObject({ + await expect(openLink({ action: 'reconnect', chargerId: 'ch_1' })).resolves.toMatchObject({ status: 'success', sessionId: 'ls_2', }); @@ -241,7 +241,7 @@ describe('openLink', () => { { action: '' }, { action: ' ' }, { action: 42 }, - { action: 'network', chargerId: 42 }, + { action: 'reconnect', chargerId: 42 }, { action: 'add', siteId: {} }, ]) { await expect(untyped(action)).rejects.toBeInstanceOf(TypeError); diff --git a/react-native/src/openLink.ts b/react-native/src/openLink.ts index 54f9e6f..b8a47b0 100644 --- a/react-native/src/openLink.ts +++ b/react-native/src/openLink.ts @@ -12,7 +12,7 @@ let isOpen = false; * {@link configurePlugchoice} with this action. * * ```ts - * const result = await openLink({ action: 'network', chargerId }); + * const result = await openLink({ action: 'reconnect', chargerId }); * ``` * * Throws a `TypeError` for an action that isn't `{ action: string, chargerId?, siteId? }`. Rejects @@ -54,7 +54,7 @@ export async function openLink(action: LinkAction): Promise { /** The action as the native module takes it, or a `TypeError`. */ function checkedAction(value: unknown): NativeLinkAction { if (!isRecord(value)) { - throw new TypeError("openLink: pass an action such as { action: 'add' } or { action: 'network', chargerId }"); + throw new TypeError("openLink: pass an action such as { action: 'add' } or { action: 'reconnect', chargerId }"); } if (typeof value.action !== 'string' || value.action.trim() === '') { throw new TypeError('openLink: action.action must be a non-empty string'); diff --git a/react-native/src/types.ts b/react-native/src/types.ts index 9dcbbc4..e9a3201 100644 --- a/react-native/src/types.ts +++ b/react-native/src/types.ts @@ -4,8 +4,8 @@ * names it. */ export type LinkAction = { - /** `add`, `network`, `setup`, `reconnect`, or a later one. */ - action: 'add' | 'network' | 'setup' | 'reconnect' | (string & {}); + /** `add`, `setup`, `reconnect` (which also changes a charger's network), or a later one. */ + action: 'add' | 'setup' | 'reconnect' | (string & {}); /** The charger. Every action but `add` needs one. */ chargerId?: string; /** `add` only: the site to add the charger to. */