diff --git a/cli/pom.xml b/cli/pom.xml index 7c007cbf..16158d22 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -101,13 +101,6 @@ 4.4.14 - - - com.github.javakeyring - java-keyring - 1.0.4 - - com.vdurmont diff --git a/cli/src/main/java/ca/weblite/jdeploy/cli/di/JDeployCliModule.java b/cli/src/main/java/ca/weblite/jdeploy/cli/di/JDeployCliModule.java index 604dce02..69b3f230 100644 --- a/cli/src/main/java/ca/weblite/jdeploy/cli/di/JDeployCliModule.java +++ b/cli/src/main/java/ca/weblite/jdeploy/cli/di/JDeployCliModule.java @@ -2,14 +2,10 @@ import ca.weblite.jdeploy.cli.util.CliChatThreadDispatcher; import ca.weblite.jdeploy.cli.util.CliUiThreadDispatcher; -import ca.weblite.jdeploy.npm.NpmAccountServiceInterface; -import ca.weblite.jdeploy.npm.PreferencesNpmAccountService; import ca.weblite.jdeploy.openai.interop.ChatThreadDispatcher; import ca.weblite.jdeploy.openai.interop.UiThreadDispatcher; import ca.weblite.jdeploy.project.service.DefaultProjectLoader; import ca.weblite.jdeploy.project.service.ProjectLoader; -import ca.weblite.jdeploy.secure.JavaKeyringPasswordService; -import ca.weblite.jdeploy.secure.PasswordServiceInterface; import org.codejargon.feather.Provides; public class JDeployCliModule { @@ -29,13 +25,4 @@ public ProjectLoader projectLoader(DefaultProjectLoader impl) { return impl; } - @Provides - public NpmAccountServiceInterface npmAccountServiceInterface(PreferencesNpmAccountService impl) { - return impl; - } - @Provides - public PasswordServiceInterface passwordServiceInterface(JavaKeyringPasswordService javaKeyringPasswordService) { - return javaKeyringPasswordService; - } - } diff --git a/cli/src/main/java/ca/weblite/jdeploy/gui/EditNpmAccountDialog.java b/cli/src/main/java/ca/weblite/jdeploy/gui/EditNpmAccountDialog.java deleted file mode 100644 index 876227b8..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/gui/EditNpmAccountDialog.java +++ /dev/null @@ -1,63 +0,0 @@ -package ca.weblite.jdeploy.gui; - -import ca.weblite.jdeploy.npm.NpmAccountInterface; - -import javax.swing.*; -import java.awt.*; - -public class EditNpmAccountDialog extends JDialog { - private EditNpmAccountPanel editNpmAccountPanel; - private JButton saveButton; - private JButton cancelButton; - public EditNpmAccountDialog(Window parent, NpmAccountInterface account) { - super(parent); - // Create the layout - GroupLayout layout = new GroupLayout(this.getContentPane()); - this.getContentPane().setLayout(layout); - - // Create the components - editNpmAccountPanel = new EditNpmAccountPanel(account); - saveButton = new JButton("Save"); - cancelButton = new JButton("Cancel"); - - // Set the layout - layout.setAutoCreateGaps(true); - layout.setAutoCreateContainerGaps(true); - - layout.setHorizontalGroup( - layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(editNpmAccountPanel) - .addGroup(layout.createSequentialGroup() - .addComponent(saveButton) - .addComponent(cancelButton) - ) - ); - - layout.setVerticalGroup( - layout.createSequentialGroup() - .addComponent(editNpmAccountPanel) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(saveButton) - .addComponent(cancelButton) - ) - ); - pack(); - } - - public JTextField getAccountNameField() { - return editNpmAccountPanel.getAccountNameField(); - } - - public JPasswordField getNpmTokenField() { - return editNpmAccountPanel.getNpmTokenField(); - } - - public JButton getSaveButton() { - return saveButton; - } - - public JButton getCancelButton() { - return cancelButton; - } - -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/gui/EditNpmAccountPanel.java b/cli/src/main/java/ca/weblite/jdeploy/gui/EditNpmAccountPanel.java deleted file mode 100644 index 3ececd0f..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/gui/EditNpmAccountPanel.java +++ /dev/null @@ -1,74 +0,0 @@ -package ca.weblite.jdeploy.gui; - -import ca.weblite.jdeploy.npm.NpmAccount; -import ca.weblite.jdeploy.npm.NpmAccountInterface; - -import javax.swing.*; - -public class EditNpmAccountPanel extends JPanel { - private JTextField accountNameField; - private JPasswordField npmTokenField; - - public EditNpmAccountPanel(NpmAccountInterface account) { - super(); - - // Create the layout - GroupLayout layout = new GroupLayout(this); - setLayout(layout); - - // Create the components - JLabel accountNameLabel = new JLabel("Account Name"); - accountNameField = new JTextField(30); - - - JLabel npmTokenLabel = new JLabel("NPM Token"); - npmTokenField = new JPasswordField(30); - - // Set the account name - accountNameField.setText(account.getNpmAccountName()); - - // Set the NPM token - if (account.getNpmToken() != null) { - npmTokenField.setText(account.getNpmToken()); - } - - // Set the layout - layout.setAutoCreateGaps(true); - layout.setAutoCreateContainerGaps(true); - - layout.setHorizontalGroup( - layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(accountNameLabel) - .addComponent(npmTokenLabel) - ) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(accountNameField) - .addComponent(npmTokenField) - ) - ); - - layout.setVerticalGroup( - layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(accountNameLabel) - .addComponent(accountNameField) - ) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(npmTokenLabel) - .addComponent(npmTokenField) - ) - ); - - - } - - - public JPasswordField getNpmTokenField() { - return npmTokenField; - } - - public JTextField getAccountNameField() { - return accountNameField; - } -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/gui/JDeployProjectEditor.java b/cli/src/main/java/ca/weblite/jdeploy/gui/JDeployProjectEditor.java index 9bb6d6fd..a52ad3f5 100644 --- a/cli/src/main/java/ca/weblite/jdeploy/gui/JDeployProjectEditor.java +++ b/cli/src/main/java/ca/weblite/jdeploy/gui/JDeployProjectEditor.java @@ -4,14 +4,12 @@ import ca.weblite.jdeploy.JDeploy; import ca.weblite.jdeploy.gui.controllers.EditGithubWorkflowController; import ca.weblite.jdeploy.gui.controllers.GenerateGithubWorkflowController; -import ca.weblite.jdeploy.gui.controllers.NpmAccountChooserController; import ca.weblite.jdeploy.gui.controllers.VerifyWebsiteController; import ca.weblite.jdeploy.gui.tabs.CheerpJSettings; import ca.weblite.jdeploy.gui.tabs.DetailsPanel; import ca.weblite.jdeploy.helpers.NPMApplicationHelper; import ca.weblite.jdeploy.models.NPMApplication; import ca.weblite.jdeploy.npm.NPM; -import ca.weblite.jdeploy.npm.NpmAccountServiceInterface; import ca.weblite.jdeploy.npm.TerminalLoginLauncher; import ca.weblite.jdeploy.services.ExportIdentityService; import ca.weblite.jdeploy.services.GithubService; @@ -20,10 +18,8 @@ import ca.weblite.tools.io.FileUtil; import ca.weblite.tools.io.MD5; import com.sun.nio.file.SensitivityWatchEventModifier; -import io.codeworth.panelmatic.PanelBuilder; import io.codeworth.panelmatic.PanelMatic; import io.codeworth.panelmatic.util.Groupings; -import io.codeworth.panelmatic.util.PanelPostProcessors; import net.coobird.thumbnailator.Thumbnails; import org.apache.commons.io.FileUtils; import org.json.JSONArray; @@ -42,7 +38,6 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.*; -import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.*; @@ -1844,52 +1839,9 @@ private void handleExportIdentity0() throws IOException { } private void handlePublish0() throws ValidationException { - handlePublish0(true); - } - - private void handlePublish0(boolean promptForAccount) throws ValidationException { - - if (promptForAccount) { - final boolean[] accountChosen = {false}; - final boolean[] accountChosenResult = {false}; - final Object lock = new Object(); - - Runnable runnablePublish = () -> { - NpmAccountChooserController accountChooserController = new NpmAccountChooserController( - frame, DIContext.getInstance().getInstance(NpmAccountServiceInterface.class) - ); - - accountChooserController.show().thenAccept(account -> { - if (account == null) { - accountChosen[0] = true; - synchronized (lock) { - lock.notify(); - return; - } - } - context.setNpmToken(account.getNpmToken()); - accountChosen[0] = true; - accountChosenResult[0] = true; - synchronized (lock) { - lock.notify(); - } - }); - - }; - SwingUtilities.invokeLater(runnablePublish); - - while (!accountChosen[0]) { - try { - synchronized (lock) { - lock.wait(1000); - } - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - } - if (!accountChosenResult[0]) { - return; - } + if (!EventQueue.isDispatchThread()) { + // We don't prompt on the dispatch thread because promptForNpmToken blocks + context.promptForNpmToken(frame); } File absDirectory = packageJSONFile.getAbsoluteFile().getParentFile(); diff --git a/cli/src/main/java/ca/weblite/jdeploy/gui/JDeployProjectEditorContext.java b/cli/src/main/java/ca/weblite/jdeploy/gui/JDeployProjectEditorContext.java index fb7398dc..b5bd9341 100644 --- a/cli/src/main/java/ca/weblite/jdeploy/gui/JDeployProjectEditorContext.java +++ b/cli/src/main/java/ca/weblite/jdeploy/gui/JDeployProjectEditorContext.java @@ -3,7 +3,6 @@ import ca.weblite.jdeploy.DIContext; import ca.weblite.jdeploy.interop.DesktopInterop; import ca.weblite.jdeploy.interop.FileChooserInterop; -import ca.weblite.jdeploy.npm.NpmAccountChooserInterface; import ca.weblite.tools.platform.Platform; import javax.swing.*; @@ -70,6 +69,10 @@ public boolean useManagedNode() { return false; } + public void promptForNpmToken(Object parent) { + + } + public void setNpmToken(String npmToken) { this.npmToken = npmToken; } diff --git a/cli/src/main/java/ca/weblite/jdeploy/gui/NpmAccountChooserDialog.java b/cli/src/main/java/ca/weblite/jdeploy/gui/NpmAccountChooserDialog.java deleted file mode 100644 index 2ad43f77..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/gui/NpmAccountChooserDialog.java +++ /dev/null @@ -1,249 +0,0 @@ -package ca.weblite.jdeploy.gui; - -import ca.weblite.jdeploy.npm.NpmAccountInterface; -import org.kordamp.ikonli.material.Material; -import org.kordamp.ikonli.swing.FontIcon; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.util.List; - -public class NpmAccountChooserDialog extends JDialog { - - // For demo, we keep references to some UI controls: - private final JLabel subtitleLabel; - private final JButton whyButton; - private final JPanel accountListPanel; - private final JButton continueButton; - private final JButton addAccountButton; - private final JButton closeButton; - - // The currently selected account - private NpmAccountInterface selectedAccount; - // The currently selected account button (to highlight) - private JButton selectedButton; - - private final Font headerFont = new Font("SansSerif", Font.BOLD, 18); - private final Font subHeaderFont = new Font("SansSerif", Font.PLAIN, 14); - - // Colors - private final Color textColor = Color.BLACK; - private final Color linkColor = new Color(0, 120, 220); - private final Color selectedBackground = new Color(220, 240, 255); - - /** - * Creates a modal, undecorated dialog. - */ - public NpmAccountChooserDialog(Frame parent, List accounts) { - super(parent, true); - setUndecorated(true); - - /* - * 1) Main content with BorderLayout - * - We'll use the NORTH region for the close button - * - The CENTER region for all the main content - */ - JPanel contentPanel = new JPanel(new BorderLayout()); - contentPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1)); - setContentPane(contentPanel); - - /* - * 2) Top "close button" panel, aligned to the right - */ - JPanel northPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0)); - northPanel.setOpaque(false); - - closeButton = createCloseButton(parent); - northPanel.add(closeButton); - - contentPanel.add(northPanel, BorderLayout.NORTH); - - /* - * 3) Main (center) content. We'll still use a BoxLayout to stack - * the "npm" title, subtitle, account list, etc. - */ - JPanel centerPanel = new JPanel(); - centerPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 20, 30)); - centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS)); - centerPanel.setOpaque(false); - - // Title: "npm", centered - JLabel titleLabel = new JLabel("npm"); - titleLabel.setFont(headerFont); - titleLabel.setForeground(textColor); - titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT); - centerPanel.add(titleLabel); - centerPanel.add(Box.createVerticalStrut(8)); - - // Subtitle: "Select an account" - subtitleLabel = new JLabel("Select an account"); - subtitleLabel.setFont(subHeaderFont); - subtitleLabel.setForeground(textColor); - subtitleLabel.setAlignmentX(Component.CENTER_ALIGNMENT); - centerPanel.add(subtitleLabel); - centerPanel.add(Box.createVerticalStrut(15)); - - // "Why" link - whyButton = createLinkButton("Why am I being asked to select an account?", - Material.HELP_OUTLINE, linkColor); - whyButton.setVisible(false); - whyButton.setAlignmentX(Component.CENTER_ALIGNMENT); - centerPanel.add(whyButton); - centerPanel.add(Box.createVerticalStrut(20)); - - // Account list - accountListPanel = new JPanel(); - accountListPanel.setLayout(new BoxLayout(accountListPanel, BoxLayout.Y_AXIS)); - accountListPanel.setOpaque(false); - accountListPanel.setAlignmentX(Component.CENTER_ALIGNMENT); - - for (NpmAccountInterface account : accounts) { - JButton accountBtn = createAccountButton(account); - accountListPanel.add(accountBtn); - accountListPanel.add(Box.createVerticalStrut(10)); - } - centerPanel.add(accountListPanel); - centerPanel.add(Box.createVerticalStrut(15)); - - // "Continue" button - continueButton = new JButton("Continue"); - continueButton.setEnabled(false); - continueButton.setAlignmentX(Component.CENTER_ALIGNMENT); - continueButton.setPreferredSize(new Dimension(120, 35)); - continueButton.addActionListener(e -> dispose()); - centerPanel.add(continueButton); - centerPanel.add(Box.createVerticalStrut(20)); - - // "Add a new account" - addAccountButton = createLinkButton("Add a new account", null, linkColor); - addAccountButton.setAlignmentX(Component.CENTER_ALIGNMENT); - centerPanel.add(addAccountButton); - - contentPanel.add(centerPanel, BorderLayout.CENTER); - - // Final setup - pack(); - setLocationRelativeTo(parent); - } - - /** - * Shows the dialog (modal). Returns the selected account, or null if none. - */ - public NpmAccountInterface showDialog() { - setVisible(true); - return selectedAccount; - } - - /** - * Create a button for each account - */ - private JButton createAccountButton(NpmAccountInterface account) { - JButton btn = new JButton(account.getNpmAccountName()); - btn.setBorderPainted(false); - btn.setFocusPainted(false); - btn.setContentAreaFilled(false); - - btn.setHorizontalAlignment(SwingConstants.LEFT); - btn.setFont(new Font("SansSerif", Font.PLAIN, 14)); - - // Account icon - FontIcon icon = FontIcon.of(Material.ACCOUNT_CIRCLE, 20, textColor); - btn.setIcon(icon); - - // Let it expand horizontally - btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30)); - - // On click => select - btn.addActionListener((ActionEvent e) -> { - selectedAccount = account; - continueButton.setEnabled(true); - setSelectedButton(btn); - }); - - return btn; - } - - /** - * Highlight the newly selected button, unhighlight old one. - */ - private void setSelectedButton(JButton btn) { - if (selectedButton != null) { - selectedButton.setOpaque(false); - selectedButton.setContentAreaFilled(false); - selectedButton.repaint(); - } - selectedButton = btn; - selectedButton.setOpaque(true); - selectedButton.setContentAreaFilled(true); - selectedButton.setBackground(selectedBackground); - selectedButton.repaint(); - } - - /** - * Create link-styled JButtons. - */ - private JButton createLinkButton(String text, Material iconType, Color linkColor) { - JButton linkBtn = new JButton(text); - linkBtn.setFocusPainted(false); - linkBtn.setBorderPainted(false); - linkBtn.setContentAreaFilled(false); - linkBtn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - linkBtn.setAlignmentX(Component.CENTER_ALIGNMENT); - - linkBtn.setFont(new Font("SansSerif", Font.PLAIN, 12)); - linkBtn.setForeground(linkColor); - - if (iconType != null) { - FontIcon icon = FontIcon.of(iconType, 16, linkColor); - linkBtn.setIcon(icon); - } - - // Underline on hover - linkBtn.addMouseListener(new java.awt.event.MouseAdapter() { - @Override - public void mouseEntered(java.awt.event.MouseEvent e) { - linkBtn.setText("
" + - "" + text + "
"); - } - @Override - public void mouseExited(java.awt.event.MouseEvent e) { - linkBtn.setText(text); - } - }); - - return linkBtn; - } - - /** - * Creates a "close" (X) button for the top-right (NORTH) region. - */ - private JButton createCloseButton(Frame parent) { - JButton closeBtn = new JButton(); - closeBtn.setFocusPainted(false); - closeBtn.setBorderPainted(false); - closeBtn.setContentAreaFilled(false); - closeBtn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - - FontIcon closeIcon = FontIcon.of(Material.CLOSE, 18, Color.GRAY); - closeBtn.setIcon(closeIcon); - - // On click => clear selected & dispose - closeBtn.addActionListener(e -> { - selectedAccount = null; - dispose(); - if (parent != null) { - parent.requestFocus(); - } - }); - return closeBtn; - } - - // External getters if needed - public JButton getAddAccountButton() { - return addAccountButton; - } - public JButton getWhyButton() { - return whyButton; - } -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/gui/SwingExecutor.java b/cli/src/main/java/ca/weblite/jdeploy/gui/SwingExecutor.java deleted file mode 100644 index 697ab54a..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/gui/SwingExecutor.java +++ /dev/null @@ -1,16 +0,0 @@ -package ca.weblite.jdeploy.gui; - -import javax.swing.*; -import java.awt.*; -import java.util.concurrent.Executor; - -public class SwingExecutor implements Executor { - @Override - public void execute(Runnable command) { - if (EventQueue.isDispatchThread()) { - command.run(); - } else { - SwingUtilities.invokeLater(command); - } - } -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/gui/controllers/EditNpmAccountController.java b/cli/src/main/java/ca/weblite/jdeploy/gui/controllers/EditNpmAccountController.java deleted file mode 100644 index fac99696..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/gui/controllers/EditNpmAccountController.java +++ /dev/null @@ -1,96 +0,0 @@ -package ca.weblite.jdeploy.gui.controllers; - -import ca.weblite.jdeploy.gui.EditNpmAccountDialog; -import ca.weblite.jdeploy.npm.NpmAccount; -import ca.weblite.jdeploy.npm.NpmAccountInterface; -import ca.weblite.jdeploy.npm.NpmAccountServiceInterface; -import ca.weblite.jdeploy.gui.SwingExecutor; - -import javax.swing.*; -import java.awt.*; -import java.util.concurrent.Executor; - -public abstract class EditNpmAccountController { - - private static final Executor EDT_EXECUTOR = new SwingExecutor(); - - private final EditNpmAccountDialog dialog; - - private final Window parentFrame; - - private final NpmAccountServiceInterface npmAccountService; - - private NpmAccountInterface newAccount; - - public EditNpmAccountController( - Window parentFrame, - NpmAccountInterface account, - NpmAccountServiceInterface npmAccountService - ) { - this.parentFrame = parentFrame; - this.dialog = new EditNpmAccountDialog(parentFrame, account); - dialog.pack(); - dialog.setLocationRelativeTo(parentFrame); - this.npmAccountService = npmAccountService; - setupSaveButton(); - setupCancelButton(); - - } - - public void show() { - - dialog.setVisible(true); - } - - protected abstract void afterSave(NpmAccountInterface account); - - private NpmAccountInterface getAccount() { - return new NpmAccount( - dialog.getAccountNameField().getText(), - isEmpty(dialog.getNpmTokenField()) - ? null : - new String(dialog.getNpmTokenField().getPassword()) - ); - } - - private boolean isEmpty(JPasswordField field) { - return field.getPassword().length == 0; - } - - private boolean isAccountValid() { - return !dialog.getAccountNameField().getText().isEmpty() && !isEmpty(dialog.getNpmTokenField()); - } - - private void update() { - dialog.getSaveButton().setEnabled(isAccountValid()); - } - - private void setupSaveButton() { - dialog.getSaveButton().addActionListener(e -> { - newAccount = getAccount(); - npmAccountService.saveNpmAccount(newAccount).thenAcceptAsync(result->{ - afterSave(newAccount); - dialog.dispose(); - parentFrame.requestFocus(); - }, EDT_EXECUTOR).exceptionally(t -> { - EventQueue.invokeLater(() -> { - JOptionPane.showMessageDialog( - dialog, - "Failed to save account: " + t.getMessage(), - "Error", - JOptionPane.ERROR_MESSAGE - ); - }); - return null; - }); - }); - } - - private void setupCancelButton() { - dialog.getCancelButton().addActionListener(e -> { - newAccount = null; - dialog.dispose(); - parentFrame.requestFocus(); - }); - } -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/gui/controllers/NpmAccountChooserController.java b/cli/src/main/java/ca/weblite/jdeploy/gui/controllers/NpmAccountChooserController.java deleted file mode 100644 index c9c73ef8..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/gui/controllers/NpmAccountChooserController.java +++ /dev/null @@ -1,68 +0,0 @@ -package ca.weblite.jdeploy.gui.controllers; - -import ca.weblite.jdeploy.gui.NpmAccountChooserDialog; -import ca.weblite.jdeploy.gui.SwingExecutor; -import ca.weblite.jdeploy.npm.NpmAccount; -import ca.weblite.jdeploy.npm.NpmAccountInterface; -import ca.weblite.jdeploy.npm.NpmAccountServiceInterface; - -import javax.swing.*; -import java.awt.*; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; - -public class NpmAccountChooserController { - - private static final Executor EDT_EXECUTOR = new SwingExecutor(); - - private final NpmAccountServiceInterface npmAccountService; - - private final Frame parentFrame; - - private NpmAccountInterface selectedAccount; - - public NpmAccountChooserController(Frame parentFrame, NpmAccountServiceInterface npmAccountService) { - this.npmAccountService = npmAccountService; - this.parentFrame = parentFrame; - } - - public CompletableFuture show() { - return npmAccountService.getNpmAccounts().thenComposeAsync(this::showDialog, EDT_EXECUTOR); - } - - public CompletableFuture showDialog(List accounts) { - NpmAccountChooserDialog dialog = new NpmAccountChooserDialog(parentFrame, accounts); - JButton newAccountButton = dialog.getAddAccountButton(); - newAccountButton.addActionListener(evt -> { - EditNpmAccountController controller = new EditNpmAccountController( - dialog, - new NpmAccount("", null), - npmAccountService - ) { - @Override - protected void afterSave(NpmAccountInterface account) { - selectedAccount = account; - dialog.dispose(); - parentFrame.requestFocus(); - } - }; - controller.show(); - - }); - NpmAccountInterface sel = dialog.showDialog(); - if (sel != null) { - selectedAccount = sel; - } - if (selectedAccount != null) { - return npmAccountService.loadNpmAccount(selectedAccount).thenApplyAsync(account -> { - - if (account == null) { - return selectedAccount; - } - return account; - }, EDT_EXECUTOR); - } - return CompletableFuture.completedFuture(selectedAccount); - } -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/npm/AbstractNpmAccountChooser.java b/cli/src/main/java/ca/weblite/jdeploy/npm/AbstractNpmAccountChooser.java deleted file mode 100644 index c9f6bb5b..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/npm/AbstractNpmAccountChooser.java +++ /dev/null @@ -1,17 +0,0 @@ -package ca.weblite.jdeploy.npm; - -import java.util.List; -import java.util.concurrent.Future; - -public abstract class AbstractNpmAccountChooser implements NpmAccountChooserInterface{ - - private final NpmAccountServiceInterface npmAccountService; - - public AbstractNpmAccountChooser(NpmAccountServiceInterface npmAccountService) { - this.npmAccountService = npmAccountService; - } - - public abstract Future selectNpmAccount(List accounts); - - public abstract Future createNpmAccount(); -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccount.java b/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccount.java deleted file mode 100644 index ce5a4081..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccount.java +++ /dev/null @@ -1,21 +0,0 @@ -package ca.weblite.jdeploy.npm; - -public class NpmAccount implements NpmAccountInterface { - - private final String npmAccountName; - private final String npmToken; - - public NpmAccount(String npmAccountName, String npmToken){ - this.npmAccountName = npmAccountName; - this.npmToken = npmToken; - } - @Override - public String getNpmAccountName() { - return npmAccountName; - } - - @Override - public String getNpmToken() { - return npmToken; - } -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountChooserInterface.java b/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountChooserInterface.java deleted file mode 100644 index dac7d552..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountChooserInterface.java +++ /dev/null @@ -1,9 +0,0 @@ -package ca.weblite.jdeploy.npm; - -import java.util.List; -import java.util.concurrent.Future; - -public interface NpmAccountChooserInterface { - Future selectNpmAccount(List accounts); - Future createNpmAccount(); -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountInterface.java b/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountInterface.java deleted file mode 100644 index f8da660d..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountInterface.java +++ /dev/null @@ -1,6 +0,0 @@ -package ca.weblite.jdeploy.npm; - -public interface NpmAccountInterface { - public String getNpmAccountName(); - public String getNpmToken(); -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountServiceInterface.java b/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountServiceInterface.java deleted file mode 100644 index bc63cbc4..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/npm/NpmAccountServiceInterface.java +++ /dev/null @@ -1,14 +0,0 @@ -package ca.weblite.jdeploy.npm; - -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; - -public interface NpmAccountServiceInterface { - CompletableFuture> getNpmAccounts(); - CompletableFuture saveNpmAccount(NpmAccountInterface account); - - CompletableFuture removeNpmAccount(NpmAccountInterface account); - - CompletableFuture loadNpmAccount(NpmAccountInterface accountName); -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/npm/PreferencesNpmAccountService.java b/cli/src/main/java/ca/weblite/jdeploy/npm/PreferencesNpmAccountService.java deleted file mode 100644 index ac7700ef..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/npm/PreferencesNpmAccountService.java +++ /dev/null @@ -1,103 +0,0 @@ -package ca.weblite.jdeploy.npm; - -import ca.weblite.jdeploy.secure.PasswordServiceInterface; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.prefs.BackingStoreException; -import java.util.prefs.Preferences; - -@Singleton -public class PreferencesNpmAccountService implements NpmAccountServiceInterface { - - private final PasswordServiceInterface passwordService; - - @Inject - public PreferencesNpmAccountService(PasswordServiceInterface passwordService) { - this.passwordService = passwordService; - } - - /** - * We'll store account names in a sub-node of Preferences. For example: - * preferences root: /ca/weblite/jdeploy/npm/PreferencesNpmAccountService - * - * Under this node, we can create a child node "npmAccounts" in which each account name - * is stored as a key. The key is the account name, and the value is also the account name, - * so that we can iterate over them in #getNpmAccounts(). - */ - private static final Preferences PREFS = - Preferences.userNodeForPackage(PreferencesNpmAccountService.class) - .node("npmAccounts"); - - @Override - public CompletableFuture> getNpmAccounts() { - return CompletableFuture.supplyAsync(() -> { - List accounts = new ArrayList<>(); - try { - // Retrieve all keys (each key is an npm account name) - String[] keys = PREFS.keys(); - for (String accountNameKey : keys) { - // The stored value for each key might just be the account name itself - String storedAccountName = PREFS.get(accountNameKey, null); - if (storedAccountName != null) { - // The token is not loaded here (null), - // as per requirement: "getNpmAccounts() should return objects with null for the token." - NpmAccountInterface account = new NpmAccount(storedAccountName, null); - accounts.add(account); - } - } - } catch (BackingStoreException e) { - e.printStackTrace(); - } - return Collections.unmodifiableList(accounts); - }); - } - - @Override - public CompletableFuture saveNpmAccount(NpmAccountInterface account) { - return CompletableFuture.runAsync(() -> { - // 1. Save only the npmAccountName in preferences - String accountName = account.getNpmAccountName(); - PREFS.put(accountName, accountName); - - // 2. Save the token in the system keychain - String token = account.getNpmToken(); - if (token != null) { - passwordService.setPassword(accountName, token.toCharArray()).join(); - } - }); - } - - @Override - public CompletableFuture removeNpmAccount(NpmAccountInterface account) { - return CompletableFuture.runAsync(() -> { - String accountName = account.getNpmAccountName(); - - // 1. Remove the account from preferences - PREFS.remove(accountName); - - // 2. Remove the keychain entry for the token - passwordService.removePassword(accountName).join(); - }); - } - - @Override - public CompletableFuture loadNpmAccount(NpmAccountInterface account) { - return CompletableFuture.supplyAsync(() -> { - // "loadNpmAccount() should load the token of the given account from the keychain, - // and return a copy of the provided NpmAccount, but with the token." - - String accountName = account.getNpmAccountName(); - // Load the token from the system keychain - CompletableFuture token = passwordService.getPassword(accountName, "Load NPM token from keychain"); - - // Return a copy of the provided NpmAccount, but with the token - return token.thenApply(t -> new NpmAccount(accountName, new String(t))) - .join(); - }); - } -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/secure/JavaKeyringPasswordService.java b/cli/src/main/java/ca/weblite/jdeploy/secure/JavaKeyringPasswordService.java deleted file mode 100644 index 27b4be54..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/secure/JavaKeyringPasswordService.java +++ /dev/null @@ -1,82 +0,0 @@ -package ca.weblite.jdeploy.secure; - -import com.github.javakeyring.BackendNotSupportedException; -import com.github.javakeyring.Keyring; -import com.github.javakeyring.PasswordAccessException; - -import javax.inject.Singleton; -import java.util.concurrent.CompletableFuture; - -/** - * An implementation of PasswordServiceInterface that uses the cross-platform - * java-keyring library to store/retrieve/remove passwords on Windows - * (via Windows Credential Manager), macOS Keychain, or Linux keyrings. - */ -@Singleton -public class JavaKeyringPasswordService implements PasswordServiceInterface { - - // This is the "service name" or "application ID" used for storing credentials. - // You can choose any string; it simply labels your credentials in the keyring. - private static final String SERVICE_NAME = "com.jdeploy"; - - @Override - public CompletableFuture getPassword(String name, String prompt) { - return CompletableFuture.supplyAsync(() -> { - try { - // The prompt parameter is ignored in this example, - // but you could use it to display a UI message if desired. - - Keyring keyring = Keyring.create(); - // Retrieve the password associated with (service=SERVICE_NAME, account=name). - String password = keyring.getPassword(SERVICE_NAME, name); - // Return as char[] if present, or null if not found. - return password != null ? password.toCharArray() : null; - } catch (PasswordAccessException e) { - throw new RuntimeException(e); - } catch (BackendNotSupportedException e) { - throw new RuntimeException(e); - } - }); - } - - @Override - public CompletableFuture setPassword(String name, char[] password) { - return CompletableFuture.runAsync(() -> { - try { - Keyring keyring = Keyring.create(); - // Convert the char[] to a String for storing in the keyring - String passStr = password != null ? new String(password) : null; - - // If password is null or empty, you might remove the credential instead. - if (passStr == null || passStr.isEmpty()) { - keyring.deletePassword(SERVICE_NAME, name); - } else { - // Store the password for (service=SERVICE_NAME, account=name). - keyring.setPassword(SERVICE_NAME, name, passStr); - } - - // Optionally, overwrite the passStr memory if you want to reduce its exposure - // but keep in mind Java strings are immutable. You might do more rigorous scrubbing. - } catch (BackendNotSupportedException e) { - throw new RuntimeException("Error setting password in keyring", e); - } catch (PasswordAccessException e) { - throw new RuntimeException(e); - } - }); - } - - @Override - public CompletableFuture removePassword(String name) { - return CompletableFuture.runAsync(() -> { - try { - Keyring keyring = Keyring.create(); - // Removes the credential from the keyring if it exists - keyring.deletePassword(SERVICE_NAME, name); - } catch (BackendNotSupportedException e) { - throw new RuntimeException("Error removing password from keyring", e); - } catch (PasswordAccessException e) { - throw new RuntimeException(e); - } - }); - } -} diff --git a/cli/src/main/java/ca/weblite/jdeploy/secure/PasswordServiceInterface.java b/cli/src/main/java/ca/weblite/jdeploy/secure/PasswordServiceInterface.java deleted file mode 100644 index 819667c4..00000000 --- a/cli/src/main/java/ca/weblite/jdeploy/secure/PasswordServiceInterface.java +++ /dev/null @@ -1,10 +0,0 @@ -package ca.weblite.jdeploy.secure; - -import java.util.concurrent.CompletableFuture; - -public interface PasswordServiceInterface { - CompletableFuture getPassword(String name, String prompt); - CompletableFuture setPassword(String name, char[] password); - - CompletableFuture removePassword(String name); -}