瀏覽代碼

Simplify native app by changing the message protocol

jherve 1 年之前
父節點
當前提交
27ee49afb1

+ 4 - 4
make_prod.patch

@@ -12,15 +12,15 @@ index f3c3aca..e365fca 100755
  EXTENSION_BIN=${EXTENSION_BACKEND_PATH}/run.sh
  EXTENSION_BIN=${EXTENSION_BACKEND_PATH}/run.sh
  
  
 diff --git a/src/Background.purs b/src/Background.purs
 diff --git a/src/Background.purs b/src/Background.purs
-index 885ac8e..a742a65 100644
+index b73fd21..86d7396 100644
 --- a/src/Background.purs
 --- a/src/Background.purs
 +++ b/src/Background.purs
 +++ b/src/Background.purs
-@@ -24,7 +24,7 @@ import LinkedIn.Output.Types (Output(..))
+@@ -28,7 +28,7 @@ import Web.URL as URL
  main :: Effect Unit
  main :: Effect Unit
  main = do
  main = do
    log "[bg] starting up"
    log "[bg] starting up"
 -  port <- connectToNativeApplication "job_search_background"
 -  port <- connectToNativeApplication "job_search_background"
 +  port <- connectToNativeApplication "job_search_background.prod"
 +  port <- connectToNativeApplication "job_search_background.prod"
-   onNativeMessageAddListener port nativeMessageHandler
+   -- TODO: This is redundant to send "port" to both addListener and the handler
+   onNativeMessageAddListener port $ nativeMessageHandler port
    onNativeDisconnectAddListener port \p -> log $ "disconnected from native port " <> p.name <> " (" <> p.error <> ")"
    onNativeDisconnectAddListener port \p -> log $ "disconnected from native port " <> p.name <> " (" <> p.error <> ")"
- 

+ 2 - 14
native/src/job_search/job_storage.py

@@ -41,7 +41,7 @@ class Flexibility(Enum):
 
 
 def convert_to_parse_result(url):
 def convert_to_parse_result(url):
     if isinstance(url, str):
     if isinstance(url, str):
-        return urlparse(url)._replace(query=None)
+        return urlparse(url)
     elif isinstance(url, ParseResult):
     elif isinstance(url, ParseResult):
         return url
         return url
 
 
@@ -51,7 +51,7 @@ def convert_to_bool(s: str) -> bool:
 
 
 @dataclass
 @dataclass
 class JobOffer:
 class JobOffer:
-    id: str = field(init=False)
+    id: str
     url: str = field(repr=False)
     url: str = field(repr=False)
     title: str
     title: str
     company: str
     company: str
@@ -89,10 +89,6 @@ class JobOffer:
             self._alternate_url = convert_to_parse_result(self.alternate_url)
             self._alternate_url = convert_to_parse_result(self.alternate_url)
             self.alternate_url = self._alternate_url.geturl()
             self.alternate_url = self._alternate_url.geturl()
 
 
