mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-08 03:08:21 +00:00
fix(extension): keep X import status on initiating tab
This commit is contained in:
parent
e651045ac5
commit
63e0d1f705
3 changed files with 138 additions and 36 deletions
|
|
@ -15,6 +15,7 @@ import {
|
||||||
type TwitterImportConfig,
|
type TwitterImportConfig,
|
||||||
TwitterImporter,
|
TwitterImporter,
|
||||||
} from "../utils/twitter-import"
|
} from "../utils/twitter-import"
|
||||||
|
import { createTwitterImportNotifications } from "../utils/twitter-import-notifications"
|
||||||
import type {
|
import type {
|
||||||
ExtensionMessage,
|
ExtensionMessage,
|
||||||
MemoryData,
|
MemoryData,
|
||||||
|
|
@ -90,36 +91,6 @@ export default defineBackground(() => {
|
||||||
["requestHeaders", "extraHeaders"],
|
["requestHeaders", "extraHeaders"],
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send message to current active tab.
|
|
||||||
const sendMessageToCurrentTab = async (message: string) => {
|
|
||||||
const tabs = await browser.tabs.query({
|
|
||||||
active: true,
|
|
||||||
currentWindow: true,
|
|
||||||
})
|
|
||||||
if (tabs.length > 0 && tabs[0].id) {
|
|
||||||
await browser.tabs.sendMessage(tabs[0].id, {
|
|
||||||
type: MESSAGE_TYPES.IMPORT_UPDATE,
|
|
||||||
importedMessage: message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send import completion message
|
|
||||||
*/
|
|
||||||
const sendImportDoneMessage = async (totalImported: number) => {
|
|
||||||
const tabs = await browser.tabs.query({
|
|
||||||
active: true,
|
|
||||||
currentWindow: true,
|
|
||||||
})
|
|
||||||
if (tabs.length > 0 && tabs[0].id) {
|
|
||||||
await browser.tabs.sendMessage(tabs[0].id, {
|
|
||||||
type: MESSAGE_TYPES.IMPORT_DONE,
|
|
||||||
totalImported,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save memory to supermemory API
|
* Save memory to supermemory API
|
||||||
*/
|
*/
|
||||||
|
|
@ -246,18 +217,21 @@ export default defineBackground(() => {
|
||||||
* Handle extension messages
|
* Handle extension messages
|
||||||
*/
|
*/
|
||||||
browser.runtime.onMessage.addListener(
|
browser.runtime.onMessage.addListener(
|
||||||
(message: ExtensionMessage, _sender, sendResponse) => {
|
(message: ExtensionMessage, sender, sendResponse) => {
|
||||||
// Handle Twitter import request
|
// Handle Twitter import request
|
||||||
if (message.type === MESSAGE_TYPES.BATCH_IMPORT_ALL) {
|
if (message.type === MESSAGE_TYPES.BATCH_IMPORT_ALL) {
|
||||||
|
const notifications = createTwitterImportNotifications(
|
||||||
|
sender.tab?.id,
|
||||||
|
(tabId, notification) =>
|
||||||
|
browser.tabs.sendMessage(tabId, notification),
|
||||||
|
)
|
||||||
const importConfig: TwitterImportConfig = {
|
const importConfig: TwitterImportConfig = {
|
||||||
isFolderImport: message.isFolderImport,
|
isFolderImport: message.isFolderImport,
|
||||||
bookmarkCollectionId: message.bookmarkCollectionId,
|
bookmarkCollectionId: message.bookmarkCollectionId,
|
||||||
selectedProject: message.selectedProject,
|
selectedProject: message.selectedProject,
|
||||||
onProgress: sendMessageToCurrentTab,
|
onProgress: notifications.onProgress,
|
||||||
onComplete: sendImportDoneMessage,
|
onComplete: notifications.onComplete,
|
||||||
onError: async (error: Error) => {
|
onError: notifications.onError,
|
||||||
await sendMessageToCurrentTab(`Error: ${error.message}`)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
twitterImporter = new TwitterImporter(importConfig)
|
twitterImporter = new TwitterImporter(importConfig)
|
||||||
|
|
@ -351,3 +325,4 @@ export default defineBackground(() => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { describe, expect, mock, test } from "bun:test"
|
||||||
|
import { MESSAGE_TYPES } from "./constants"
|
||||||
|
import { createTwitterImportNotifications } from "./twitter-import-notifications"
|
||||||
|
|
||||||
|
describe("Twitter import notifications", () => {
|
||||||
|
test("keeps progress, errors, and completion on the initiating tab", async () => {
|
||||||
|
let activeTabId = 7
|
||||||
|
const sendMessage = mock(async () => {})
|
||||||
|
const notifications = createTwitterImportNotifications(
|
||||||
|
activeTabId,
|
||||||
|
sendMessage,
|
||||||
|
)
|
||||||
|
|
||||||
|
activeTabId = 42
|
||||||
|
await notifications.onProgress("Imported 10 bookmarks")
|
||||||
|
await notifications.onError(new Error("rate limited"))
|
||||||
|
await notifications.onComplete(10)
|
||||||
|
|
||||||
|
expect(activeTabId).toBe(42)
|
||||||
|
expect(sendMessage.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
{
|
||||||
|
type: MESSAGE_TYPES.IMPORT_UPDATE,
|
||||||
|
importedMessage: "Imported 10 bookmarks",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
{
|
||||||
|
type: MESSAGE_TYPES.IMPORT_UPDATE,
|
||||||
|
importedMessage: "Error: rate limited",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[7, { type: MESSAGE_TYPES.IMPORT_DONE, totalImported: 10 }],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("does not let tab closure interrupt import callbacks", async () => {
|
||||||
|
const sendMessage = mock(async () => {
|
||||||
|
throw new Error("Receiving end does not exist")
|
||||||
|
})
|
||||||
|
const notifications = createTwitterImportNotifications(7, sendMessage)
|
||||||
|
|
||||||
|
await notifications.onProgress("Retrying")
|
||||||
|
await notifications.onError(new Error("failed"))
|
||||||
|
await notifications.onComplete(0)
|
||||||
|
|
||||||
|
expect(sendMessage).toHaveBeenCalledTimes(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("accepts tab id zero", async () => {
|
||||||
|
const sendMessage = mock(async () => {})
|
||||||
|
const notifications = createTwitterImportNotifications(0, sendMessage)
|
||||||
|
|
||||||
|
await notifications.onProgress("Starting")
|
||||||
|
|
||||||
|
expect(sendMessage).toHaveBeenCalledWith(0, {
|
||||||
|
type: MESSAGE_TYPES.IMPORT_UPDATE,
|
||||||
|
importedMessage: "Starting",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("skips notifications when the request has no sender tab", async () => {
|
||||||
|
const sendMessage = mock(async () => {})
|
||||||
|
const notifications = createTwitterImportNotifications(
|
||||||
|
undefined,
|
||||||
|
sendMessage,
|
||||||
|
)
|
||||||
|
|
||||||
|
await notifications.onProgress("Starting")
|
||||||
|
await notifications.onComplete(0)
|
||||||
|
|
||||||
|
expect(sendMessage).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
50
apps/browser-extension/utils/twitter-import-notifications.ts
Normal file
50
apps/browser-extension/utils/twitter-import-notifications.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { MESSAGE_TYPES } from "./constants"
|
||||||
|
|
||||||
|
type TwitterImportNotification =
|
||||||
|
| {
|
||||||
|
type: typeof MESSAGE_TYPES.IMPORT_UPDATE
|
||||||
|
importedMessage: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: typeof MESSAGE_TYPES.IMPORT_DONE
|
||||||
|
totalImported: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendTabMessage = (
|
||||||
|
tabId: number,
|
||||||
|
message: TwitterImportNotification,
|
||||||
|
) => Promise<unknown>
|
||||||
|
|
||||||
|
export function createTwitterImportNotifications(
|
||||||
|
tabId: number | undefined,
|
||||||
|
sendMessage: SendTabMessage,
|
||||||
|
) {
|
||||||
|
const deliver = async (message: TwitterImportNotification): Promise<void> => {
|
||||||
|
if (tabId === undefined) return
|
||||||
|
try {
|
||||||
|
await sendMessage(tabId, message)
|
||||||
|
} catch {
|
||||||
|
// The initiating tab can be closed or navigated while the import keeps
|
||||||
|
// running. Notification delivery must not cancel the import itself.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
onProgress: (message: string) =>
|
||||||
|
deliver({
|
||||||
|
type: MESSAGE_TYPES.IMPORT_UPDATE,
|
||||||
|
importedMessage: message,
|
||||||
|
}),
|
||||||
|
onComplete: (totalImported: number) =>
|
||||||
|
deliver({
|
||||||
|
type: MESSAGE_TYPES.IMPORT_DONE,
|
||||||
|
totalImported,
|
||||||
|
}),
|
||||||
|
onError: (error: Error) =>
|
||||||
|
deliver({
|
||||||
|
type: MESSAGE_TYPES.IMPORT_UPDATE,
|
||||||
|
importedMessage: `Error: ${error.message}`,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue