Skip to content

Commit

Permalink
Merge pull request #53 from wkarl/feature/local-storage-namespaces-ba…
Browse files Browse the repository at this point in the history
…ckport

Backport of local storage namespace feature with an option to disable namespaces for backward compatibility
  • Loading branch information
Eridana authored Jun 14, 2023
2 parents 352f4da + 47b902b commit 8023d5f
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 23 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Evaluate JavaScript code and map values, objects and functions between Kotlin/Java and JavaScript on Android.

```kotlin
val jsBridge = JsBridge(JsBridgeConfig.bareConfig())
val jsBridge = JsBridge(JsBridgeConfig.bareConfig(), context, "namespace")
val msg: String = jsBridge.evaluate("'Hello world!'.toUpperCase()")
println(msg) // HELLO WORLD!
```
Expand Down Expand Up @@ -304,6 +304,9 @@ Other network clients are not tested but should work as well (polyfill for
Support for ES6 promises (Duktape: via polyfill, QuickJS: built-in). Pending jobs are triggered
after each evaluation.

- **LocalStorage:**<br/>
Support for browser-like local storage.

- **JS Debugger:**<br/>
JS debugger support (Duktape only via Visual Studio Code plugin)

Expand Down Expand Up @@ -401,7 +404,7 @@ val nativeApi = object: NativeApi {
Bridging JavaScript and Kotlin:
```kotlin
val jsBridge = JsBridge(JsBridgeConfig.standardConfig())
val jsBridge = JsBridge(JsBridgeConfig.standardConfig(), context, "namespace")
jsBridge.evaluateLocalFileUnsync(context, "js/api.js")

// JS "proxy" to native API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void triggerCallback(TestAidlCallback cb) throws RemoteException {
// ---

private JsBridge createAndSetUpJsBridge() {
JsBridge jsBridge = new JsBridge(JsBridgeConfig.standardConfig(), context);
JsBridge jsBridge = new JsBridge(JsBridgeConfig.standardConfig(), context, "test_namespace");
this.jsBridge = jsBridge;
return jsBridge;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ interface JsExpectationsNativeApi : JsToNativeInterface {
fun addExpectation(name: String, value: JsValue)
}

private const val NAMESPACE = "test_namespace"

class JsBridgeTest {
private var jsBridge: JsBridge? = null
private val context: Context = InstrumentationRegistry.getInstrumentation().context
Expand Down Expand Up @@ -1180,7 +1182,7 @@ class JsBridgeTest {
val config = JsBridgeConfig.standardConfig().apply {
xhrConfig.okHttpClient = okHttpClient
}
val subject = JsBridge(config, context)
val subject = JsBridge(config, context, NAMESPACE)

val jsExpectations = JsExpectations()
val jsExpectationsJsValue = JsValue.fromNativeObject(subject, jsExpectations)
Expand Down Expand Up @@ -1551,7 +1553,7 @@ class JsBridgeTest {
val config = JsBridgeConfig.standardConfig().apply {
xhrConfig.okHttpClient = okHttpClient
}
val subject = JsBridge(config, context)
val subject = JsBridge(config, context, NAMESPACE)

val jsExpectations = JsExpectations()
val jsExpectationsJsValue = JsValue.fromNativeObject(subject, jsExpectations)
Expand Down Expand Up @@ -2591,7 +2593,7 @@ class JsBridgeTest {
messages.add(priority to message)
}
}
val subject = JsBridge(config, context)
val subject = JsBridge(config, context, NAMESPACE)
jsBridge = subject

// WHEN
Expand Down Expand Up @@ -2629,7 +2631,7 @@ class JsBridgeTest {
messages.add(priority to message)
}
}
val subject = JsBridge(config, context)
val subject = JsBridge(config, context, NAMESPACE)
jsBridge = subject

// WHEN
Expand Down Expand Up @@ -2667,7 +2669,7 @@ class JsBridgeTest {
hasMessage = true
}
}
val subject = JsBridge(config, context)
val subject = JsBridge(config, context, NAMESPACE)
jsBridge = subject

// WHEN
Expand Down Expand Up @@ -2883,11 +2885,11 @@ class JsBridgeTest {
val subject = createAndSetUpJsBridge()

// WHEN
subject.evaluateBlocking<Unit>("""localStorage.setItem("foo", "bar");""")
val result = subject.evaluateBlocking<String>("""localStorage.getItem("foo");""")
subject.evaluateBlocking<Unit>("""localStorage.setItem("key", "value");""")
val result = subject.evaluateBlocking<String>("""localStorage.getItem("key");""")

// THEN
assertEquals(result, "bar")
assertEquals("value", result)
assertTrue(errors.isEmpty())

// GIVEN
Expand All @@ -2903,6 +2905,27 @@ class JsBridgeTest {
assertTrue(errors.isEmpty())
}

@Test
fun testLocalStorageNamespaces() {
// GIVEN
val subject1 = createAndSetUpJsBridge()
val subject2 = createAndSetUpJsBridge(namespace = "other_namespace")

// WHEN
subject1.evaluateBlocking<Unit>("""localStorage.setItem("key", "value");""")
subject1.evaluateBlocking<Unit>("""localStorage.setItem("key2", "value");""")
val result1 = subject1.evaluateBlocking<String>("""localStorage.getItem("key");""")
subject2.evaluateBlocking<Unit>("""localStorage.setItem("key", "TEST");""")
val result2 = subject2.evaluateBlocking<String>("""localStorage.getItem("key");""")
val result3 = subject2.evaluateBlocking<String>("""localStorage.getItem("key2");""")

// THEN
assertEquals("value", result1)
assertEquals("TEST", result2)
assertNull(result3)
assertTrue(errors.isEmpty())
}

// JsExpectations
// ---

Expand Down Expand Up @@ -2947,10 +2970,11 @@ class JsBridgeTest {
private fun createAndSetUpJsBridge(
config: JsBridgeConfig = JsBridgeConfig.standardConfig().apply {
xhrConfig.okHttpClient = okHttpClient
}
},
namespace: String = NAMESPACE,
): JsBridge {

return JsBridge(config, context).also { jsBridge ->
return JsBridge(config, context, namespace).also { jsBridge ->
this@JsBridgeTest.jsBridge = jsBridge

jsBridge.registerErrorListener(createErrorListener())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ReadmeTest {

@Before
fun setUp() {
jsBridge = JsBridge(JsBridgeConfig.standardConfig(), context)
jsBridge = JsBridge(JsBridgeConfig.standardConfig(), context,"test_namespace")
}

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,13 @@ import java.lang.reflect.Proxy
* Note: all the public methods are asynchronous and will not block the caller threads. They
* can be safely called in a "synchronous" way. though, because their executions are guaranteed
* to be performed sequentially (via an internal queue).
*
* @param config JsBridge configuration
* @param context Context needed for built in implementation of local storage
* @param namespace arbitrary string for separation of local storage between multiple JsBridge instances
*/
class JsBridge
constructor(config: JsBridgeConfig, context: Context) : CoroutineScope {
constructor(config: JsBridgeConfig, context: Context, namespace: String) : CoroutineScope {

companion object {
private var isLibraryLoaded = false
Expand Down Expand Up @@ -158,7 +162,7 @@ class JsBridge
if (config.xhrConfig.enabled)
xhrExtension = XMLHttpRequestExtension(this@JsBridge, config.xhrConfig)
if (config.localStorageConfig.enabled)
localStorageExtension = LocalStorageExtension(this@JsBridge, config.localStorageConfig, context.applicationContext)
localStorageExtension = LocalStorageExtension(this@JsBridge, config.localStorageConfig, context.applicationContext, namespace)
config.jvmConfig.customClassLoader?.let { customClassLoader = it }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ private constructor() {
class LocalStorageConfig {
var enabled: Boolean = false
var useDefaultLocalStorage: Boolean = true

/**
* Only disable namespaces if a particular instance of JsBridge requires access to local
* storage key/value pairs that were saved with a previous version of the library.
*
* You should try to avoid using multiple unrelated instances of JsBridge without namespaces
* or with an identical namespace. An exception would be if you want to explicitly share data
* between instances and the possibility of key name collisions is not an issue.
*/
var useNamespaces: Boolean = true
}

class JvmConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ interface LocalStorageInteface : JsToNativeInterface {
fun clear()
}

class LocalStorage(context: Context) : LocalStorageInteface {
class LocalStorage(context: Context, namespace: String?) : LocalStorageInteface {

private val localStoragePreferences = context.getSharedPreferences(
context.applicationInfo.packageName + ".LOCAL_STORAGE_PREFERENCE_FILE_KEY",
namespace?.let { "${it.takeIf { it.isNotEmpty() } ?: "default"}.LOCAL_STORAGE_PREFERENCES" }
?: "${context.applicationInfo.packageName}.LOCAL_STORAGE_PREFERENCE_FILE_KEY",
Context.MODE_PRIVATE
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ import de.prosiebensat1digital.oasisjsbridge.JsBridgeConfig
import de.prosiebensat1digital.oasisjsbridge.JsValue

internal class LocalStorageExtension(
private val jsBridge: JsBridge,
val config: JsBridgeConfig.LocalStorageConfig,
jsBridge: JsBridge,
config: JsBridgeConfig.LocalStorageConfig,
context: Context,
namespace: String,
) {

init {
if (config.useDefaultLocalStorage) {
val localStorageJsValue: JsValue
val localStorage: LocalStorageInteface = LocalStorage(context)
localStorageJsValue = JsValue.fromNativeObject(jsBridge, localStorage)
val localStorage: LocalStorageInteface = LocalStorage(context, namespace.takeIf { config.useNamespaces })
val localStorageJsValue = JsValue.fromNativeObject(jsBridge, localStorage)
localStorageJsValue.assignToGlobal("localStorage")
}
}
Expand Down

0 comments on commit 8023d5f

Please sign in to comment.