-        if self.origin == JobOfferOrigin.LINKED_IN:
-            path = Path(self._url.path)
-            self.id = f"linked_in_{path.name}"
-
     def to_storage(self):
     def to_storage(self):
         return {
         return {
             k: v
             k: v
@@ -102,8 +98,6 @@ class JobOffer:
 
 
     @staticmethod
     @staticmethod
     def from_storage(dict: dict):
     def from_storage(dict: dict):
-        id = dict.pop("id")
-
         for field, converter in [
         for field, converter in [
             ("origin", JobOfferOrigin),
             ("origin", JobOfferOrigin),
             ("application_process", ApplicationProcess),
             ("application_process", ApplicationProcess),
@@ -183,12 +177,6 @@ class JobStorage:
     def read_all(self) -> dict[str, JobOffer]:
     def read_all(self) -> dict[str, JobOffer]:
         return {r["id"]: JobOffer.from_storage(r) for r in self.select_all("job_offer")}
         return {r["id"]: JobOffer.from_storage(r) for r in self.select_all("job_offer")}
 
 
-    def add_job(self, offer: JobOffer):
-        self.insert(offer)
-
-    def insert(self, offer: JobOffer):
-        self.insert_record("job_offer", offer.to_storage())
-
     def insert_record(self, type_, fields):
     def insert_record(self, type_, fields):
         cmd_args = [
         cmd_args = [
             arg
             arg

+ 34 - 35
native/src/job_search/messages.py

@@ -27,17 +27,23 @@ class BackgroundScriptMessage(Message):
 
 
         try:
         try:
             tag = message.pop("tag")
             tag = message.pop("tag")
-            [values] = message.pop("values")
+            match message.pop("values"):
+                case []:
+                    values = {}
+                case [v]:
+                    values = v
         except KeyError:
         except KeyError:
             raise ValueError("messages from background script should contain a tag and values")
             raise ValueError("messages from background script should contain a tag and values")
 
 
         values = {to_snake_case(k): v for k, v in values.items()}
         values = {to_snake_case(k): v for k, v in values.items()}
 
 
         match tag:
         match tag:
-            case "NativeMessageVisitedJobPage":
-                return VisitedLinkedInJobPageMessage(**values)
             case "NativeMessageInitialConfiguration":
             case "NativeMessageInitialConfiguration":
                 return InitialConfigurationMessage(**values)
                 return InitialConfigurationMessage(**values)
+            case "NativeMessageAddJob":
+                return AddJobMessage(**values)
+            case "NativeMessageListJobsRequest":
+                return ListJobsRequestMessage(**values)
             case _:
             case _:
                 raise ValueError(f"Got message with unknown tag {tag}")
                 raise ValueError(f"Got message with unknown tag {tag}")
 
 
@@ -48,40 +54,28 @@ class NativeMessage(Message):
 
 
 
 
 @dataclass
 @dataclass
-class VisitedLinkedInJobPageMessage(BackgroundScriptMessage):
+class AddJobMessage(BackgroundScriptMessage):
+    id: str
+    origin: str
+    title: str
     url: str
     url: str
-    job_title: str
-    page_title: str
     company: str
     company: str
-    location: str
-    has_simplified_process: bool
-    company_url: str
-    flexibility: Optional[str] = None
+    location: Optional[str] = None
     company_domain: Optional[str] = None
     company_domain: Optional[str] = None
+    company_url: Optional[str] = None
+    flexibility: Optional[Flexibility] = None
+    application_process: Optional[ApplicationProcess] = None
+    application_considered: Optional[bool] = None
+    application_date: Optional[str] = None
+    application_rejection_date: Optional[str] = None
 
 
-    def extract_job_offer(self):
-        application_process = (
-            ApplicationProcess.LINKED_IN_SIMPLIFIED
-            if self.has_simplified_process
-            else ApplicationProcess.REGULAR
-        )
-
-        if isinstance(self.flexibility, str):
-            flexibility = Flexibility(self.flexibility)
-        elif self.flexibility is None:
-            flexibility = None
-
-        return JobOffer(
-            url=self.url,
-            title=self.job_title,
-            company=self.company,
-            origin=JobOfferOrigin.LINKED_IN,
-            application_process=application_process,
-            location=self.location,
-            company_domain=self.company_domain,
-            company_url=self.company_url,
-            flexibility=flexibility,
-        )
+    def serialize(self):
+        return {"tag": "NativeMessageAddJob", "values": [asdict(self)]}
+
+
+@dataclass
+class ListJobsRequestMessage(BackgroundScriptMessage):
+    pass
 
 
 
 
 @dataclass
 @dataclass
@@ -97,14 +91,19 @@ class JobOfferListMessage(NativeMessage):
         return {"tag": "NativeMessageJobOfferList", "values": [self.job_offers]}
         return {"tag": "NativeMessageJobOfferList", "values": [self.job_offers]}
 
 
 
 
+@dataclass
+class StorageReadyMessage(NativeMessage):
+    def serialize(self):
+        return {"tag": "NativeMessageStorageReady", "values": []}
+
+
 @dataclass
 @dataclass
 class JobAddedMessage(NativeMessage):
 class JobAddedMessage(NativeMessage):
-    job: JobOffer
+    job_id: str
 
 
     def serialize(self):
     def serialize(self):
         return {"tag": "NativeMessageJobAdded", "values": [asdict(self)]}
         return {"tag": "NativeMessageJobAdded", "values": [asdict(self)]}
 
 
-
 @dataclass
 @dataclass
 class JobAlreadyExistsMessage(NativeMessage):
 class JobAlreadyExistsMessage(NativeMessage):
     job_id: str
     job_id: str

+ 13 - 11
native/src/job_search/writer.py

@@ -1,12 +1,15 @@
 import sys
 import sys
 import traceback
 import traceback
 from pathlib import Path
 from pathlib import Path
+from dataclasses import asdict
 
 
 from job_search.read_write import ReadWriter
 from job_search.read_write import ReadWriter
 from job_search.job_storage import JobStorage
 from job_search.job_storage import JobStorage
 from job_search.messages import (
 from job_search.messages import (
-    VisitedLinkedInJobPageMessage,
+    AddJobMessage,
     InitialConfigurationMessage,
     InitialConfigurationMessage,
+    StorageReadyMessage,
+    ListJobsRequestMessage,
     JobOfferListMessage,
     JobOfferListMessage,
     LogMessage,
     LogMessage,
     Message,
     Message,
@@ -22,17 +25,20 @@ class Application:
 
 
     def handle_message(self, message: Message):
     def handle_message(self, message: Message):
         match message:
         match message:
-            case VisitedLinkedInJobPageMessage():
-                offer = message.extract_job_offer()
-
+            case AddJobMessage():
                 try:
                 try:
-                    self.job_storage.add_job(offer)
-                    self.read_writer.send_message(JobAddedMessage(offer))
+                    self.job_storage.insert_record("job_offer", asdict(message))
+                    self.read_writer.send_message(JobAddedMessage(message.id))
                 except FileExistsError as e:
                 except FileExistsError as e:
-                    self.read_writer.send_message(JobAlreadyExistsMessage(offer.id))
+                    self.read_writer.send_message(JobAlreadyExistsMessage(message.id))
+
+            case ListJobsRequestMessage():
+                offers = list(self.job_storage.read_all().values())
+                self.read_writer.send_message(JobOfferListMessage(offers))
 
 
             case InitialConfigurationMessage(jobs_path):
             case InitialConfigurationMessage(jobs_path):
                 self.job_storage = JobStorage(base_dir=Path(jobs_path))
                 self.job_storage = JobStorage(base_dir=Path(jobs_path))
+                self.read_writer.send_message(StorageReadyMessage())
 
 
     def loop(self):
     def loop(self):
         while True:
         while True:
@@ -40,10 +46,6 @@ class Application:
                 received_message = self.read_writer.get_message()
                 received_message = self.read_writer.get_message()
                 self.handle_message(received_message)
                 self.handle_message(received_message)
 
 
-                if self.job_storage:
-                    offers = list(self.job_storage.read_all().values())
-                    self.read_writer.send_message(JobOfferListMessage(offers))
-
             except Exception as e:
             except Exception as e:
                 exc_info = sys.exc_info()
                 exc_info = sys.exc_info()
                 tb = "".join(traceback.format_exception(*exc_info))
                 tb = "".join(traceback.format_exception(*exc_info))

+ 0 - 47
native/tests/test_input.py

@@ -1,47 +0,0 @@
-import pytest
-from datetime import date, datetime
-from job_search.messages import VisitedLinkedInJobPageMessage
-from job_search.job_storage import (
-    JobOffer,
-    JobOfferOrigin,
-    ApplicationProcess,
-    Flexibility,
-)
-
-
-@pytest.fixture(
-    params=[
-        (
-            VisitedLinkedInJobPageMessage(
-                url="https://www.linkedin.com/jobs/view/3755217595",
-                job_title="Job title",
-                page_title="Page title",
-                company="Company",
-                location="location",
-                company_domain="domain",
-                company_url="https://www.linkedin.com/company/the-company/life",
-                has_simplified_process=True,
-                flexibility=Flexibility.FULL_REMOTE.value,
-            ),
-            JobOffer(
-                url="https://www.linkedin.com/jobs/view/3755217595",
-                title="Job title",
-                company="Company",
-                origin=JobOfferOrigin.LINKED_IN,
-                application_process=ApplicationProcess.LINKED_IN_SIMPLIFIED,
-                location="location",
-                company_domain="domain",
-                company_url="https://www.linkedin.com/company/the-company/life",
-                flexibility=Flexibility.FULL_REMOTE,
-            ),
-        ),
-    ]
-)
-def message_job_offer(request):
-    return request.param
-
-
-class TestJobOfferExtraction:
-    def test_extract_from_visited_linkedin(self, message_job_offer):
-        (message, expected_job_offer) = message_job_offer
-        assert message.extract_job_offer() == expected_job_offer

+ 3 - 31
native/tests/test_read_write.py

@@ -7,7 +7,6 @@ from dataclasses import asdict
 from job_search.read_write import StdReadWriter, ReadWriter
 from job_search.read_write import StdReadWriter, ReadWriter
 from job_search.messages import (
 from job_search.messages import (
     InitialConfigurationMessage,
     InitialConfigurationMessage,
-    VisitedLinkedInJobPageMessage,
     JobOfferListMessage,
     JobOfferListMessage,
     JobAddedMessage,
     JobAddedMessage,
     JobAlreadyExistsMessage,
     JobAlreadyExistsMessage,
@@ -84,34 +83,7 @@ class TestReadWriter:
             (
             (
                 {"tag": "NativeMessageInitialConfiguration", "values": [{"jobsPath": "jobs_path"}]},
                 {"tag": "NativeMessageInitialConfiguration", "values": [{"jobsPath": "jobs_path"}]},
                 InitialConfigurationMessage(jobs_path="jobs_path"),
                 InitialConfigurationMessage(jobs_path="jobs_path"),
-            ),
-            (
-                {
-                    "tag": "NativeMessageVisitedJobPage",
-                    "values": [{
-                        "url": "url",
-                        "jobTitle": "job_title",
-                        "pageTitle": "page_title",
-                        "company": "company",
-                        "companyUrl": "company_url",
-                        "companyDomain": "company_domain",
-                        "location": "location",
-                        "hasSimplifiedProcess": True,
-                        "flexibility": "hybrid"
-                    }]
-                },
-                VisitedLinkedInJobPageMessage(
-                    url="url",
-                    job_title="job_title",
-                    page_title="page_title",
-                    company="company",
-                    company_url="company_url",
-                    company_domain="company_domain",
-                    location="location",
-                    has_simplified_process=True,
-                    flexibility="hybrid",
-                ),
-            ),
+            )
         ]
         ]
     )
     )
     def input_message(self, request):
     def input_message(self, request):
@@ -133,8 +105,8 @@ class TestReadWriter:
                 {"tag": "NativeMessageJobOfferList", "values": [[{"id": "job_offer_1"}, {"id": "job_offer_2"}]]},
                 {"tag": "NativeMessageJobOfferList", "values": [[{"id": "job_offer_1"}, {"id": "job_offer_2"}]]},
             ),
             ),
             (
             (
-                JobAddedMessage(job="job"),
-                {"tag": "NativeMessageJobAdded", "values": [{"job": "job"}]},
+                JobAddedMessage(job_id="job_id"),
+                {"tag": "NativeMessageJobAdded", "values": [{"job_id": "job_id"}]},
             ),
             ),
             (
             (
                 JobAlreadyExistsMessage(job_id="job_id"),
                 JobAlreadyExistsMessage(job_id="job_id"),

+ 6 - 3
native/tests/test_storage.py

@@ -19,6 +19,7 @@ def job_storage(request, tmp_path):
 @pytest.fixture(
 @pytest.fixture(
     params=[
     params=[
         JobOffer(
         JobOffer(
+            id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
             title="Job title",
             company="Company",
             company="Company",
@@ -30,6 +31,7 @@ def job_storage(request, tmp_path):
             publication_date=date.today(),
             publication_date=date.today(),
         ),
         ),
         JobOffer(
         JobOffer(
+            id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
             title="Job title",
             company="Company",
             company="Company",
@@ -52,6 +54,7 @@ def job_storage(request, tmp_path):
             application_rejection_date=date.today(),
             application_rejection_date=date.today(),
         ),
         ),
         JobOffer(
         JobOffer(
+            id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
             title="Job title",
             company="Company",
             company="Company",
@@ -78,7 +81,7 @@ class TestJobStorage:
         assert job_storage.read_all() == {}
         assert job_storage.read_all() == {}
 
 
     def test_job_addition(self, job_storage, linked_in_job_offer):
     def test_job_addition(self, job_storage, linked_in_job_offer):
-        job_storage.add_job(linked_in_job_offer)
+        job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
 
 
         all_items = job_storage.read_all().items()
         all_items = job_storage.read_all().items()
         assert len(all_items) == 1
         assert len(all_items) == 1
@@ -92,9 +95,9 @@ class TestJobStorage:
         assert stored_job == linked_in_job_offer
         assert stored_job == linked_in_job_offer
 
 
     def test_job_duplicate_addition(self, job_storage, linked_in_job_offer):
     def test_job_duplicate_addition(self, job_storage, linked_in_job_offer):
-        job_storage.add_job(linked_in_job_offer)
+        job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
 
 
         with pytest.raises(FileExistsError) as excinfo:
         with pytest.raises(FileExistsError) as excinfo:
-            job_storage.add_job(linked_in_job_offer)
+            job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
 
 
         assert linked_in_job_offer.id in str(excinfo.value)
         assert linked_in_job_offer.id in str(excinfo.value)

+ 27 - 18
src/Background.purs

@@ -10,23 +10,27 @@ import Browser.WebExt.Tabs (Tab)
 import Data.Argonaut.Decode (printJsonDecodeError)
 import Data.Argonaut.Decode (printJsonDecodeError)
 import Data.Either (Either(..))
 import Data.Either (Either(..))
 import Data.Foldable (for_)
 import Data.Foldable (for_)
+import Data.Int64 as I64
 import Data.Maybe (Maybe(..))
 import Data.Maybe (Maybe(..))
 import Effect (Effect)
 import Effect (Effect)
 import Effect.Aff (launchAff_)
 import Effect.Aff (launchAff_)
 import Effect.Class (liftEffect)
 import Effect.Class (liftEffect)
 import Effect.Class.Console (debug, error, log, logShow)
 import Effect.Class.Console (debug, error, log, logShow)
-import ExampleWebExt.NativeMessage (NativeMessage(..), connectToNativeApplication, onNativeDisconnectAddListener, onNativeMessageAddListener, sendMessageToNative)
+import ExampleWebExt.NativeMessage (ApplicationProcess(..), NativeMessage(..), connectToNativeApplication, onNativeDisconnectAddListener, onNativeMessageAddListener, sendMessageToNative)
 import ExampleWebExt.RuntimeMessage (RuntimeMessage(..), onRuntimeMessageAddListener, sendMessageToContent)
 import ExampleWebExt.RuntimeMessage (RuntimeMessage(..), onRuntimeMessageAddListener, sendMessageToContent)
 import ExampleWebExt.Storage (clearAllJobs, getJobsPath, storeJob)
 import ExampleWebExt.Storage (clearAllJobs, getJobsPath, storeJob)
 import LinkedIn.Jobs.JobOffer (JobOffer(..))
 import LinkedIn.Jobs.JobOffer (JobOffer(..))
 import LinkedIn.Output.Types (Output(..))
 import LinkedIn.Output.Types (Output(..))
+import LinkedIn.PageUrl (PageUrl(..))
+import LinkedIn.UI.Basic.Types (JobOfferId(..))
 import Web.URL as URL
 import Web.URL as URL
 
 
 main :: Effect Unit
 main :: Effect Unit
 main = do
 main = do
   log "[bg] starting up"
   log "[bg] starting up"
   port <- connectToNativeApplication "job_search_background"
   port <- connectToNativeApplication "job_search_background"
-  onNativeMessageAddListener port nativeMessageHandler
+  -- TODO: This is redundant to send "port" to both addListener and the handler
+  onNativeMessageAddListener port $ nativeMessageHandler port
   onNativeDisconnectAddListener port \p -> log $ "disconnected from native port " <> p.name <> " (" <> p.error <> ")"
   onNativeDisconnectAddListener port \p -> log $ "disconnected from native port " <> p.name <> " (" <> p.error <> ")"
 
 
   sendConfigurationToNative port
   sendConfigurationToNative port
@@ -43,7 +47,7 @@ browserActionOnClickedHandler tab = do
 contentScriptMessageHandler ∷ Port -> RuntimeMessage -> MessageSender → Effect Unit
 contentScriptMessageHandler ∷ Port -> RuntimeMessage -> MessageSender → Effect Unit
 contentScriptMessageHandler
 contentScriptMessageHandler
   port
   port
-  (RuntimeMessagePageContent _ (OutJobOffer offer))
+  (RuntimeMessagePageContent (UrlJobOffer (JobOfferId jobId)) (OutJobOffer offer))
   (MessageSender {tab: Just {url, title}}) =
   (MessageSender {tab: Just {url, title}}) =
     case maybeMsg offer of
     case maybeMsg offer of
       Just msg -> sendMessageToNative port msg
       Just msg -> sendMessageToNative port msg
@@ -51,19 +55,22 @@ contentScriptMessageHandler
 
 
   where
   where
     maybeMsg (JobOffer jo) = ado
     maybeMsg (JobOffer jo) = ado
-      location <- jo.location
       url <- cleanUpUrl url
       url <- cleanUpUrl url
-    in NativeMessageVisitedJobPage {
-      url: url,
-      jobTitle: jo.title,
-      pageTitle: title,
-      company: jo.companyName,
-      companyDomain: jo.companyDomain,
-      companyUrl: jo.companyLink,
-      location,
-      hasSimplifiedProcess: jo.hasSimplifiedApplicationProcess,
-      flexibility: jo.flexibility
-    }
+    in NativeMessageAddJob {
+        id: "linked_in_" <> I64.toString jobId,
+        origin: "linked_in",
+        title: jo.title,
+        url,
+        company: jo.companyName,
+        location: jo.location,
+        company_domain: jo.companyDomain,
+        company_url: Just jo.companyLink,
+        flexibility: jo.flexibility,
+        application_process: Just $ if jo.hasSimplifiedApplicationProcess then ApplicationProcessLinkedInSimplified else ApplicationProcessRegular,
+        application_date: Nothing,
+        application_rejection_date: Nothing,
+        application_considered: Nothing
+      }
 
 
 contentScriptMessageHandler _ m (MessageSender {tab, id}) = do
 contentScriptMessageHandler _ m (MessageSender {tab, id}) = do
   let
   let
@@ -79,13 +86,15 @@ cleanUpUrl u = do
   url <- URL.fromAbsolute u
   url <- URL.fromAbsolute u
   pure $ URL.toString $ URL.setSearch "" url
   pure $ URL.toString $ URL.setSearch "" url
 
 
-nativeMessageHandler ∷ NativeMessage → Effect Unit
-nativeMessageHandler (NativeMessageJobOfferList job_offers) = do
+nativeMessageHandler ∷ Port -> NativeMessage → Effect Unit
+nativeMessageHandler _ (NativeMessageJobOfferList job_offers) = do
   clearAllJobs
   clearAllJobs
   for_ job_offers \jo -> do
   for_ job_offers \jo -> do
     storeJob jo
     storeJob jo
 
 
-nativeMessageHandler m = logShow m
+nativeMessageHandler port NativeMessageStorageReady = sendMessageToNative port $ NativeMessageListJobsRequest
+nativeMessageHandler port (NativeMessageJobAdded _) = sendMessageToNative port $ NativeMessageListJobsRequest
+nativeMessageHandler _ m = logShow m
 
 
 sendConfigurationToNative ∷ Port → Effect Unit
 sendConfigurationToNative ∷ Port → Effect Unit
 sendConfigurationToNative port = launchAff_ do
 sendConfigurationToNative port = launchAff_ do

+ 6 - 13
src/NativeMessage.purs

@@ -24,19 +24,11 @@ data NativeMessage =
   NativeMessageBackground String
   NativeMessageBackground String
   | NativeMessageLog {level :: String, content :: String}
   | NativeMessageLog {level :: String, content :: String}
   | NativeMessageInitialConfiguration {jobsPath :: String}
   | NativeMessageInitialConfiguration {jobsPath :: String}
-  | NativeMessageVisitedJobPage {
-    url :: String,
-    jobTitle :: String,
-    pageTitle :: String,
-    company :: String,
-    companyDomain :: Maybe String,
-    companyUrl :: String,
-    location :: String,
-    hasSimplifiedProcess :: Boolean,
-    flexibility :: Maybe JobFlexibility
-  }
+  | NativeMessageStorageReady
+  | NativeMessageAddJob NativePythonJobOffer
+  | NativeMessageListJobsRequest
   | NativeMessageJobAlreadyExists {job_id :: String}
   | NativeMessageJobAlreadyExists {job_id :: String}
-  | NativeMessageJobAdded {job :: NativePythonJobOffer}
+  | NativeMessageJobAdded {job_id :: String}
   | NativeMessageJobOfferList (Array NativePythonJobOffer)
   | NativeMessageJobOfferList (Array NativePythonJobOffer)
 
 
 data ApplicationProcess
 data ApplicationProcess
@@ -64,6 +56,7 @@ instance DecodeJson ApplicationProcess where
 
 
 type NativePythonJobOffer = {
 type NativePythonJobOffer = {
   id :: String,
   id :: String,
+  origin :: String,
   title :: String,
   title :: String,
   url :: String,
   url :: String,
   company :: String,
   company :: String,
@@ -78,7 +71,7 @@ type NativePythonJobOffer = {
 }
 }
 
 
 derive instance Generic NativeMessage _
 derive instance Generic NativeMessage _
-instance Show NativeMessage where show = genericShow
+instance Show NativeMessage where show a = genericShow a
 instance EncodeJson NativeMessage where encodeJson a = genericEncodeJson a
 instance EncodeJson NativeMessage where encodeJson a = genericEncodeJson a
 
 
 instance DecodeJson NativeMessage where
 instance DecodeJson NativeMessage where