Skip to content

Commit

Permalink
Merge pull request #51 from wkarl/feature/local-storage-namespaces
Browse files Browse the repository at this point in the history
Add mandatory namespace constructor argument to prevent unexpected behaviour when two instances access the same key
  • Loading branch information
Eridana authored May 30, 2023
2 parents 41d5127 + c88eaa5 commit c1308f5
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 21 deletions.
4 changes: 2 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 @@ -404,7 +404,7 @@ val javaApi = object: JavaApi {
Bridging JavaScript and Kotlin:
```kotlin
val jsBridge = JsBridge(JsBridgeConfig.standardConfig())
val jsBridge = JsBridge(JsBridgeConfig.standardConfig(), context, "namespace")
jsBridge.evaluateLocalFile(context, "js/api.js")

// JS "proxy" to Java API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public void testRegisterJavaToJs() {
// ---

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 JsExpectationsJavaApi : JsToJavaInterface {
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 @@ -1236,7 +1238,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.createJsToJavaProxy(subject, jsExpectations)
Expand Down Expand Up @@ -1607,7 +1609,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.createJsToJavaProxy(subject, jsExpectations)
Expand Down Expand Up @@ -2737,7 +2739,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 @@ -2775,7 +2777,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 @@ -2813,7 +2815,7 @@ class JsBridgeTest {
hasMessage = true
}
}
val subject = JsBridge(config, context)
val subject = JsBridge(config, context, NAMESPACE)
jsBridge = subject

// WHEN
Expand All @@ -2840,11 +2842,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 @@ -2860,7 +2862,26 @@ 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())
}
data class EmbeddedObject(val a: Int, val b: String)

@Test
Expand Down Expand Up @@ -3003,10 +3024,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 @@ -56,9 +56,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 @@ -157,7 +161,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 @@ -13,10 +13,10 @@ interface LocalStorageInteface : JsToJavaInterface {
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.takeIf { it.isNotEmpty() } ?: "default"}.LOCAL_STORAGE_PREFERENCES",
Context.MODE_PRIVATE
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ 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 localStorage: LocalStorageInteface = LocalStorage(context)
val localStorage: LocalStorageInteface = LocalStorage(context, namespace)
val localStorageJsValue = JsValue.createJsToJavaProxy(jsBridge, localStorage)
localStorageJsValue.assignToGlobal("localStorage")
}
Expand Down

0 comments on commit c1308f5

Please sign in to comment.